15

I'm trying to use ctypes to create a char * array in python to be passed to a library for populating with strings. I'm expecting 4 strings back no more than 7 characters in length each.

My py code looks like this

testlib.py

from ctypes import *
primesmile = CDLL("/primesmile/lib.so")

getAllNodeNames = primesmile.getAllNodeNames
getAllNodeNames.argtypes = [POINTER(c_char_p)]
results = (c_char_p * 4)(addressof(create_string_buffer(7)))
err = getAllNodeNames(results)

lib.cpp

void getAllNodeNames(char **array){
    DSL_idArray nodes; //this object returns const char * when iterated over
    network.GetAllNodeIds(nodes);
    for(int i = 0; i < (nodes.NumItems()); i++){
    strcpy(array[i],nodes[i]);
    }
}

I keep getting segmentation faults when I try to run this code. I've created a test from C that works perfectly but in Python I must be setting up the pointer array incorrectly or something. It seems to get to the second node in the loop and then have a problem as I've seen from spitting out data into the command line. Any insight would be much appreciated.

Eryk Sun
  • 33,190
  • 5
  • 92
  • 111
bitwit
  • 2,589
  • 2
  • 28
  • 33

3 Answers3

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
Florian Rhiem
  • 1,758
  • 1
  • 14
  • 23
  • 3
    You can also create a 2D array for the buffer: `string_buffers = ((ctypes.c_char * 8) * 4)()`. It'll iterate the same when you define `pointers` on the next line. – Eryk Sun May 22 '13 at 19:43
  • Don't we have to `extern "C"` from C side? – ghchoi Jul 04 '18 at 13:36
  • @GyuHyeonChoi `extern"C"` is only needed to tell C++ to create C-compatible symbols (see C++ name mangling). – Florian Rhiem Jul 05 '18 at 06:59
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);
Lee Daniel Crocker
  • 12,927
  • 3
  • 29
  • 55
2

I can't (easily) test the code, but based on what you've said, my guess would be the problem lies in the line...

results = (c_char_p * 4)(addressof(create_string_buffer(7)))

According to the Python docs for create_string_buffer()...

init_or_size must be an integer which specifies the size of the array, or a string which will be used to initialize the array items.

If a string is specified as first argument, the buffer is made one item larger than the length of the string so that the last element in the array is a NUL termination character. An integer can be passed as second argument which allows to specify the size of the array if the length of the string should not be used.

...which implies it's creating a char[7], but a strcpy() will attempt to copy the NULL terminator for a string, so if your maximum "node name" length is seven chars, you'll need a char[8] to hold the NULL, although you might get away with using memcpy(array[i], nodes[i], 7) instead of strcpy().

Either way, it's probably safest to use create_string_buffer(8) instead.

Aya
  • 39,884
  • 6
  • 55
  • 55