When wrapping C functions with ctypes what happens when a method returns a NULL pointer to a struct mapped to a python class derived from ctypes.Structure? Also what happens when valid pointers passed to a method are set to NULL values? The following test will help us find out:
//python_ctypes_test.c
#include <stdlib.h>
struct test_struct {
int a;
int b;
int c;
int d;
} typedef test_struct;
test_struct* get_test_struct(const int valid) {
test_struct* newStruct = 0;
if (valid > 0) {
newStruct = malloc(sizeof *newStruct);
newStruct->a = 1;
newStruct->b = 2;
newStruct->c = 3;
newStruct->d = 4;
}
return newStruct;
}
void get_two_floats(const int valid, float* float1, float* float2) {
if (valid > 0) {
*float1 = 1.1f;
*float2 = 2.2f;
} else {
float1 = 0;
float2 = 0;
}
}
#python_ctypes_test.py
import ctypes
import ctypes.util
dll = ctypes.CDLL('libpython_ctypes_test.so')
class _test_struct(ctypes.Structure):
_fields_ = [("a", ctypes.c_int),
("b", ctypes.c_int),
("c", ctypes.c_int),
("d", ctypes.c_int)]
def wrap(self, something):
self.a,self.b,self.c,self.d = something
def unwrap(self):
part1 = self.a,self.b
part2 = self.c,self.d
return part1, part2
dll.get_test_struct.restype = ctypes.POINTER(_test_struct)
dll.get_test_struct.argtypes = [ctypes.c_int]
def py_get_test_struct(valid):
return dll.get_test_struct(valid).contents.unwrap()
def py_get_test_struct_safe(valid):
testStructPtr = dll.get_test_struct(valid)
if testStructPtr:
return testStructPtr.contents.unwrap()
else:
return None
dll.get_two_floats.restype = None
dll.get_two_floats.argtypes = [ctypes.c_int,
ctypes.POINTER(ctypes.c_float), ctypes.POINTER(ctypes.c_float)]
def py_get_two_floats(valid):
float1_value = ctypes.c_float(0)
float1 = ctypes.pointer(float1_value)
float2_value = ctypes.c_float(0)
float2 = ctypes.pointer(float2_value)
dll.get_two_floats(valid, float1, float2)
return float1_value.value, float2_value.value
print py_get_two_floats(True)
print py_get_two_floats(False)
print py_get_test_struct_safe(True)
print py_get_test_struct_safe(False)
print py_get_test_struct(True)
print py_get_test_struct(False)
As it turns out, the developer need not be concerned about the address of a pointer, that has been passed to a function, changing.
print py_get_two_floats(True)
(1.100000023841858, 2.200000047683716)
print py_get_two_floats(False)
(0.0, 0.0)
In the second case the values remain unchanged from their initialized state. This is generally true. Had float1_value and float2_value been initialized to 10 and 20. Those values would have been remained unchanged as well.
As @eryksun points out:
float1 and float2 are local variables that contain the address of the two floats. You can set the value to 0, or any other address, but that's just changing a local variable. That's not the same as dereferencing the pointer (e.g. *float1) to access the floating point value at the address.
In the case of return values things are handled differently. The object returned evaluates to true (non-null) or false (null).
print py_get_test_struct_safe(True)
((1, 2), (3, 4))
print py_get_test_struct_safe(False)
None
Attempting to access a NULL pointer results in a ValueError exception.
print py_get_test_struct(True)
((1, 2), (3, 4))
print py_get_test_struct(False)
Traceback (most recent call last):
File "python_ctypes.py", line 52, in <module>
print py_get_test_struct(False)
File "python_ctypes.py", line 24, in py_get_test_struct
return dll.get_test_struct(valid).contents.unwrap()
ValueError: NULL pointer access
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With