The following code works:
test.py:
import ctypes
lib = ctypes.CDLL("./libtest.so")
string_buffers = [ctypes.create_string_buffer(8) for i in range(4)]
pointers = (ctypes.c_char_p*4)(*map(ctypes.addressof, string_buffers))
lib.test(pointers)
results = [s.value for s in string_buffers]
print results
test.c (compiled to libtest.so with gcc test.c -o libtest.so -shared -fPIC):
#include <string.h>
void test(char **strings) {
strcpy(strings[0],"this");
strcpy(strings[1],"is");
strcpy(strings[2],"a");
strcpy(strings[3],"test!");
}
As Aya said, you should make sure there is room for the terminating zero. But I think your main problem was that the string buffer was garbage collected or something similar, as there was no direct reference to it anymore. Or something else is causing trouble in the creation process of the string buffers when no references are stored for them. For example this results in four times the same address instead of different addresses:
import ctypes
pointers = [ctypes.addressof(ctypes.create_string_buffer(8)) for i in range(4)]
print pointers
Answer from Florian Rhiem on Stack OverflowThe following code works:
test.py:
import ctypes
lib = ctypes.CDLL("./libtest.so")
string_buffers = [ctypes.create_string_buffer(8) for i in range(4)]
pointers = (ctypes.c_char_p*4)(*map(ctypes.addressof, string_buffers))
lib.test(pointers)
results = [s.value for s in string_buffers]
print results
test.c (compiled to libtest.so with gcc test.c -o libtest.so -shared -fPIC):
#include <string.h>
void test(char **strings) {
strcpy(strings[0],"this");
strcpy(strings[1],"is");
strcpy(strings[2],"a");
strcpy(strings[3],"test!");
}
As Aya said, you should make sure there is room for the terminating zero. But I think your main problem was that the string buffer was garbage collected or something similar, as there was no direct reference to it anymore. Or something else is causing trouble in the creation process of the string buffers when no references are stored for them. For example this results in four times the same address instead of different addresses:
import ctypes
pointers = [ctypes.addressof(ctypes.create_string_buffer(8)) for i in range(4)]
print pointers
In this line:
results = (c_char_p * 4)(addressof(create_string_buffer(7)))
You're creating a single buffer of 7 bytes, then trying to use it to hold 4 character pointers (which are probably 4 bytes each), and then also copying 4 8-byte strings into the random addresses it might happen to point to. You need to allocate a buffer for each string, and also allocate the array of pointers. Something like this:
s = []
for i in range(4):
s[i] = create_string_buffer(8)
results = (c_char_p * 4)(s);
The callback is invoked with pBuf and nBufLength. pBuf is already allocated with writable memory, but if you ask for pBuf.value, this is converted to an immutable python string.
Instead convert pBuf to an object that can be modified directly:
## If pBuf is c_char_p, then convert to writable object
c = ctypes.cast(pBuf, ctypes.POINTER(ctypes.c_char))
## At this point, you can set individual bytes
## with c[i] = x, but this is dangerous and there's a safer way:
## get address of string
addr = ctypes.addressof(c.contents)
## convert to char[] for safe writing
c2 = (c_char*nBufLength).from_address(addr)
## see how many bytes to write
nb = min(len(msg), nBufLength-1)
c2[:nb] = msg[:nb]
c2[nb+1] = '\0'
ctypes can allocate a buffer object that your C library should be able to write to:
import ctypes
init_size = 256
pBuf = ctypes.create_string_buffer(init_size)
See: http://docs.python.org/2/library/ctypes.html#ctypes.create_string_buffer
Mark's answer is quite helpful in that it passes a character array to the C function, which is what the OP really wanted, but in case there's folks finding their way here who really want to pass a byte-array, an approach seems to be to build a ctypes.c_char backed by the memory of your byte-array, and pass that.
My example here disregards the argument declaration recommend by Mark, which indeed seems like a good idea.
import ctypes
# libFoo.c:
# (don't forget to use extern "C" if this is a .cpp file)
#
# void foo(unsigned char* buf, size_t bufSize) {
# for (size_t n = 0; n < bufSize; ++n) {
# buf[n] = n;
# }
# }
fooLib = ctypes.cdll.LoadLibrary('./lib/libFoo.dylib')
ba = bytearray(10)
char_array = ctypes.c_char * len(ba)
fooLib.foo(char_array.from_buffer(ba), len(ba))
for b in ba:
print b
# 0
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9
The minimum you need (Python 3) is:
hello = b"hello"
encryptPy.encrypt(5, hello)
encryptPy.decrypt(5, hello)
But it is best practice to declare the argument types and return values as well so ctypes can perform argument type checking. Full program:
#!python3
import ctypes as ct
encryptPy = ct.CDLL('/home/aradhak/Documents/libencrypt.so')
encryptPy.encrypt.argtypes ct.c_int, ct.c_char_p
encryptPy.encrypt.restype = None
encryptPy.decrypt.argtypes = ct.c_int, ct.c_char_p
encryptPy.decrypt.restype = None
hello = b"hello"
encryptPy.encrypt(len(hello), hello)
encryptPy.decrypt(len(hello), hello)
Note that when passing a Python byte string, consider the buffer immutable. In this case you only are reading the buffer, but if you need to allow the C function to mutate the string use:
hello = ct.create_string_buffer(b'hello', 5)
Below works as well, but will be length 6. A terminating null will be included.
hello = ct.create_string_buffer(b'hello')