myLibrary.resolve is undefined, but the general code you need (untested) is:
import ctypes
dll = ctypes.CDLL('your.dll')
Open = dll.Open
Open.argtypes = [ctypes.POINTER(ctypes.c_void_p),ctypes.c_char_p]
Open.restype = ctypes.c_uint16
Handle = ctypes.c_void_p()
result = Open(ctypes.byref(Handle),'c:\\Config.xml')
This assumes you have a DLL named your.dll with a function Open you want to call.
If you truly have a POINTER(c_char) type, it is subscriptable. In the future provide code that reproduces your issue:
>>> p = cast(create_string_buffer(b'Hello, world!'),POINTER(c_char))
>>> p
<ctypes.LP_c_char object at 0x000001C2F6B58848>
>>> p[0]
b'H'
>>> p[:14]
b'Hello, world!\x00'
>>> cast(p,c_char_p).value # only if known to be nul-terminated
b'Hello, world!'
As [Python.Docs]: ctypes - A foreign function library for Python states, you must not use c_char_p with binary data.
Of course that can be ignored, but then surprises (string silently truncated) may occur.
Although it could be exemplified in ~5 lines of code, pasting the whole thing:
dll.c:
#include <stdlib.h>
#if defined(_WIN32)
# define DLL_EXPORT __declspec(dllexport)
#else
# define DLL_EXPORT
#endif
#define LEN 5
typedef struct CharPtrWrapperTag {
int len;
char *data;
} CharPtrWrapper;
DLL_EXPORT CharPtrWrapper *get() {
CharPtrWrapper *ret = malloc(sizeof(CharPtrWrapper));
ret->len = LEN;
ret->data = malloc(LEN * sizeof(char));
ret->data[0] = 'A';
ret->data[1] = 'B';
ret->data[2] = 0;
ret->data[3] = 'C';
ret->data[4] = 'D';
return ret;
}
DLL_EXPORT void release(CharPtrWrapper *pWrap) {
if (pWrap) {
free(pWrap->data);
pWrap->data = NULL;
pWrap->len = 0;
free(pWrap);
}
}
code.py:
#!/usr/bin/env python3
import sys
import ctypes
DLL_NAME = "./dll.dll"
CharPtr = ctypes.POINTER(ctypes.c_char)
class CharPtrWrapper(ctypes.Structure):
_fields_ = [
("len", ctypes.c_int),
("data", CharPtr),
]
CharPtrWrapperPtr = ctypes.POINTER(CharPtrWrapper)
def main():
dll = ctypes.CDLL(DLL_NAME)
get = dll.get
get.restype = CharPtrWrapperPtr
release = dll.release
release.argtypes = [CharPtrWrapperPtr]
wrap_ptr = get()
wrap = wrap_ptr.contents
print("{:}\n Len: {:d}".format(wrap, wrap.len))
for idx in range(wrap.len):
print(" {:d}: {:}".format(idx, wrap.data[idx]))
s = ctypes.cast(wrap.data, ctypes.c_char_p).value[:wrap.len]
print("\nctypes.c_char_p cast: {:}".format(s))
CharArr = ctypes.c_char * wrap.len
char_arr = CharArr(*wrap.data[:wrap.len])
print("CharArr: {:}".format(char_arr.raw))
release(wrap_ptr)
print("\nDone.")
if __name__ == "__main__":
print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
main()
Output:
[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q055103298]> sopr.bat
*** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***
[prompt]> "c:\Install\x86\Microsoft\Visual Studio Community\2015\vc\vcvarsall.bat" x64
[prompt]> dir /b
code.py
dll.c
[prompt]> cl /nologo /DDLL /MD dll.c /link /NOLOGO /DLL /OUT:dll.dll
dll.c
Creating library dll.lib and object dll.exp
[prompt]> dir /b
code.py
dll.c
dll.dll
dll.exp
dll.lib
dll.obj
[prompt]> "e:\Work\Dev\VEnvs\py_064_03.06.08_test0\Scripts\python.exe" code.py
Python 3.6.8 (tags/v3.6.8:3c6b436a57, Dec 24 2018, 00:16:47) [MSC v.1916 64 bit (AMD64)] on win32
<__main__.CharPtrWrapper object at 0x000001279250D248>
Len: 5
0: b'A'
1: b'B'
2: b'\x00'
3: b'C'
4: b'D'
ctypes.c_char_p cast: b'AB'
CharArr: b'AB\x00CD'
Done.
Normally each function you use in ctypes should have its arguments and return type declared so Python can check for the correct number and type of arguments and convert Python object arguments to the correct C data objects. Unfortunately in this case, the normal return value for func would be c_char_p, but ctypes tries to be helpful and convert a c_char_p return value to a Python string, losing access to the raw C pointer value. Instead, you can declare the return type as POINTER(c_char) and use cast to retrieve the string value, which leaves the return value a LP_c_char object that can be freed.
Here's an example. Note that declaring the correct .restype is especially important for 64-bit Python, since the default return type is c_int (32-bit) and a 64-bit pointer could be truncated. This code is tested with both 32- and 64-bit builds.
test.c
#include <string.h>
#include <stdlib.h>
#ifdef _WIN32
# define API __declspec(dllexport)
#else
# define API
#endif
API char* func(const char* str1, const char* str2) {
size_t len = strlen(str1) + strlen(str2) + 1;
char* tmp = malloc(len);
strcpy_s(tmp, len, str1);
strcat_s(tmp, len, str2);
return tmp;
}
API void freeMem(void *mem) {
free(mem);
}
test.py
import ctypes as ct
dll = ct.CDLL('./test')
dll.func.argtypes = ct.c_char_p,ct.c_char_p
dll.func.restype = ct.POINTER(ct.c_char)
dll.freeMem.argtypes = ct.c_void_p,
dll.freeMem.restype = None
# Helper function to extract the return value as a Python object
# and always free the pointer.
def freeMem(a,b):
p = dll.func(b'abcdef', b'ghijkl')
print(p)
s = ct.cast(p, ct.c_char_p).value
dll.freeMem(p)
return s
print(freeMem(b'abcdef', b'ghijkl'))
Output:
<ctypes.LP_c_char object at 0x00000279D7959DC0>
b'abcdefghijkl'
You're on the right track.
// TestDLL.cpp
#include <string.h> // strcpy
extern "C" __declspec(dllexport) char* stringdup(const char* str) {
char* p = new char[strlen(str)+1];
strcpy(p,str);
return p;
}
// if you have no good reason to use void*, use the type
// you've allocated. while it usually works for built-in
// types, it wouldn't work for classes (it wouldn't call
// the destructor)
extern "C" __declspec(dllexport) void stringfree(char* ptr) {
// you don't need to check for 0 before you delete it,
// but if you allocate with new[], free with delete[] !
delete [] ptr;
}
And in python:
# Test.py
import ctypes
lib = ctypes.cdll.TestDLL
# this creates a c-style char pointer, initialized with a string whose
# memory is managed by PYTHON! do not attempt to free it through the DLL!
cstr = ctypes.c_char_p("hello ctypes")
# call the dll function that returns a char pointer
# whose memory is managed by the DLL.
p = lib.stringdup(cstr)
# p is just an integer containing the memory address of the
# char array. therefore, this just prints the address:
print p
# this prints the actual string
print ctypes.c_char_p(p).value
# free the memory through the DLL
lib.stringfree(p)
Thanks to @ErykSun the solution:
Python code
string1 = "my string 1"
string2 = "my string 2"
# create byte objects from the strings
b_string1 = string1.encode('utf-8')
b_string2 = string2.encode('utf-8')
# send strings to c function
my_c_function.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
my_c_function(b_string1, b_string2)
I think you just need to use c_char_p() instead of create_string_buffer().
string1 = "my string 1"
string2 = "my string 2"
# create byte objects from the strings
b_string1 = string1.encode('utf-8')
b_string2 = string2.encode('utf-8')
# send strings to c function
my_c_function(ctypes.c_char_p(b_string1),
ctypes.c_char_p(b_string2))
If you need mutable strings then use create_string_buffer() and cast those to c_char_p using ctypes.cast().
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
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);
ctypes.cast() is used to convert one ctype instance to another ctype datatype. You don't need it To convert it to python string. Just use ".value" to get it in python string.
>>> s = "Hello, World"
>>> c_s = c_char_p(s)
>>> print c_s
c_char_p('Hello, World')
>>> print c_s.value
Hello, World
More info here
If you set the argtypes or restype attributes of ctypes functions, they will return the right Python object without the need for a cast.
Here's an example calling the C-runtime time and ctime functions:
>>> from ctypes import *
>>> m=CDLL('msvcrt')
>>> t=c_long(0)
>>> m.time(byref(t))
1326700130
>>> m.ctime(byref(t)) # restype not set
6952984
>>> m.ctime.restype=c_char_p # set restype correctly
>>> m.ctime(byref(t))
'Sun Jan 15 23:48:50 2012\n'