I have a dll function in c++:
void get_DLLVersion(CAxClass* obj, char ** pVal);
In pVal get_DLLVersion write c string like "1.0.0.1"
In c++ its like:
char *strdll = (char*)malloc(50);
get_DLLVersion(tst, &strdll);
cout << "strdll = "<<strdll<<endl;
I need to use this function in python.
The main problem is how to create char**
and put as 2nd argument of dll function.
I use next code:
import ctypes
libc = ctypes.CDLL("AxECR.so")
ecr = libc.create_object() #return CAxClass* obj
print (libc.get_DLLVersion)
libc.get_DLLVersion.argtypes = [c_void_p, ctypes.POINTER(ctypes.POINTER(c_char))]
dll = ctypes.POINTER(ctypes.POINTER(c_char))
libc.get_DLLVersion(ecr,dll) #don`t work Segmentation fault (core dumped)
Listing [Python.Docs]: ctypes - A foreign function library for Python.
Notes:
To fix this, a buffer (array) can be created via create_string_buffer, then its address passed (via byref) to the function
For the 1st argument, I create singleton CAxClass object that is returned by every createObject call. I could also have the function creating the new instance, but another one would be then required to destroy it, in order to prevent memory leaks (1)
Looking at the way the function is called from C++, it just populates the memory at the address given as an argument (if not NULL, hopefully).
In this case, using a double pointer doesn't make much sense, as the same goal could be achieved using a simple one (I added another function in the example below to prove this)
Example:
dll00.cpp:
#include <cstring>
#include <iostream>
#if defined(_WIN32)
# define DLL00_EXPORT_API __declspec(dllexport)
#else
# define DLL00_EXPORT_API
#endif
#define BUF_LEN 50
class CAxClass {};
static CAxClass *gObj = new CAxClass(); // nullptr;
#if defined(__cplusplus)
extern "C" {
#endif
DLL00_EXPORT_API void* createObject();
DLL00_EXPORT_API void dllVersion(CAxClass *pObj, char **ppVer);
DLL00_EXPORT_API void dllVersionSinglePtr(CAxClass *pObj, char *pVer);
#if defined(__cplusplus)
}
#endif
void* createObject() {
return gObj;
}
void dllVersion(CAxClass *pObj, char **ppVer)
{
if ((ppVer) && (*ppVer)) {
strncpy(*ppVer, "1.22.333.4444", BUF_LEN);
} else {
std::cout << "C - NULL pointer\n";
}
}
void dllVersionSinglePtr(CAxClass *pObj, char *pVer)
{
if (pVer) {
strncpy(pVer, "55555.666666.7777777.88888888", BUF_LEN);
} else {
std::cout << "C - NULL pointer\n";
}
}
code00.py:
#!/usr/bin/env python
import ctypes as cts
import sys
CharPtr = cts.c_char_p # More generic: cts.POINTER(cts.c_char) ?
CharPtrPtr = cts.POINTER(CharPtr)
BUF_LEN = 50
DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")
def main(*argv):
dll = cts.CDLL(DLL_NAME)
createObject = dll.createObject
createObject.argtypes = ()
createObject.restype = cts.c_void_p
dllVersion = dll.dllVersion
dllVersion.argtypes = (cts.c_void_p, CharPtrPtr)
dllVersion.restype = None
# @TODO - cfati: Testing purposes
dllVersionSinglePtr = dll.dllVersionSinglePtr
dllVersionSinglePtr.argtypes = (cts.c_void_p, CharPtr)
dllVersionSinglePtr.restype = None
obj = createObject()
print("Object: {:}".format(obj))
buf = cts.create_string_buffer(BUF_LEN)
dllVersion(obj, cts.byref(cts.cast(buf, CharPtr)))
print("Version: {:}".format(buf.value))
dllVersionSinglePtr(obj, cts.cast(buf, CharPtr))
print("Version: {:}".format(buf.value))
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.\n")
sys.exit(rc)
output:
(qaic-env) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackOverflow/q075446745]> ~/sopr.sh ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ### [064bit prompt]> ls code00.py dll00.cpp [064bit prompt]> [064bit prompt]> g++ -fPIC -shared -o dll00.so dll00.cpp [064bit prompt]> [064bit prompt]> ls code00.py dll00.cpp dll00.so [064bit prompt]> [064bit prompt]> python ./code00.py Python 3.8.10 (default, Nov 14 2022, 12:59:47) [GCC 9.4.0] 064bit on linux Object: 34716928 Version: b'1.22.333.4444' Version: b'55555.666666.7777777.88888888' Done.
Might also check:
[SO]: C++ & Python: Pass and return a 2D double pointer array from python to c++ (@CristiFati's answer) for the use of a double pointer when passing a 2D array
[SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer) for a common pitfall when working with CTypes (calling functions)
[SO]: Python ctypes cdll.LoadLibrary, instantiate an object, execute its method, private variable address truncated (@CristiFati's answer) for an example of footnote #1