Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issue with passing string from Python to C shared lib using ctypes

Tags:

python

c

ctypes

I'm having difficulty passing string from Python to C by ctypes:

My C code(compiled into a hello.so)

...
typedef struct test{
    unsigned int    a;
    unsigned char*  b;
} my_struct;

int hello(my_struct *in) {
    FILE *fp;
    ...
    fprintf(fp, "%d\t%s\n", in->a, in->b);
    fclose(fp);
    return 0;
}

My Python code:

...
from ctypes import *
...
class TEST_STRUCT(Structure):
    _fields_ = [("a", c_int),
                ("b", c_char_p)]
...
    hello_lib = ctypes.cdll.LoadLibrary("hello.so")
    hello = hello_lib.hello
    hello.argtypes = [POINTER(TEST_STRUCT)]
    name = create_string_buffer(b"test")
    hello_args = TEST_STRUCT(1, name)
    hello(ctypes.byref(hello_args))
...

I get the error: hello_args = TEST_STRUCT(1, name) TypeError: expected string, c_char_Array_5 found

I tried to change c_char_p to c_wchar_p or c_char*5 or c_wchar*5 etc. Sometimes it can run without error, the first int parameter of the struct can be printed correctly, but not the second string pointer, the best I can get is just the first character 't' instead of the whole word "test".

BTW, my python3 version is 3.3.0

like image 374
Alex Avatar asked Jul 01 '16 18:07

Alex


Video Answer


1 Answers

The solution depends on whether you need the data type to be mutable (from Python's perspective).

If don't need the data to be mutable, then don't bother with the buffer constructor. Just pass the bytes object direct to the TEST_STRUCT constructor. eg.

from ctypes import *

class TEST_STRUCT(Structure):
    _fields_ = [("a", c_int),
                ("b", c_char_p)]

hello_args = TEST_STRUCT(1, b"test")

If you need a mutable buffer then you need to specify your char* type slightly differently in the TEST_STRUCT class. eg.

from ctypes import *

class TEST_STRUCT(Structure):
    _fields_ = [("a", c_int),
                ("b", POINTER(c_char))] # rather than c_char_p

name = create_string_buffer(b"test")
hello_args = TEST_STRUCT(1, name)

c_char_p versus POINTER(c_char)

Ostensibly, whilst these two appear similar they are subtly different. POINTER(c_char) is for any buffer of char data. c_char_p is specifically for NULL terminated strings. As a side effect of this condition all c_char_p objects are immutable (on the python side) so as to make this guarantee easier to enforce. eg. {65, 66, 67, 0} is a string ("ABC"), whereas {1, 2, 3} would not be.

like image 146
Dunes Avatar answered Oct 25 '22 18:10

Dunes