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 Overflow
Top answer
1 of 3
12

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
2 of 3
4

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);
🌐
Python
docs.python.org › 3 › library › ctypes.html
ctypes — A foreign function library for Python
This function creates a mutable character buffer. The returned object is a ctypes array of c_char.
🌐
ProgramCreek
programcreek.com › python › example › 3579 › ctypes.c_char
Python Examples of ctypes.c_char
Args: pointer : int or ctypes pointer address of a buffer count : int Number of items to read. dtype : data-type, optional Data-type of the returned array; default: float. Examples: >>> s = numpy.ones(3, dtype=numpy.int32) >>> ptr = s.ctypes.data >>> frompointer(ptr, count=6, dtype=numpy.int16) [1, 0, 1, 0, 1, 0] ''' dtype = numpy.dtype(dtype) count *= dtype.itemsize buf = (ctypes.c_char * count).from_address(pointer) a = numpy.ndarray(count, dtype=numpy.int8, buffer=buf) return a.view(dtype)
🌐
Python.org
discuss.python.org › python help
Dealing with char*** in ctypes - Python Help - Discussions on Python.org
December 13, 2023 - I have this function in C library that I want to access from python using ctypes: static const char *names[] = {"Backplane", "ADC", "External", "Reserved"}; int test_char_ptr(const char ***names_, int *count_) { *names_ = names; *count_ = 4; return 0; } I can then do this in C app: const char **items; unsigned nr_items; result = test_char_ptr(&items, &nr_items); for (unsigned i = 0; i
🌐
Bytes
bytes.com › topic › python › answers › 837648-ctypes-get-full-contents-character-array
ctypes: Get full contents of character array - Python
December 12, 2008 - Your requests are coming too fast. Please slow down your access rate to continue browsing our site
🌐
ProgramCreek
programcreek.com › python › example › 1243 › ctypes.c_char_p
Python Examples of ctypes.c_char_p
def c_str_array(strings): """Create ctypes const char ** from a list of Python strings. Parameters ---------- strings : list of string Python strings. Returns ------- (ctypes.c_char_p * len(strings)) A const char ** pointer that can be passed to C API.
Find elsewhere
🌐
The Coding Forums
thecodingforums.com › archive › archive › python
ctypes: Get full contents of character array | Python | Coding Forums
September 14, 2008 - _fields_ = [...("array", c_char * 12)...] however, ctypes seems to try to return struct.array as a Python string rather than a character array, and stops as soon as it encounters a null within the character array. I ended up having to define a dummy struct class dummystruct(Structure): _fields_ = [] and declare array as: ("array", dummystruct) then use string_at(byref(struct.array), 12).
🌐
Google Groups
groups.google.com › g › comp.lang.python › c › Ggmvia6XRj8
ctypes return char array with null chars
Is there any way to get the full array back including null chars? ... Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message ... "chris cannady" <boo...@gmail.com> wrote in message news:b103e85a-05d5-4195...@b33g2000yqc.googlegroups.com... ... It could depend on how your struct is declared. Maybe this demo will help? >>> x=(c_char*10)() >>> x <__main__.c_char_Array_10 object at 0x00A049E0> >>> x.raw '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' >>> x.value ''
🌐
Post.Byes
post.bytes.com › home › forum › topic › python
ctypes: Get full contents of character array - Post.Byes - Bytes
September 12, 2008 - _fields_ = [...("array", c_char * 12)...] however, ctypes seems to try to return struct.array as a Python string rather than a character array, and stops as soon as it encounters a null within the character array. > I ended up having to define a dummy struct class dummystruct(Str ucture): _fields_ = [] > and declare array as: ("array", dummystruct) > then use string_at(byref (struct.array), 12).
🌐
Crankuptheamps
devnull.crankuptheamps.com › documentation › html › 5.2.1.0 › dev-guides › python › html › chapters › advanced-topics.html
12. Advanced Topics — Python Developer Guide 5.2.1.0 documentation
The ctypes module handles type conversions between Python and C types. In this case, the module passes the underlying Python client as the first argument of the C function. The two Python strings are passed as NULL-terminated char * arrays.
Top answer
1 of 2
19

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
2 of 2
6

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')