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.

Answer from Mark Tolonen on Stack Overflow
🌐
Python
docs.python.org › 3 › library › ctypes.html
ctypes — A foreign function library for Python
This function is similar to the cast operator in C. It returns a new instance of type which points to the same memory block as obj. type must be a pointer type, and obj must be an object that can be interpreted as a pointer.
🌐
CodersLegacy
coderslegacy.com › home › python › python ctypes tutorial
Python ctypes Tutorial - CodersLegacy
October 27, 2022 - Hence it worked! Every ctype datatype has a value attribute, which returns a native python object. Hence we were able to return a string from the char pointer (which is basically C’s version of a string).
Top answer
1 of 2
2

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!'
2 of 2
1

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.
🌐
Python.org
discuss.python.org › python help
Dealing with char*** in ctypes - Python Help - Discussions on Python.org
December 13, 2023 - I have this function in C library that I want to access from python using ctypes: static const char *names[] = {"Backplane", "ADC", "External", "Reserved"}; int test_char_ptr(const char ***names_, int *count_) { …
🌐
ProgramCreek
programcreek.com › python › example › 1243 › ctypes.c_char_p
Python Examples of ctypes.c_char_p
def c_str(string): """Create ctypes char * from a Python string. Parameters ---------- string : string type Python string. Returns ------- str : c_char_p A char pointer that can be passed to C API.
Top answer
1 of 3
16

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'
2 of 3
10

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)
🌐
Runebook.dev
runebook.dev › en › docs › python › library › ctypes › ctypes.c_wchar_p
Handling Unicode Pointers with Python's ctypes.c_wchar_p
The alternative for these cases is ctypes.c_char_p. Use c_char_p when The C function expects a char* pointer, typically for UTF-8 or ASCII/ANSI strings. Conversion You must explicitly encode your Python string into bytes (usually UTF-8 or whatever encoding the C function expects) before passing it.
🌐
Behaviour
behaviour.space › posts › 2021-03-28-ctypes-weird-and-inconvenient-typing.html
The weird and inconvenient typing of Python's ctypes library
March 28, 2021 - Then install Pythas using pip and run python. Using Valgrind’s own massif memory profiler I created two graphs showing the issue. In the first graph we’ll use the code from above. Pythas is designed with this issue in mind. It doesn’t actually hand over char-pointers but char-pointer-pointers! Hence, ctypes doesn’t automatically convert the string and we can dereference the pointer after conversion.
Find elsewhere
🌐
dbader.org
dbader.org › blog › python-ctypes-tutorial
Extending Python With C Libraries and the “ctypes” Module – dbader.org
September 5, 2017 - The ctypes.POINTER objects are not overly useful, but they can be converted to objects which are useful. Once we convert our string to a ctypes.c_char, we can access it’s value attribute to get the bytes in Python.
Top answer
1 of 3
12

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
2 of 3
4

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);
🌐
Turing
turing.com › kb › ctypes-modules-and-their-implementation-in-python
Ctypes Modules and their implementation in Python.
Each ctypes data type has an attribute that returns a native Python object that will help you return a string from the char pointer.
🌐
Python
python-list.python.narkive.com › pJK2bMLY › ctypes-delay-conversion-from-c-char-p-to-string
ctypes: delay conversion from c_char_p to string
Post by Brendan Miller I have a function exposed through ctypes that returns a c_char_p. Since I need to deallocate that c_char_p, it's inconvenient that ctypes copies the c_char_p into a string instead of giving me the raw pointer. I believe this will cause a memory leak, unless ctypes is smart enough to free the string itself after the copy...
🌐
GitHub
gist.github.com › nzjrs › 990493
Python ctypes example · GitHub
Learn more about bidirectional Unicode characters ... You can define a function which calls malloc() in your shared object and call it through ctypes. ... http://www.johnstowers.co.nz/blog/index.php/2011/07/15/interfacing-python-c-opencv-via-ctypes/ has changed to http://johnstowers.co.nz/2011/07/15/interfacing-python-c-opencv-via-ctypes/
🌐
Bytes
bytes.com › home › forum › topic › python
ctypes and unsigned char* - Post.Byes
August 19, 2007 - I also tried dll.MagickGetIm ageProfile.rest ype = POINTER(c_ubyte ) with the same result. But maybe I should do some further debugging.... Best regards, Oliver ... Re: ctypes and unsigned char* En Mon, 20 Aug 2007 08:30:50 -0300, oliver.andrich@ gmail.com <oliver.andrich @gmail.comescri bi�: On 19 Aug., 20:40, Gabriel Genellina <gagsl-...@yahoo.com.a rwrote: >In the first case, use a plain c_char_p - the individual "chars" are >already unsigned in Python (that is, ord(xxx[i]) is always positive) >In the second case, first define the pointer type: >> >c_ubyte_p = POINTER(c_ubyte ) >your_function.