1

I need to pass a System.IntPtr to a .NET function (Python with pythonnet). This pointer should refer to a struct created in cffi.

I found this:

from CLR.System import IntPtr, Int32
i = Int32(32)
p = IntPtr.op_Explicit(i)

This is what I tried so far

import clr
from cffi import FFI

ffi = FFI()

custom_t = '''
            typedef struct
            {
                float x;
                 float y;
                 float z;

            } float3;
            '''

ffi.cdef(custom_t)

cfloat3 = ffi.new("float3 *")
cfloat3.x = 1.0
cfloat3.y = 2.0
cfloat3.z = 3.0
print(cfloat3)

from System import IntPtr

p = IntPtr.op_Explicit(id(cfloat3)) #works, sort of...
print(p)

#I would rather see something like
p2 = IntPtr.op_Explicit(ffi.addressof(cfloat3).value)
p3 = IntPtr(ffi.cast("float3*", cfloat3))
p4 = IntPtr(ffi.cast("void3*", cfloat3))

#type(_), ffi.typeof(_), ffi.typeof(_).kind, ffi.addressof()

But I am not sure if using IntPtr.op_Explicit is the best solution. It looks a bit like workaround in combination with id() and I am pretty sure there is a better solution.

Joe
  • 6,758
  • 2
  • 26
  • 47
  • This is the p2 in my example, does not work. Or does it for you? – Joe Aug 24 '17 at 12:19
  • I found this: IntPtr is just a .NET type for void*. It doesn't imply any meaning by itself. No pointer type is associated with it even though its name sounds like "pointer to int", it is the equivalent of void* in C/C++. – Joe Aug 24 '17 at 13:05
  • Also found this in the cffi docs: To cast a pointer to an int, cast it to intptr_t or uintptr_t, which are defined by C to be large enough integer types. But something like `p = IntPtr(int(ffi.cast("intptr_t", ...)))` does also not work. – Joe Aug 24 '17 at 13:44
  • does this example with ctypes help? https://mail.python.org/pipermail/pythondotnet/2013-January/001272.html – denfromufa Aug 24 '17 at 18:59
  • I found a similar example earlier, too. Will try that tomorrow. https://mail.python.org/pipermail/pythondotnet/2015-July/001672.html – Joe Aug 24 '17 at 19:11

3 Answers3

3

There is no direct support for CFFI inside Python.Net or vice-versa, so you need to cast the pointer from one library to an integer and reimport that integer into the other library.

From CFFI, intaddr = ffi.cast("intptr_t", p) will give you an integer. Then you can probably do IntPtr(intaddr).

Armin Rigo
  • 12,048
  • 37
  • 48
  • I already tried that, see the comment to my question. But IntPtr still complained. Even when I imported the `System.Int64` or `System.Int32` data type from pythonnet and fed the casted integer to there and then the `Int64/32` to IntPtr. Don't have the code here at the moment. I can paste it tomorrow. – Joe Aug 24 '17 at 16:54
1

FFI has an addressof function built in:

http://cffi.readthedocs.io/en/latest/ref.html#ffi-addressof

lancew
  • 780
  • 4
  • 22
  • This is the p2 in my example, does not work. Or does it for you? – Joe Aug 24 '17 at 12:21
  • Right, sorry. Looking into it further, it seems the code `ffi.new("float3 *")` actually returns a pointer already. Are you sure you cant just use the variable cfloat3 as a pointer? – lancew Aug 24 '17 at 12:27
  • like `p4 = IntPtr(cfloat3)`? – Joe Aug 24 '17 at 12:42
1

The solution to this is to use the Overloads method of IntPtr

from System import IntPtr, Int32, Int64
my_ptr = ffi.cast("intptr_t", my_c_struct)
cs_handle = IntPtr.Overloads[Int64](Int64(int(my_ptr)))

as mentioned here and here.

Here is a working example:

import clr
from cffi import FFI

ffi = FFI()

custom_t = '''
            typedef struct
            {
                float x;
                 float y;
                 float z;

            } float3;
            '''

ffi.cdef(custom_t)

cfloat3 = ffi.new("float3 *")
cfloat3.x = 1.0
cfloat3.y = 2.0
cfloat3.z = 3.0
print(cfloat3)

from System import IntPtr, Int32, Int64

my_ptr = ffi.cast("intptr_t", cfloat3)
print(my_ptr)
print(ffi.typeof(my_ptr))
print(ffi.typeof(my_ptr).kind)
print(ffi.typeof(my_ptr).cname)
print(int(my_ptr))
print('hex', hex(int(my_ptr)))

#you don't need to cast to System.Int64 first, also works without:

#with casting the Python int to System.Int64
cs_handle = IntPtr.Overloads[Int64](int(my_ptr))
print(cs_handle)

#without casting the Python int to System.Int64
cs_handle = IntPtr.Overloads[Int64](Int64(int(my_ptr)))
print(cs_handle)

# using this workaround mentioned in the question also works
p = IntPtr.op_Explicit(int(my_ptr)) #works, sort of...
print('op_Explicit', p)

#careful: do not use the id() of the C structure, it is different and incorrect
print(cfloat3)
print(ffi.addressof(cfloat3[0]))
print('id(cfloat3)', id(cfloat3), hex(id(cfloat3)))

Some further info on what is happening above (found here and there):

  • C#'s IntPtr maps exactly to C/C++'s intptr_t.

  • intptr_t integer type capable of holding a pointer

  • To cast a pointer to an int, cast it to intptr_t or uintptr_t, which are defined by C to be large enough integer types. cffi doc with examples

  • IntPtr is just a .NET type for void*.

  • The equivalent of an unmanaged pointer in the C# language is IntPtr. You can freely convert a pointer back and forth with a cast. No pointer type is associated with it even though its name sounds like "pointer to int", it is the equivalent of void* in C/C++.

  • IntPtr(5) complains that int/long' value cannot be converted to System.IntPtr. It seems like it is trying to cast or something instead of calling the constructor. (found here)

  • Methods of CLR objects have an '_ overloads _', which will soon be deprecated in favor of iPy compatible Overloads, attribute that can be used for this purpose. (found here)

Joe
  • 6,758
  • 2
  • 26
  • 47