6

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

2 Answers 2

8

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

Sign up to request clarification or add additional context in comments.

2 Comments

Unfortunately this example doesn't work on Windows 10 21H2 (64-bit) and Python 3.10. A Debug Assertion fails in the MS Visual C++ run-time library with the message "Buffer is too small".
You should invoke data.value.decode() to get a Python string, not a bytes object. Also, to convert a string to bytes, when the string is a variable, use s.encode() where s is a string.
2

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.