Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does setting ctypes dll.function.restype=c_void_p return long?

Tags:

python

ctypes

It seems odd that even after setting restype, python returns long rather than c_void_p.

For example;

# python code
from ctypes import *
dll = windll.LoadLibrary("my.dll")
dll.my_object_create.restype = c_void_p
x = dll.my_object_create()
print type(x) # prints <type 'long'>

//c++ code
my_object *my_object_create() { return new my_object(); }
void my_object_destroy(my_object *obj) { delete obj; }

I recently had to fix a bug where, feeding x back to another ctypes function, the pointer got trampled. This was fixed by changing the initial dll call to

x = c_void_p(dll.my_object_create())

...I'm guessing somewhere along the line ctypes treated x as 4 bytes long not 8 (64 bit architecture).

So I am wondering if there is a reason why the existing behaviour leads you into this trap?

like image 277
Sideshow Bob Avatar asked Jul 24 '13 16:07

Sideshow Bob


1 Answers

P_get for the 'P' pointer type uses PyLong_FromVoidPtr. If the address fits in a platform long, it returns a Python int; otherwise it returns a Python long, which has variable precision. That's fine, but when passing this integer value as an argument, the default behavior is to convert to a C int, which is 32-bit on all supported platforms.

I think the best solution is to set argtypes to properly convert an argument to a pointer type. Another option is to set restype to a subclass of c_void_p. Using a subclass disables the conversion to a Python integer. GetResult checks this by calling _ctypes_simple_instance, which actually returns the opposite of what its name and the source comment suggest. (In 2.5 this function was named IsSimpleSubType, and the source comment was wrong back then too. The "simple" in question was never the metaclass PyCSimpleType, but the base type _SimpleCData.)

POSIX:

# Configure the interpreter to load visible extension-
# module symbols, such as _ctypes_simple_instance, 
# into the global symbol table.
import sys, DLFCN
sys.setdlopenflags((sys.getdlopenflags() & ~DLFCN.RTLD_LOCAL) |
                   DLFCN.RTLD_GLOBAL)
from ctypes import *

_ctypes_simple_instance = PyDLL(None)._ctypes_simple_instance
_ctypes_simple_instance.argtypes = py_object,
malloc = CDLL(None).malloc

class my_void_p(c_void_p): 
    pass
>>> _ctypes_simple_instance(c_void_p)
0
>>> _ctypes_simple_instance(my_void_p)
1

>>> malloc.restype = c_void_p
>>> type(malloc(100))
<type 'int'>
>>> malloc.restype = my_void_p
>>> type(malloc(100))
<class '__main__.my_void_p'>

Windows:

_ctypes_simple_instance isn't exported by _ctypes.pyd.

from ctypes import *

malloc = cdll.msvcrt.malloc

class my_void_p(c_void_p): 
    pass
>>> malloc.restype = c_void_p
>>> type(malloc(100))          
<class 'int'>
>>> malloc.restype = my_void_p
>>> type(malloc(100))         
<class '__main__.my_void_p'>
like image 143
Eryk Sun Avatar answered Oct 03 '22 00:10

Eryk Sun