Here’s a gotcha I came across in ctypes a while ago. Have a look at the code below, and see if you can work out what it will print:
import ctypes
int_array_type = ctypes.c_int * 2
pint_array_type = ctypes.POINTER(ctypes.c_int)
def display_values(pointer_list):
#print("pointer_list = %s" % pointer_list)
for i in range(3):
pint_array = ctypes.cast(pointer_list[i], pint_array_type)
print("values of pointer_list[%d] are [%d, %d]" % (i, pint_array[0], pint_array[1]) )
def wrong():
pointer_list = []
for i in range(3):
int_array = int_array_type()
for j in range(2):
int_array[j] = i + j
pointer_list.append(ctypes.addressof(int_array))
display_values(pointer_list)
wrong()
If you’re using Python 2.x or Python 3.x, then you might be surprised. Your values may vary, but I get:
values of pointer_list[0] are [2, 3]
values of pointer_list[1] are [11187064, 0]
values of pointer_list[2] are [2, 3]
(If you’re using IronPython 2.6.1, the code works, and does not display this particular problem).
Uncommenting line 7 gives us a bit more of a clue. Your values will vary, but mine produces:
pointer_list = [11187064, 11187144, 11187064]
It took me ages to work out what was going on here. The problem seems to be that storing the address of some Python variable (in this case int_array) is not enough to keep it from being reused. The addressof method returns an integer, and this is not a Python reference, so the memory can be reused. And ctypes seems to reuse it every other time.
So one way round this problem would be to keep a reference to it in some other way. There are many ways to do this, but one example would be:
import ctypes
int_array_type = ctypes.c_int * 2
pint_array_type = ctypes.POINTER(ctypes.c_int)
def display_values(pointer_list):
#print("pointer_list = %s" % pointer_list)
for i in range(3):
pint_array = ctypes.cast(pointer_list[i], pint_array_type)
print("values of pointer_list[%d] are [%d, %d]" % (i, pint_array[0], pint_array[1]) )
def right():
pointer_list = []
safe_store = []
for i in range(3):
int_array = int_array_type()
for j in range(2):
int_array[j] = i + j
pointer_list.append(ctypes.addressof(int_array))
safe_store.append(int_array)
display_values(pointer_list)
right()
This produces the correct output:
values of pointer_list[0] are [0, 1]
values of pointer_list[1] are [1, 2]
values of pointer_list[2] are [2, 3]