For anyone who finds this, in Python 3 string and bytes are now different. ctypes needs a bytes instance in order to correctly pass the parameter as char*.
test_lib.get_val(b'Test1', byref(a)) will pass the full 'Test1' as a char* to the c function
test_lib.get_val('Test1', byref(a)) will not pass 'Test1' as a char*, from my little bit of testing it passes the first letter of the python str followed by a null charcter.
For anyone who finds this, in Python 3 string and bytes are now different. ctypes needs a bytes instance in order to correctly pass the parameter as char*.
test_lib.get_val(b'Test1', byref(a)) will pass the full 'Test1' as a char* to the c function
test_lib.get_val('Test1', byref(a)) will not pass 'Test1' as a char*, from my little bit of testing it passes the first letter of the python str followed by a null charcter.
1: What is Happening
ctypes does adapt the Python string, correctly guessing that the function takes a char*. Effectively, libx.foo("string") is equivalent to the second example libx.foo(c_char_p("string")) with a little runtime adaptation.
Your intuition about the string being garbage collected shortly after the call is correct. Nothing in Python retains a reference to the adapted c_char_p view of the string, and thus it will be returned to the object pool — likely to be re-issued in the future. Which means that the char* c_ptr will probably point at junk very soon.
2: Different in Effect
The second form allows you to keep a reference to the view passed to the C function. If you changed the example slightly:
>>> s = c_char_p("string")
>>> libx.foo(s)
Then as long as a reference is maintained to s, the c_ptr value will be valid. You could accomplish that by just keeping a reference in the module implementing your binding to libx.
ctypes how to pass string from python to c++ function, and how to return string from c++ function to python - Stack Overflow
Passing byte string from Python to C - Stack Overflow
How to pass Python string to C++ (extern C) function - Stack Overflow
Issue with passing string from Python to C shared lib using ctypes - Stack Overflow
There are a number of problems:
- Define
.argtypesfor 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,
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")
- 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) orcreate_string_buffer(for c_char_p); otherwise, thestrcatin 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').
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.