3

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. :)

martineau
  • 119,623
  • 25
  • 170
  • 301
Eddie Random
  • 33
  • 1
  • 4
  • *This function takes the arguments and modifies them* -- No it doesn't modify the pointer. That is impossible. What is modified is what is being pointed to -- the actual pointer value remains the same. – PaulMcKenzie Apr 28 '21 at 18:02
  • *I guess I would need to use ctypes.create_string_buffer* -- Is the buffer sized correctly? The DLL trusts that you have room for whatever string data will be copied to the buffer. – PaulMcKenzie Apr 28 '21 at 18:07
  • As I said, I don't know much if anything about C/C++. But I know that (in C++ code) if I pass that argument and then I try printing it, it gives me the camera info I'm looking for. I'm sure you are correct in that it doesn't modify the pointer (just what its pointing to), but do you understand what I'm trying to do? – Eddie Random Apr 28 '21 at 18:10
  • I sized it like this: create_string_buffer(256), because in the original c++ code it would have a line like this: char description[256] = "\0"; – Eddie Random Apr 28 '21 at 18:13
  • Well then the issue is a Python one, and I'm not a Python expert. Try something like: `string_buffer = create_string_buffer(256)`, create 3 of those, and pass them as arguments to the function. – PaulMcKenzie Apr 28 '21 at 18:16
  • [\[SO\]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer)](https://stackoverflow.com/questions/58610333/c-function-called-from-python-via-ctypes-returns-incorrect-value/58611011#58611011). – CristiFati Apr 28 '21 at 18:50

1 Answers1

4

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.

Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
  • Thank you, I think I had my code exactly like that except for the ```.decode()``` and when the camera was connected it wouldn't print anything (maybe at the time I was using a ```ctypes.c_char_p``` instead of ```create_string_buffer``` so it wouldn't change its value). I don't have access to the camera right now, so it crashes with ```OSError: exception: access violation reading 0x00000000```. I think the code would be correct now, so when I'm able to connect the camera I'll verify and confirm this as the correct answer. – Eddie Random Apr 29 '21 at 09:04
  • One more thing. If the argument was ```short * ```, would the argtype be ```ctypes.POINTER(ctypes.c_short)```? And how would the argument that I'm passing be mutable? – Eddie Random Apr 30 '21 at 16:37
  • 1
    @EddieRandom If it is just a pointer to one `short`, you create an instance `x = ctypes.c_short()`, then pass it as `ctypes.byref(x)`. If you need an array, use `(ctypes.c_short * array_size)()`. Multiplying a ctypes type by a size create an array type, and "calling" it makes an instance of the array. Pass it without `byref`, otherwise it is a "pointer to short array" and not "pointer to short". – Mark Tolonen Apr 30 '21 at 17:50
  • And would the argtype be ```ctypes.POINTER``` as I thought or would it be something else? I already have ```(ctypes.c_short * array_size)()``` to create the instance before passing it. This Monday I'll verify when I connect the camera. – Eddie Random Apr 30 '21 at 18:29
  • 1
    @EddieRandom `ctypes.POINTER(ctypes.c_short)`. – Mark Tolonen Apr 30 '21 at 18:31
  • Thank you! I was really stuck – Eddie Random Apr 30 '21 at 18:36