1

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

Pygmalion
  • 785
  • 2
  • 8
  • 24
  • I think you may find usefull this question : [click me](https://stackoverflow.com/questions/13086318/using-the-python-c-api-to-get-the-values-of-pystrings-in-the-interpreter-as-cstr) – IkarusDeveloper Nov 12 '20 at 19:44
  • @IkarusDeveloper It must be `ctypes`. I already written a huge program and all other `Python` <-> `C` communication is done by `ctypes` – Pygmalion Nov 12 '20 at 19:58
  • 1
    "As far as I understand, string is actually an array of bytes/chars" - a *C* string is, but not a Python string. A Python string is an abstract sequence of Unicode code points, which may have multiple different in-memory representations. – user2357112 Nov 12 '20 at 22:28
  • @user2357112supportsMonica Fair argument. What about if I know there are only ASCII characters, or if I put `b` in front, i.e. `b"test string"`? – Pygmalion Nov 12 '20 at 22:49
  • @Pygmalion: Putting a `b` in front makes it `bytes`, not `str`, so it should work. – ShadowRanger Nov 12 '20 at 23:10
  • @ShadowRanger Well the `ctypes` returns the same error. Even with `b`, it is still `str`. – Pygmalion Nov 13 '20 at 07:19
  • @Pygmalion Are you using Python2 or Python3? –  Nov 15 '20 at 19:35
  • @DavidCullen Python 3 – Pygmalion Nov 17 '20 at 20:07

1 Answers1

1

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
  • +1 Uh, oh, it works, but I am sometimes accessing the same function with actual char/byte arrays, i.e. `uints = (c_ubyte*8)(*name_of_list)`, `writedata(uints, 8)`. Can I have both? – Pygmalion Nov 12 '20 at 21:58
  • @Pygmalion I modifed the script to handle both data types. –  Nov 12 '20 at 22:54
  • I really appreciate the effort, but this will not work for me, because I `malloc` different `struct`s in the C library that have to be available for all functions... If I load several instances of the same C library this `struct`s won't be visible. – Pygmalion Nov 12 '20 at 23:00
  • @Pygmalion I modified the script so that the library is only loaded once. –  Nov 12 '20 at 23:08