| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
There was a problem hiding this comment.
Thank you very much, this is a nice addition!
I've added a few general comments and will look into the Python-API details of this tomorrow.
Sorry, something went wrong.
|
By the way, the failing CI is probably since you didn't add PyBuffer.cs to the old project files. |
Sorry, something went wrong.
|
Fixed most of your comments. But i dont know why git marked almost all lines of the old csproject file as changed, even though i only added on line. I hope this is no problem :) |
Sorry, something went wrong.
|
You probably changed the line endings |
Sorry, something went wrong.
Codecov Report
@@ Coverage Diff @@
## master #980 +/- ##
=======================================
Coverage 86.71% 86.71%
=======================================
Files 1 1
Lines 301 301
=======================================
Hits 261 261
Misses 40 40
Continue to review full report at Codecov.
|
Sorry, something went wrong.
There was a problem hiding this comment.
This PR needs unit tests with good coverage. Currently, basic functionality is non-functional.
Sorry, something went wrong.
| _handle = _gchandle.AddrOfPinnedObject(); | ||
| _view = (Runtime.Py_buffer)Marshal.PtrToStructure(_handle, typeof(Runtime.Py_buffer)); | ||
|
|
||
| success = Runtime.PyObject_GetBuffer(obj, _handle, flags) >= 0; |
There was a problem hiding this comment.
Why return success/failure via out, and not simply throw an exception?
Sorry, something went wrong.
There was a problem hiding this comment.
... in which case you also have to release resources.
Sorry, something went wrong.
There was a problem hiding this comment.
Your right. My bad.
Sorry, something went wrong.
| byte[] rawData = new byte[size]; | ||
| _gchandle = GCHandle.Alloc(rawData, GCHandleType.Pinned); | ||
| _handle = _gchandle.AddrOfPinnedObject(); | ||
| _view = (Runtime.Py_buffer)Marshal.PtrToStructure(_handle, typeof(Runtime.Py_buffer)); |
There was a problem hiding this comment.
This is the only line where _view is initialized, however, at this moment the memory _handle points to is empty (e.g. all zeroes). So the _view is also always empty.
None of the ifs below ever succeed.
Sorry, something went wrong.
There was a problem hiding this comment.
Your wrong. _view should be empty, because PyObject_GetBuffer fills _view with data. Every if below works as expected
Sorry, something went wrong.
There was a problem hiding this comment.
@Sngmng you might be using some funky runtime, which makes this happen. PyObject_GetBuffer does not get a pointer to _view. It gets a pointer to the rawData instead. The call to Marshal.PtrToStructure simply creates a copy of memory it is pointing to.
Sorry, something went wrong.
There was a problem hiding this comment.
Oh my bad i messed up the order of these two lines when i was moving the code into the constructor. The PR was working before this commit tho.
Sorry, something went wrong.
| } | ||
| } | ||
|
|
||
| public PyObject Object => new PyObject(_view.obj); |
There was a problem hiding this comment.
Creating PyObject does not increment reference counter of the object. You have to manually do it in this getter.
Sorry, something went wrong.
There was a problem hiding this comment.
But PyObject_GetBuffer() does. And PyBuffer_Release decrements the reference count... Just read the docs first. https://docs.python.org/3/c-api/buffer.html
Sorry, something went wrong.
There was a problem hiding this comment.
But in this case you actually get a new reference, so you have to increase the recount, or cache the instance of pyobject in constructor.
Sorry, something went wrong.
There was a problem hiding this comment.
Why am i getting a new refference? Isnt PyObject() just wrapping the handle that was already increfed? Whatever, im just going to add a XIncref as you say.
Sorry, something went wrong.
There was a problem hiding this comment.
@Sngmng you are right, that it usually takes a pre-increfed pointer. But each instance requires its own incref. Otherwise when the instance is disposed, all other instances of PyObject pointing to the same reference will simply crash on access.
Sorry, something went wrong.
| public void Release() | ||
| { | ||
| Runtime.PyBuffer_Release(_handle); | ||
| _gchandle.Free(); |
There was a problem hiding this comment.
It would be good to reset these handles to zero, and check, that the buffer has not been released yet.
Sorry, something went wrong.
| public IntPtr buf; | ||
| public IntPtr obj; /* owned reference */ | ||
| public long len; | ||
| public long itemsize; /* This is Py_ssize_t so it can be |
There was a problem hiding this comment.
Py_size_t is not the same as Int64 aka long. There is an ongoing discussion about sizes of Python types.
Sorry, something went wrong.
There was a problem hiding this comment.
Well it works with long and i didnt see a reason to use Py_ssize_t if it will get casted to long anyway. I looked it up in the python source and it litteraly typdefs Py_ssize_t as long. What do you recommend to do?
Sorry, something went wrong.
There was a problem hiding this comment.
I don't know yet, as we did not work it out. C's long is not the same as C#'s long. The later is always Int64.
This code won't work on 32 bit systems. There C's long is almost always == Int32
Sorry, something went wrong.
There was a problem hiding this comment.
@lostmsu What about something like this:
[StructLayout(LayoutKind.Sequential)]
internal struct Py_ssize_t
{
[MarshalAs(UnmanagedType.SysInt)]
private IntPtr size;
public long GetValue()
{
if (Is32Bit)
return size.ToInt32();
else
return size.ToInt64();
}
}
Sorry, something went wrong.
There was a problem hiding this comment.
@Sngmng this struct might not work either, because on 64-bit windows IntPtr is 64-bit, but Py_ssize_t is probably 32-bit. https://stackoverflow.com/questions/384502/what-is-the-bit-size-of-long-on-64-bit-windows
And that problem will have to be solved, before PR is accepted.
Sorry, something went wrong.
There was a problem hiding this comment.
long and thus Py_ssize_t is 4 bytes on Windows (32 and 64 bit) and 8 bytes on other 64 bit systems. Very annoying.
Sorry, something went wrong.
There was a problem hiding this comment.
See my comment below, I made this mistake (assuming Py_ssize_t == long) before ;)
Py_ssize_t is a typedef for intptr_t or ssize_t (on POSIX), so in all relevant cases it is equivalent in size to IntPtr.
Sorry, something went wrong.
Yeah it needs unit tests. But why are you claiming its non functional? Did you even test this PR? It works as expected. Just try out one of the examples i provided, you will see. Apart from that, im going to implement all of your other requested changes like implementing IDisposable later this day |
Sorry, something went wrong.
|
Ive implemented most of the requested changes, but i dont know what you want me to do with the Py_ssize_t fields? Leave them as longs? Or use a struct like i proposed? |
Sorry, something went wrong.
|
@Sngmng unfortunately, the only way I can think of so far is to have two struct types defined, and decide which one to use at runtime. It is a sad state of things, I know. |
Sorry, something went wrong.
|
BTW, there appears to be a number of warnings regarding XML doc comments on the new file in the build, I guess related to the recent refactoring: https://ci.appveyor.com/project/pythonnet/pythonnet/builds/28472200/job/3otegb2hqnq9f6on |
Sorry, something went wrong.
| if (disposedValue) | ||
| throw new ObjectDisposedException("PyBuffer"); | ||
| if (ReadOnly) | ||
| throw new Exception("Buffer is read-only"); |
There was a problem hiding this comment.
InvalidOperationException?
Sorry, something went wrong.
|
Sorry for the confusion, we had this discussion already before (in #531) and the conclusion is that Py_ssize_t is indeed IntPtr (not long, though!), so if you change the longs in the Py_buffer definition and ensure that the copy is done correctly this is fine. (Currently you do the copy to Strides etc. assuming you have 64bit data). |
Sorry, something went wrong.
|
@filmor it might be fine for passing function parameters which will occupy an entire register anyway, but for structs incorrect size will cause the following fields to be incorrectly placed, leading to wrong memory being accessed. If our tests are run on Windows x64 (which we should do exactly for this kind of issue) they will likely catch the problem. |
Sorry, something went wrong.
|
Hmm, it still seems to not quite work on x86, I'll try to reproduce this locally. |
Sorry, something went wrong.
|
@filmor weird, should not be a problem on x86. |
Sorry, something went wrong.
I'm not sure what exactly this one was the answer to, but IntPtr is the correct type (on both 32 and 64 bit systems) and should lead to the correct layout. Interestingly enough, non-xplat built Py3.7 ran through on x86... |
Sorry, something went wrong.
Sorry, something went wrong.
Sorry, something went wrong.
|
Yeah, it doesn't really fail but times out. I'll try to reproduce it locally (running exactly what AppVeyor is supposed to run), but I don't use Windows as my primary dev platform, so I need a while to set everything up. |
Sorry, something went wrong.
| public static long SizeFromFormat(string format) | ||
| { | ||
| if (Runtime.pyversionnumber < 39) | ||
| throw new NotSupportedException("GetPointer requires at least Python 3.9"); |
There was a problem hiding this comment.
Should the message say "SizeFromFormat ..." not "GetPointer ..."?
Sorry, something went wrong.
| /// Copy contiguous len bytes from buf to view. fort can be 'C' or 'F' (for C-style or Fortran-style ordering). 0 is returned on success, -1 on error. | ||
| /// </summary> | ||
| /// <returns>0 is returned on success, -1 on error.</returns> | ||
| public int FromContiguous(IntPtr buf, long len, char fort) |
There was a problem hiding this comment.
Since this is a public method, why not throw a PythonException on failure?
Sorry, something went wrong.
| throw new ObjectDisposedException(nameof(PyBuffer)); | ||
| if (Runtime.pyversionnumber < 36) | ||
| throw new NotSupportedException("ToContiguous requires at least Python 3.6"); | ||
| return Runtime.PyBuffer_ToContiguous(buf, _handle, _view.len, order); |
There was a problem hiding this comment.
Same, why not throw?
Sorry, something went wrong.
| { | ||
| if (disposedValue) | ||
| throw new ObjectDisposedException(nameof(PyBuffer)); | ||
| return Runtime.PyBuffer_FillInfo(_handle, exporter, buf, (IntPtr)len, _readonly, flags); |
There was a problem hiding this comment.
And here
Sorry, something went wrong.
| /// If this function is used as part of a getbufferproc, exporter MUST be set to the exporting object and flags must be passed unmodified.Otherwise, exporter MUST be NULL. | ||
| /// </remarks> | ||
| /// <returns>On success, set view->obj to a new reference to exporter and return 0. Otherwise, raise PyExc_BufferError, set view->obj to NULL and return -1;</returns> | ||
| public int FillInfo(IntPtr exporter, IntPtr buf, long len, int _readonly, int flags) |
There was a problem hiding this comment.
_readonly should be bool
Sorry, something went wrong.
| if (_view.ndim != 1) | ||
| throw new NotSupportedException("Multidimensional arrays, scalars and objects without a buffer are not supported."); | ||
|
|
||
| int copylen = count < (int)_view.len ? count : (int)_view.len; |
There was a problem hiding this comment.
This cast to int should be checked to avoid data loss
Sorry, something went wrong.
There was a problem hiding this comment.
Also, I think it is wrong to silently write _view.len instead of count, when count is > _view.len.
Return the number of bytes actually written, or throw an exception.
Sorry, something went wrong.
| if (_view.ndim != 1) | ||
| throw new NotSupportedException("Multidimensional arrays, scalars and objects without a buffer are not supported."); | ||
|
|
||
| int copylen = count < (int)_view.len ? count : (int)_view.len; |
There was a problem hiding this comment.
This cast to int should be checked to avoid data loss
Sorry, something went wrong.
|
@Sngmng what's your solution architecture? VS has unexpected behavior when project and solution don't match. Also try running tests from command line like CI does it, e.g. with nunit3-console.exe |
Sorry, something went wrong.
|
@lostmsu I ran it through nunit3-console and it timeouts at the exact same line as appveyor. Which is odd because the tests didnt even run... PS D:\Users\user\Downloads\gitrepos\pythonnetsn\src\embed_tests\bin> nunit3-console Python.EmbeddingTest.dll --trace=Verbose
NUnit Console Runner 3.10.0 (.NET 2.0)
Copyright (c) 2019 Charlie Poole, Rob Prouse
Samstag, 2. November 2019 00:00:32
Runtime Environment
OS Version: Microsoft Windows NT 10.0.18362.0
CLR Version: 4.0.30319.42000
Test Files
Python.EmbeddingTest.dll
=> Python.EmbeddingTest.TestDomainReload.DomainReloadAndGC
[Program.Main] === creating domain for assembly test1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
[Program.Main] Before Domain Unload on test1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
[Program.Main] After Domain Unload on test1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
[Program.Main] The Proxy object is not valid anymore, domain unload complete.
[Program.Main] === creating domain for assembly test2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
[Program.Main] Before Domain Unload on test2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
[Program.Main] After Domain Unload on test2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
[Program.Main] The Proxy object is not valid anymore, domain unload complete.
|
Sorry, something went wrong.
|
@Sngmng you can try --inprocess parameter, with and without debugger. E.g. https://docs.microsoft.com/en-us/visualstudio/debugger/how-to-debug-from-a-dll-project?view=vs-2019 |
Sorry, something went wrong.
If i try --inprocess: NUnit.Engine.NUnitEngineException : Cannot run tests in process - a 32 bit process is required. --NUnitEngineException Cannot run tests in process - a 32 bit process is required. bei NUnit.Engine.Runners.MasterTestRunner.InitializePackage() bei NUnit.Engine.TestEngine.GetRunner(TestPackage package) bei NUnit.ConsoleRunner.ConsoleRunner.RunTests(TestPackage package, TestFilter filter) |
Sorry, something went wrong.
|
@Sngmng build fails because of the wrong casing in Py_buffer.cs in csproj file |
Sorry, something went wrong.
|
@Sngmng you can try https://stackoverflow.com/questions/1507268/force-x86-clr-on-an-any-cpu-net-assembly corflags /32bit+ <PathToExe> to force nunit-console to run as 32 bit process (note, this is persisted). If the binary is not AnyCPU, you could try building a x86 binary. |
Sorry, something went wrong.
There was a problem hiding this comment.
Looks good to me, as soon as tests pass in CI.
Maybe update CHANGELOG?
Sorry, something went wrong.
| /// <summary> | ||
| /// Controls the <see cref="PyBuffer.Format"/> field. If set, this field MUST be filled in correctly. Otherwise, this field MUST be NULL. | ||
| /// </summary> | ||
| FORMATS = 0x0004, |
There was a problem hiding this comment.
This is originally named FORMAT and not FORMATS but for some reason i get a "name not cls compilant" warning if i use FORMAT
Sorry, something went wrong.
|
@Sngmng have you tried tests locally after removing finalizer? Do they pass? |
Sorry, something went wrong.
Visual studio fooled me that they pass but they didnt when i retested through nunit3-console. Weird. |
Sorry, something went wrong.
|
@Sngmng a sanity check: if you comment your tests out, do the tests pass? |
Sorry, something went wrong.
|
@lostmsu |
Sorry, something went wrong.
|
@Sngmng hahaha, now I remember facing the same issue. Damn. Looks like we should add GIL checks to many entry points... |
Sorry, something went wrong.
|
@lostmsu yeah would be better hahaha. But the tests seem to fail again... seems like Py.GIL wasnt the only cause.. 😭 |
Sorry, something went wrong.
|
At least the failure is now less erratic in that it fails exactly for all Python 3 versions on Windows running on x86 :) |
Sorry, something went wrong.
I'm not sure about that, maybe the cause is calling PyBuffer_Release in ~PyBuffer, it have not been got GIL before calling PyBuffer_Release(destructor called in GC thread). Also the destructor may call after Py_Finalize. btw. I wonder why the PyBuffer not making an implementation of System.Stream? |
Sorry, something went wrong.
|
A Stream implementation is (I think) out of scope here, but the GIL comment is valid. |
Sorry, something went wrong.
|
@Sngmng can you rebase/merge master and update the PyBuffer finalizer to behave similarly to the one in PyObject (see latest changes in master)? |
Sorry, something went wrong.
yeah sure |
Sorry, something went wrong.
Sorry, something went wrong.
|
Why is appveyor configured to do the conda build? I mean it fails everytime and is ignored anyway? And it takes 6min which is a lot. Removing these checks would improve the test speed to 1min which is a 6 times speed improvement. I am waiting multiple hours for appveyor because it is so slow, just because of the unnecessary conda build... |
Sorry, something went wrong.
|
@Sngmng the issue was just fixed in master by filmor. |
Sorry, something went wrong.
|
Given 2.5 is just around the corner, and in 3.0 I'd like to fix #980 , which might affect the interfaces proposed here, I suggest we get it after 2.5 is released (should be soon). @Sngmng the issues you mentioned are fixed in master, you should be able to update the finalizer now. |
Sorry, something went wrong.
|
Be aware that the last commit to update the finalizer is still untested because i had some problems using nunit and couldnt run the tests. |
Sorry, something went wrong.
Codecov Report
@@ Coverage Diff @@
## master #980 +/- ##
=========================================
Coverage ? 86.66%
=========================================
Files ? 1
Lines ? 300
Branches ? 0
=========================================
Hits ? 260
Misses ? 40
Partials ? 0
Continue to review full report at Codecov.
|
Sorry, something went wrong.
|
@Sngmng oh, your Dispose method was perfectly fine. It is the finalizer (e.g. ~PyBuffer()), that needed to be updated. |
Sorry, something went wrong.
|
Somehow this is already in o-O |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
What does this implement/fix? Explain your changes.
Passing data like images from managed to python will be easyer than ever!
I implemented the python buffer api which makes copying big chunks of data directly into the python object buffer possible!
You can copy strings to python object:
PyObject array = scope.Eval("bytearray(3)"); byte[] managedArr = new UTF8Encoding().GetBytes(new char[] { 'a', 'b', 'c' }); PyBuffer buf = array.GetBuffer(out bool success, 0); if (success) { buf.WriteToBuffer(managedArr, 0, managedArr.Length); buf.Release(); }Or copy images to python byte arrays:
byte[] imageArr = LoadImage(); PyObject array = scope.Eval("bytearray(" + managedArr.Length + ")"); PyBuffer buf = array.GetBuffer(out bool success, 0); if (success) { buf.WriteToBuffer(managedArr, 0, managedArr.Length); buf.Release(); }Does this close any currently open issues?
No
Any other comments?
...
Checklist
Check all those that are applicable and complete.