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
🌐
NumPy
numpy.org › doc › stable › reference › generated › numpy.char.chararray.ctypes.html
numpy.char.chararray.ctypes — NumPy v2.4 Manual
This attribute creates an object that makes it easier to use arrays when calling shared libraries with the ctypes module.
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.
🌐
NumPy
numpy.org › devdocs › reference › generated › numpy.char.chararray.ctypes.html
numpy.char.chararray.ctypes — NumPy v2.5.dev0 Manual
This attribute creates an object that makes it easier to use arrays when calling shared libraries with the ctypes module.
🌐
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) Example #7 ·
🌐
University of Texas at Austin
het.as.utexas.edu › HET › Software › Numpy › reference › generated › numpy.chararray.ctypes.html
numpy.chararray.ctypes — NumPy v1.9 Manual
This attribute creates an object that makes it easier to use arrays when calling shared libraries with the ctypes module.
🌐
LinkedIn
linkedin.com › pulse › calling-c-from-python-using-ctypes-warren-jitsing
Calling C from Python Using Ctypes
August 27, 2021 - We create mutable character buffers using the ctypes.create_string_buffer. This takes an initial bytes object and length of the buffer, or just a length argument. When just a length argument is given, the buffer is initialized with null bytes. >>> in_buffer: ctypes.Array[ctypes.c_char] = ctypes.create_string_buffer(b"Hello World!") >>> out_buffer: ctypes.Array[ctypes.c_char] = ctypes.create_string_buffer(20) >>> in_buffer.raw b'Hello World!\x00' >>> out_buffer.raw b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' We can now run the function and print the results ·
🌐
NumPy
numpy.org › doc › 2.1 › reference › generated › numpy.char.chararray.ctypes.html
numpy.char.chararray.ctypes — NumPy v2.1 Manual
This attribute creates an object that makes it easier to use arrays when calling shared libraries with the ctypes module.
🌐
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
Find elsewhere
🌐
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
🌐
GitHub
gist.github.com › nzjrs › 990493
Python ctypes example · GitHub
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters ... You can define a function which calls malloc() in your shared object and call it through ctypes.
🌐
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.
🌐
SciPy
docs.scipy.org › doc › › numpy-1.9.2 › reference › generated › numpy.chararray.ctypes.html
chararray.ctypes - Numpy and Scipy Documentation
Standard array subclasses · numpy.chararray · index · next · previous · chararray.ctypes¶ · An object to simplify the interaction of the array with the ctypes module. This attribute creates an object that makes it easier to use arrays when calling shared libraries with the ctypes module.
🌐
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).
🌐
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).
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')