Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error when exposing a python method to .Net in python 2. #492

Closed
klasma opened this issue Jun 14, 2017 · 9 comments
Closed

Error when exposing a python method to .Net in python 2. #492

klasma opened this issue Jun 14, 2017 · 9 comments

Comments

@klasma
Copy link

klasma commented Jun 14, 2017

Environment

  • Pythonnet version: 2.3.0
  • Python version: 2.7.13
  • Operating System: Windows 10

Details

We are trying to expose a python method in .Net, in a class that inherits from System.Object.

We are following the example code in clr.py in the pythonnet repository except that we are inheriting from System.Object and that we are using a namespace. When we run

class X(System.Object):
    __namespace__ = "PyTest"
    @clrmethod(int, [str])
    def test(self, x):
        return len(x)

we get the error message

Traceback (most recent call last):
File "test.py", line 80, in
class X(System.Object):
TypeError: Error when calling the metaclass bases
Unable to find an entry point named 'PyIter_Check' in DLL 'python27'.

The error seems to be due to the fact that PyIter_Check is a #define statement and not a function in python. Therefore the import of the function fails in runtime.cs. The error does not occur in python 3 because PyIter_Check is not imported when python 3 is used.

@klasma
Copy link
Author

klasma commented Jun 14, 2017

The definition of PyIterCheck in runtime.cs is

#if PYTHON2
        [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)]
        internal static extern bool PyIter_Check(IntPtr pointer);
#elif PYTHON3
        internal static bool PyIter_Check(IntPtr pointer)
        {
            var ob_type = (IntPtr)Marshal.PtrToStructure(pointer + ObjectOffset.ob_type, typeof(IntPtr));
            IntPtr tp_iternext = ob_type + TypeOffset.tp_iternext;
            return tp_iternext != null && tp_iternext != _PyObject_NextNotImplemented;
        }
#endif

We have tried to implement PyIter_Check for python 2, similarly to the way it is done for python 3, but we have not gotten it to work. Here is what we tried

internal static bool PyIter_Check(IntPtr pointer)
{
    IntPtr pyType = Marshal.ReadIntPtr(pointer, ObjectOffset.ob_type);
    long flags = Marshal.ReadInt64(pyType, TypeOffset.tp_flags);
    IntPtr iternext = Marshal.ReadIntPtr(pyType, TypeOffset.tp_iternext);
    return (flags & Py_TPFLAGS_HAVE_ITER) != 0 && (Int64)iternext != 0;
}

Maybe you can tell us what we are doing wrong.

@rickardraysearch
Copy link
Contributor

I got a bit further, and I believe that I have understood what is happening. ClassDerivedObject.AddPythonMethod calls PyObject.IsIterable() to find out whether it makes sense to call GetEnumerator on that pyobject. PyObject.IsIterable does a PyIter_Check to find out whether the PyObject implements the iterator interface. However, e.g. list is not an iterator, and PyObject.GetEnumerator does not expect it to be - only that there is a valid iter method on it.

So, the test in ClassDerivedObject.AddPythonMethod should probably not be IsIterable, but rather something like HasEnumerator, which would check for the presence of a iter method, not an iternext method as PyIter_Check does.

@den-run-ai
Copy link
Contributor

Here is comparison of macros from CPython C-API:

https://github.com/python/cpython/blob/2.7/Include/abstract.h#L637

#define PyIter_Check(obj) \
    (PyType_HasFeature((obj)->ob_type, Py_TPFLAGS_HAVE_ITER) && \
     (obj)->ob_type->tp_iternext != NULL && \
     (obj)->ob_type->tp_iternext != &_PyObject_NextNotImplemented)

https://github.com/python/cpython/blob/3.6/Include/abstract.h#L711

#define PyIter_Check(obj) \
    ((obj)->ob_type->tp_iternext != NULL && \
     (obj)->ob_type->tp_iternext != &_PyObject_NextNotImplemented)

@rickardraysearch
Copy link
Contributor

Yes, and the only difference there is that in python 3.6, the iter and iternext fields are always present.

@rickardraysearch
Copy link
Contributor

Update: Calling IsIterable() in ClassDerivedObject.AddPythonMethod is probably correct - it's the implementation which returns wheter the object is an iterator, not an iterable...

@rickardraysearch
Copy link
Contributor

Perhaps it's easier to discuss in code. I put up a prospective fix at rickardraysearch@5d052ca

@den-run-ai
Copy link
Contributor

@rickardraysearch feel free to submit a pull request!

@rickardraysearch
Copy link
Contributor

Hi @denfromufa , now there's a PR that passes all tests and might be possible to merge.

@den-run-ai
Copy link
Contributor

Thanks for finding this issue and fixing it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants