Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python using ctypes to pass a char * array and populate results

Tags:

c++

python

c

ctypes

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.

like image 938
bitwit Avatar asked May 22 '13 19:05

bitwit


3 Answers

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
like image 195
Florian Rhiem Avatar answered Nov 06 '22 22:11

Florian Rhiem


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);
like image 4
Lee Daniel Crocker Avatar answered Nov 06 '22 23:11

Lee Daniel Crocker


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.

like image 2
Aya Avatar answered Nov 07 '22 00:11

Aya