0

I have a c function that takes as arguments a void * pointer and an integer length for the size of the buffer pointed to.

e.g.

char* myfunc(void *mybuffer, int buflen)

On the python side I have a bytes object of binary data read from a file.

What I am trying to figure out is the right conversions to be able to call the c function from python, and am struggling a bit.

I understand the conversions for dealing with simple string data (e.g. encoding to utf-8 and using a char_p type) but dealing with a bytes object has been a bit of a struggle....

Thanks in advance!

3
  • 1
    The answer depends on the function. Is the return value a null-terminated string? An allocated string that must be freed later? Can it contain embedded nulls? For the mybuffer parameter, will the contents be mutable? Provide a simple implementation in C of the function, and your attempt at calling it with Python. See minimal reproducible example. Commented Jun 15, 2022 at 0:41
  • 1
    Also, if your data comes from a binary file, you shouldn't use c_char_p (but POINTER(c_char)). Commented Jun 15, 2022 at 5:55
  • @MarkTolonen yes, the return will be a null terminated string with no embedded nulls (the return value is not a problem to handle in other cases where I've used c-python interactions). The challenge I'm having is identifying the proper argument as well as conversions for arbitrary binary data as input to the called c-function. No, the input data will not be mutated in anyway by either caller or callee. I'll update to a more complete example later today. Commented Jun 15, 2022 at 17:33

1 Answer 1

2

Given your commented description, you can just use the obvious types if you don't need to free the returned char* memory. You can pass a bytes object to a void*. Here's a quick demo:

test.c

#include <stdio.h>
#include <stdint.h>

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

API char* myfunc(void *mybuffer, int buflen) {
    const uint8_t* tmp = (const uint8_t*)mybuffer;
    for(int i = 0; i < buflen; ++i) // show the passed bytes
        printf("%02X\n", tmp[i]);
    return "output"; // static string no deallocation required
}

test.py

import ctypes as ct
import os

dll = ct.CDLL('./test')
dll.myfunc.argtypes = ct.c_void_p, ct.c_int
dll.myfunc.restype = ct.c_char_p

buf = bytes([1,2,0,0xaa,0x55])  # including embedded null
ret = dll.myfunc(buf, len(buf))
print(ret)

Output:

01
02
00
AA
55
b'output'
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks. I didn't realize you could hand in a straight bytes object with no other changes like that to a void* arg. Much appreciated.
If I have ctypes.POINTER(ctypes.c_int16) in argtypes definition, can I pass in a buf = bytes(...)-object as ctypes.cast(buf, ctypes.POINTER(ctypes.c_int16))? Will it marshal the pointer correctly?
@VadimKantorov That should normally just be submitted as a question, but in this case just try it (hint: it works).
I tried it, it seems to work okay, but I first was confused that a = bytes([1,2,3,4]); print(b := ctypes.cast(a, ctypes.POINTER(ctypes.c_int16))); print(c := ctypes.cast(a, ctypes.POINTER(ctypes.c_int16))); prints two different addresses <__main__.LP_c_short object at 0x7f6f02a2a540>, <__main__.LP_c_short object at 0x7f6f02a2a240>, but ctypes.cast(b, ctypes.c_void_p).value and ctypes.cast(c, ctypes.c_void_p).value do print the same, so I'm hoping that no buffer re-allocations happened behind the scenes...
@VadimKantorov Again, ask a question. Code in comments is not readable. Those addresses are of the python objects, not the buffers they represent

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.