How do I use ctypes.create_string_buffer to pass it as a char * argument for a C function?

0

I have a C/C++ dll with a function:

int get_camera_info(char * description, char * serial_number, char * manufacturer)

This function takes the arguments and modifies them, so that you could print them. I'm trying to use this DLL from Python by using ctypes. I guess I would need to use ctypes.create_string_buffer to create an empty buffer so that I have a mutable object. But then, how do I do so that I can pass it as an argument and then retrieve the data?

Do I need to use cast(buffer_object, c_char_p_object)? How would this be any different from creating and passing a c_char_p object in the first place? I don't know much about C/C++, but maybe I should be using ctypes.POINTER.

My problem was that after passing a c_char_p type object as an argument I'm getting the error: OSError: exception: access violation reading 0x00000000.

If you need any help trying to understand what my question is, just ask. Or if you think I'm better off using something like cppyy, then I would need help with that too. :)

python
c++
c
dll
ctypes
asked on Stack Overflow Apr 28, 2021 by Eddie Random • edited Apr 28, 2021 by martineau

1 Answer

0

Here's the basics. Although not strictly required in this case, setting .argtypes and .restype correctly helps ctypes marshal parameters correctly and detect incorrectly passed parameters.

Similar to passing arrays to C functions, create_string_buffer returns an c_char_Array_array_size object, but it is marshaled as a pointer to it's first element (c_char_p) so it agrees with the .argtypes assignment.

Without the .argtypes assignment, passing something incorrect such as an int or a create_unicode_buffer() array would crash or have the wrong result. But defining it correctly would catch those errors and raise an exception.

test.c

#include <string.h>

#ifdef _WIN32
#   define API __declspec(dllexport)
#else
#   define API
#endif

API int get_camera_info(char * description, char * serial_number, char * manufacturer)
{
    strcpy(description, "Description");
    strcpy(serial_number, "12345678");
    strcpy(manufacturer, "Manufacturer");
    return 1;
}

test.py

from ctypes import *

MAX_STR = 256  # docs better say how big the buffers are required to be.

# int get_camera_info(char * description, char * serial_number, char * manufacturer)

dll = CDLL('./test')
dll.get_camera_info.argtypes = c_char_p,c_char_p,c_char_p
dll.get_camera_info.restype = c_int

desc = create_string_buffer(MAX_STR)
sn = create_string_buffer(MAX_STR)
mfg = create_string_buffer(MAX_STR)

ret = dll.get_camera_info(desc,sn,mfg)
print(desc.value.decode(),sn.value.decode(),mfg.value.decode())

Output:

Description 12345678 Manufacturer

Note that .value returns a byte string at the beginning of the buffer up to the first null byte exclusive. .raw will dump a byte string with every byte of the buffer. .decode() converts a byte string into a Unicode string using UTF-8 as the default encoding.

answered on Stack Overflow Apr 28, 2021 by Mark Tolonen • edited Apr 28, 2021 by Mark Tolonen

User contributions licensed under CC BY-SA 3.0