Pygmalion
Pygmalion

Reputation: 919

Pass string as a c_ubyte array to C

In C I have a function that expects array of unsigned chars

void writedata(unsigned char *datapos, int datanum)

I would like to pass a standard string from Python instead

writedata = parallel.writedata
writedata.argtypes = [POINTER(c_ubyte), c_int]

a = "test string"
writedata(a, 11)

As far as I understand, string is actually an array of bytes/chars, and a is a pointer. However, ctypes disagrees:

ctypes.ArgumentError: argument 2: <type 'exceptions.TypeError'>: expected LP_c_ubyte instance instead of str

How can I get "real" pointer from a string?

EDIT: David Cullen provided a solution that takes string pointer as a parameter:

writedata.argtypes = [c_char_p, c_int]

That's fine, but I would like to supply both byte arrays and strings to the function. This means that this should also work

ll = [0,1,2,3,4,5,6,7]
uints = (c_ubyte*8)(*ll)
writedata(uints, 8)

I am curious why I can't do both, because in terms of memory I think byte arrays and strings should be the same? Perhaps this is all about pointer conversions?

I also tried make two ctypes connections to the same C function, and this does not work.

SOLUTION: I have reformulated the question and received the best answer here:

https://stackoverflow.com/a/64838842/2957687

Upvotes: 1

Views: 993

Answers (1)

user3657941
user3657941

Reputation:

We can use the same function if we convert strings to match the required argument types:

def writedata(value):
    if isinstance(value, str):
        value = (ctypes.c_ubyte * len(value)) (*bytearray(value))
    writedataf(value, len(value))

To test my theory, I created a very simple library:

#include <stdio.h>

void writedata(unsigned char *datapos, int datanum) {
    for (int index = 0; index < datanum; index++) {
        putchar(datapos[index]);
    }
    putchar('\n');
}

I created a shared library for macos using

clang -Wall -Werror -shared -fpic main.c -o libwritedata.so

I coped the shared library to /usr/local/lib and created this Python script:

import ctypes

writedataf = ctypes.CDLL('/usr/local/lib/libwritedata.so').writedata
writedataf.argtypes = [ctypes.POINTER(ctypes.c_ubyte), ctypes.c_int]

def writedata(value):
    if isinstance(value, str):
        value = (ctypes.c_ubyte * len(value)) (*bytearray(value))
    writedataf(value, len(value))

x = "a short string"
writedata(x)
uints = (ctypes.c_ubyte * len(x)) (*bytearray(x))
writedata(uints)

Output

a short string
a short string

Upvotes: 1

Related Questions