0

I have the following c function.

/* returns (uint8_t *)outbuf */
uint8_t *func(uint8_t *inbuf, uint32_t inbuf_len, uint32_t *outbuf_len);

This function returns outbuf, the output length is unknown before calling the function so the function receives a pointer to the length as an argument outbuf_len, also the caller is responsible to free outbuf.

I want to get the result of this function from python, so I started writing the following code:

import ctypes as ct

libb = ct.cdll.LoadLibrary('./a.so')
libb.func.restype = ct.c_char_p
    
inbuf = bytearray(inbuf_len)
inbuf = python_data
arr = ct.c_ubyte * inbuf_len
    
outbuf_len = ct.c_uint  # there is no ct.c_uint_p...
    
outbuf = libb.func(arr.from_buffer_copy(inbuf), inbuf_len, outbuf_len)
    
print hexlify(outbuf) #prints only the first 4 bytes of outbuf

The problems i have is:

  1. I didn't find pointer to uint in ctypes types, so how can I pass the outbuf_len pointer to the C function?
  2. when printing the outbuf, only the first 4 bytes that are pointed by the pointer are printed.
  3. How do I free() the outbuf buffer from python?

I have the source of the C function so it is possible to change how arguments are passed the the C function.

Thanks.

1 Answer 1

2

If you'll be passing Python byte strings as the input buffer, here's a way to do it. I made a minimal example of the C call:

test.c

#include <stdint.h>
#include <stdlib.h>

#define API __declspec(dllexport)

// double every character in the input buffer as an example
API uint8_t *func(uint8_t *inbuf, uint32_t inbuf_len, uint32_t *outbuf_len) {
    *outbuf_len = inbuf_len * 2;
    uint8_t* outbuf = malloc(*outbuf_len);
    for(uint32_t i = 0; i < inbuf_len; ++i) {
        outbuf[i*2] = inbuf[i];
        outbuf[i*2+1] = inbuf[i];
    }
    return outbuf;
}

API void freebuf(uint8_t* buf) {
    free(buf);
}

test.py

import ctypes as ct

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

# c_char_p accepts Python byte strings and is compatible with C uint8_t*
# but don't use it for the output buffer because ctypes converts the pointer
# back to a Python byte string and you would lose access to the pointer for
# later freeing it.  Use POINTER(ct.c_char) to get the actual pointer back.
dll.func.argtypes = ct.c_char_p, ct.c_uint32, ct.POINTER(ct.c_uint32)
dll.func.restype = ct.POINTER(ct.c_char)
dll.freebuf.argtypes = ct.POINTER(ct.c_char),
dll.freebuf.restype = None

def func(inbuf):
    outlen = ct.c_uint32()  # create storage for the output length and pass by reference
    outbuf = dll.func(inbuf, len(inbuf), ct.byref(outlen))
    # Slicing the returned POINTER(c_char) returns a Python byte string.
    # If you used POINTER(c_uint8) for the return value instead,
    # you'd get a list of integer byte values.
    data = outbuf[:outlen.value]
    # Can free the pointer now if you want, or return it for freeing later
    dll.freebuf(outbuf)
    return data

print(func(b'ABC'))

Output:

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

Comments

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.