Roe
Roe

Reputation: 71

cTypes - passing a string as a pointer from python to c

I'm trying to pass a string (as a pointer) from python to a C function using cTypes.

The c function needs to get a pointer to a string (array of chars) but I haven't successfully got this to work with multiple chars, but my only success(kind of successful, look at the output) is with 1 char and would love for some help!

I need to send a pointer of a string to char - (unsigned char * input)

My Python Code:

def printmeP(CHAR):
    print("In Print function")
    print(CHAR)
    c_sends = pointer(c_char_p(CHAR.encode('utf-8')))
    print("c_sends: ")
    print(c_sends[0])
    python_p_printme(c_sends)
    print("DONE function - Python")
    print(c_sends[0])
    return


from ctypes import c_double, c_int,c_char, c_wchar,c_char_p, c_wchar_p, pointer, POINTER,create_string_buffer,  byref, CDLL
import sys

lib_path = '/root/mbedtls/programs/test/mylib_linux.so' .format(sys.platform)


CHAR = "d"

try:
    mylib_lib = CDLL(lib_path)
except:
     print('STARTING...' )
python_p_printme = mylib_lib.printme
python_p_printme.restype = None
printmeP(CHAR)


My C Code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void printme(char * param) {
    printf("\nStart c Function!\n");
    printf("%s\n Printing param\n ", param);
    char add = '!';
    strncat(param, &add, 1);

    printf("%s\n Printing param\n ", param);
    printf("Function Done - c\n");
}

My Output:

In Print function
d <--- this is what i am sending
c_sends: 
b'd' <--- this is after encoding
��[� <-------------=|
 Printing param       |
 ��[�               | This is the c code print
 Printing param        | **Question:** Why does it print '�' and no what's supposed to be printed
 Function Done - c <--=|
DONE function - Python
b'd!' <--------------------- this is the last print that prints after the change.

Would love for some help, thanks to everyone that participated :)

sincerely, Roe

Upvotes: 6

Views: 7541

Answers (2)

Mark Tolonen
Mark Tolonen

Reputation: 177481

There are a number of problems:

  1. Define .argtypes for your functions. It will catch errors passing incorrect parameters. Add the line below and note it is plural and is a tuple of argument types. The comma makes a 1-tuple:

python_p_printme.argtypes = c_char_p,

  1. Once you make that change, you'll get an error because this code:

    c_sends = pointer(c_char_p(CHAR.encode('utf-8')

is actually sending a C char** (a pointer to a c_char_p). Once you've set the argtypes properly, you can just call the function with a byte string and it will work. Your function becomes:

def printmeP(CHAR):
    print("In Print function")
    print(CHAR)
    python_p_printme(CHAR.encode())
    print("DONE function - Python")
  1. There is one more subtle problem. While the program may appear to work at this point Python strings are immutable so if the function being called requires a mutable string, you must allocate a mutable buffer using either create_unicode_buffer (for c_wchar_p) or create_string_buffer (for c_char_p); otherwise, the strcat in your C code is going to corrupt the Python string.

Here's a full example:

test.cpp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// For Windows compatibility
#ifdef _WIN32
#   define API __declspec(dllexport)
#else
#   define API
#endif

// For C++ compiler compatibility
#ifdef __cplusplus
extern "C" {
#endif

API void alter_me(char* param, size_t length) {
    // truncates if additional info exceeds length
    strncat_s(param, length, " adding some info", length - 1);
}

#ifdef __cplusplus
}
#endif

test.py

from ctypes import *

lib = CDLL('./test')
alter_me = lib.alter_me
alter_me.argtypes = c_char_p,c_size_t
alter_me.restype = None

data = create_string_buffer(b'test',size=10)
alter_me(data,sizeof(data))
print(data.value)

data = create_string_buffer(b'test',size=50)
alter_me(data,sizeof(data))
print(data.value)

Output:

b'test addi'
b'test adding some info'

Note you do not need to use create_string_buffer if the C function does not alter the buffer, such as if the C parameter is const char*. Then you could just call printme(b'test string').

Upvotes: 8

Stephan Schlecht
Stephan Schlecht

Reputation: 27096

You could use create_string_buffer.

The documentation can be found here: https://docs.python.org/3/library/ctypes.html#ctypes.create_string_buffer

ctypes.create_string_buffer(init_or_size, size=None)

This function creates a mutable character buffer. The returned object is a ctypes array of c_char.

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

With buf.value.decode("utf-8") you can convert the buffer back to a UTF-8 python string.

A small example with your C code library might look like this:

from ctypes import *

mylib_lib = cdll.LoadLibrary("<your-lib>")
mylib_lib.printme.argtypes = c_char_p,

buf = create_string_buffer(128)
buf.value = b'Hello World'
mylib_lib.printme(buf)
print("print on python side:", buf.value.decode("utf-8"))

It would output:

Start c Function!
Hello World
...
print on python side: Hello World!

on the console.

Upvotes: 2

Related Questions