Search code examples
pythonc++dllctypescpython

Python ctypes, char** and DLL


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)

Solution

  • 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: