I came across some slightly unexpected behaviour with ctypes in IronPython. Maybe this is the wrong way to look at it – I should be amazed that ctypes works at all in IronPython. But I thought I would document it here.
This deals with DLLs, and so is Windows-centric. I need to find out how / if ctypes works on Linux.
Start with a trivial DLL. It contains a single function, nAdd, which takes an array of integers, and a length of the array, and returns the sum of the array elements:
#ifdef __cplusplus
extern "C"
{
#endif
int nAdd(int * pnData, int nDataLen)
{
int nTotal = 0;
for (int i = 0; i < nDataLen; ++i)
{
nTotal = nTotal + pnData[i];
}
return nTotal;
}
#ifdef __cplusplus
}
#endif
and build it using MSYS / MinGW:
gcc -c strange.cpp
gcc -fPIC -shared -o strange.dll strange.o
Now lets call the function in the DLL, using ctypes:
import ctypes
import sys
# define an array of 3 integers
array3type = ctypes.c_int * 3
array3 = array3type()
array3[0] = 4
array3[1] = 5
array3[2] = 21
# define an array of 2 integers
array2type = ctypes.c_int * 2
array2 = array2type()
array2[0] = 65
array2[1] = 34
def call3then2_noTypes():
print("call3then2_noTypes")
dll = ctypes.cdll.LoadLibrary("strange.dll")
dll.nAdd.restype = ctypes.c_int
#dll.nAdd.argtypes = (ctypes.POINTER(ctypes.c_int), ctypes.c_int)
try:
print(" %d" % dll.nAdd(array3, 3))
print(" %d" % dll.nAdd(array2, 2))
except Exception:
e = sys.exc_info()[1]
print(" %s" % e)
print("")
def call2then3_noTypes():
print("call2then3_noTypes")
dll = ctypes.cdll.LoadLibrary("strange.dll")
#dll.nAdd.restype = ctypes.c_int
#dll.nAdd.argtypes = (ctypes.POINTER(ctypes.c_int), ctypes.c_int)
try:
print(" %d" % dll.nAdd(array2, 2))
print(" %d" % dll.nAdd(array3, 3))
except Exception:
e = sys.exc_info()[1]
print(" %s" % e)
print("")
call3then2_noTypes()
call2then3_noTypes()
For Python 2.5+ and Python 3.x, this correctly prints the following:
call3then2_noTypes
30
99
call2then3_noTypes
99
30
So the question is, what will it do in IronPython 2.6?
The answer is unexpected (at least, to me):
call3then2_noTypes
30
expected c_long_Array_3, got c_long_Array_2
call2then3_noTypes
99
expected c_long_Array_2, got c_long_Array_3
It seems to be trying to learn the signature of the function from the first call, but unfortunately, in this case, getting it wrong. Note that the first call to the function was successful in each case – it is the second call that is failing.
Two of the following three methods work for Python 2.5+, Python 3.x and IronPython 2.6:
import ctypes
import sys
# define an array of 3 integers
array3type = ctypes.c_int * 3
array3 = array3type()
array3[0] = 4
array3[1] = 5
array3[2] = 21
# define an array of 2 integers
array2type = ctypes.c_int * 2
array2 = array2type()
array2[0] = 65
array2[1] = 34
def call():
print("call")
dll = ctypes.cdll.LoadLibrary("strange.dll")
dll.nAdd.restype = ctypes.c_int
dll.nAdd.argtypes = (ctypes.POINTER(ctypes.c_int), ctypes.c_int)
try:
print(" %d" % dll.nAdd(array3, 3))
print(" %d" % dll.nAdd(array2, 2))
except Exception:
e = sys.exc_info()[1]
print(" %s" % e)
print("")
def call_voidPointer():
print("call_voidPointer")
dll = ctypes.cdll.LoadLibrary("strange.dll")
dll.nAdd.restype = ctypes.c_int
dll.nAdd.argtypes = (ctypes.c_void_p, ctypes.c_int)
print(" %d" % dll.nAdd(array3, 3))
print(" %d" % dll.nAdd(array2, 2))
print("")
def call_voidPointer_addressOf():
print("call_voidPointer_addressOf")
dll = ctypes.cdll.LoadLibrary("strange.dll")
dll.nAdd.restype = ctypes.c_int
dll.nAdd.argtypes = (ctypes.c_void_p, ctypes.c_int)
print(" %d" % dll.nAdd(ctypes.addressof(array3), 3))
print(" %d" % dll.nAdd(ctypes.addressof(array2), 2))
print("")
call()
call_voidPointer()
call_voidPointer_addressOf()
Extra hint: the output for IronPython 2.6 is as follows:
call3then2
expected c_long, got c_long_Array_3
call_voidPointer
30
99
call_voidPointer_addressOf
30
99