I have a shared library exposing a function:
const char* foo(const char* s);
To call it from Python using ctypes, I can do the following:
import ctypes
foo = ctypes.cdll.LoadLibrary('foo.so')
foo.foo.argtypes = [ctypes.c_char_p]
foo.foo.restype = ctypes.c_char_p
print(foo.foo('Hello World!'.encode('utf8')).decode('utf8'))
Is there a way to embed the encode/decode('utf8')
inside the type itself? I want to do the following:
foo.foo.argtypes = [c_utf8_p]
foo.foo.restype = c_utf8_p
print(foo.foo('Hello World!'))
I was able to handle the argtypes
with following:
class c_utf8_p:
@classmethod
def from_param(cls, obj: str):
return obj.encode('utf8')
But cannot figure out the return type. restype
docs say assigning it to anything that is not a ctypes
type is deprecated, plus truncates value to int
which destroys 64-bit pointers. I would like to avoid using errcheck
since logically I am not checking any errors, plus it would require setting two attributes for every function.
There is not a documented way to do what you want with .restype
. I think you have two choices:
Use .errcheck
, since it allows access to the actual return value and can return a modified version. An advantage is it is only setting an extra attribute per function, vs. #2...
Wrapper function. It would be needed for each function that needed post-processing.
Examples:
// test.c - cl /LD test.c
#include <stdlib.h>
#include <string.h>
__declspec(dllexport)
const char* foo(const char* s) {
char* ret = malloc(strlen(s) * 2 + 1);
strcpy(ret, s);
strcat(ret, s);
return ret;
}
__declspec(dllexport)
void free_foo(const char* s) {
free((void*)s);
}
Using .errcheck
:
import ctypes as ct
class c_utf8_p:
@classmethod
def from_param(cls, obj):
return obj.encode()
def decode_bytes(result, func, args):
s = ct.string_at(result).decode()
dll.free_foo(result)
return s
dll = ct.CDLL('./test')
foo = dll.foo
dll.foo.argtypes = c_utf8_p,
foo.restype = ct.POINTER(ct.c_char)
foo.errcheck = decode_bytes
free_foo = dll.free_foo
free_foo.argtypes = ct.POINTER(ct.c_char),
free_foo.restype = None
s = foo('Hello World!')
print(type(s), s)
Output:
<class 'str'> Hello World!Hello World!
Using a wrapper:
import ctypes as ct
dll = ct.CDLL('./test')
_foo = dll.foo
_foo.argtypes = ct.c_char_p,
_foo.restype = ct.POINTER(ct.c_char)
dll.free_foo.argtypes = ct.POINTER(ct.c_char),
dll.free_foo.restype = None
def foo(s):
p = _foo(s.encode())
s = ct.string_at(p).decode()
dll.free_foo(p)
return s
s = foo('Hello World!')
print(type(s), s)
Output:
<class 'str'> Hello World!Hello World!