4

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.

6
  • 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. Commented Apr 28, 2021 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. Commented Apr 28, 2021 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? Commented Apr 28, 2021 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"; Commented Apr 28, 2021 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. Commented Apr 28, 2021 at 18:16

1 Answer 1

6

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

import ctypes as ct

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

dll = ct.CDLL('./test')

# int get_camera_info(char* description, char* serial_number, char* manufacturer)
dll.get_camera_info.argtypes = ct.c_char_p, ct.c_char_p, ct.c_char_p
dll.get_camera_info.restype = ct.c_int

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

ret = dll.get_camera_info(desc, sn, mfg)

# .decode() assumes UTF-8-encoding of the byte strings returned
# and is needed so the values aren't decorated with b''.
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.

Sign up to request clarification or add additional context in comments.

6 Comments

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.
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?
@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".
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.
@EddieRandom ctypes.POINTER(ctypes.c_short).
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.