1

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)
BRAiNPet
  • 33
  • 4
  • (1) Explain "don't work". (2) Is there documentation about `get_DLLVersion` which describes what the parameters should be? – Michael Butscher Feb 14 '23 at 10:50
  • @AlanBirtles Without exactly knowing the problem of the OP you can't know if the referred question solves it. – Michael Butscher Feb 14 '23 at 10:54
  • 1
    @MichaelButscher its fairly reasonable to assume that a function called `get_DLLVersion` is supposed to return a string so `pVal` should be a pointer to a `char` array – Alan Birtles Feb 14 '23 at 11:00
  • 1
    @AlanBirtles The OP needs to create an *instance* of a pointer assuming it is an output parameter and pass `byref` and also set `.restype` of `create_object`. The duplicate proposed isn’t sufficient – Mark Tolonen Feb 14 '23 at 11:03
  • @AlanBirtles In addition to Mark's comment there are libraries around who may e.g. not provide a null-terminated string via `pVal` but a string of fixed length instead. – Michael Butscher Feb 14 '23 at 11:10
  • @Mark Tolonen what `.restype` of `create_objec` must be? I add an example with c++ code, maybe it helps to understand how get_DLLVersion works. – BRAiNPet Feb 14 '23 at 11:41
  • @BRAiNPet is is odd that the parameter is a `char**` since if the user allocated the memory a `char*` is sufficient. Are you sure that `strdll` maintains its value after the call? – Mark Tolonen Feb 14 '23 at 18:11
  • @BRAiNPet I would declare `class CAxClass(ctypes.c_void_p): pass` as an opaque pointer to the class, and use `.restype = ctypes.POINTER(CAxClass)` as the return type. – Mark Tolonen Feb 14 '23 at 19:21

1 Answers1

1

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

    • An explicit cast (from char array to char pointer) is required
  • 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:

CristiFati
  • 38,250
  • 9
  • 50
  • 87