Search code examples
pythonc++ctypes

Create object in C++ and pass pointer out to python


I am attempting to use ctypes to share a C++ object with Python, by creating the object in C++, and passing the pointer out to Python via a C wrapper. I want to be able to act on this object later using the other functions in the Python class, do_something in the code below.

I've tried the following code, but I get a segfault. I'm new to interfacing C with C++ and C with Python, so I'm not sure if I'm doing something fundamentally wrong when passing the pointer, or if the memory is being cleared/shifted by Python garbage-collection, after I create the object?

This question discusses a similar problem for boost, but the answer isn't very useful for ctypes.

object.h

class object {  
public:

  // constructor 
  object() { 
    pointer = nullptr;
  }

  // destructor
  virtual ~object() {
    delete pointer;
    pointer = nullptr;
  }

  // get member functions of object_pointer
  // from C++
  double do_something();

protected:

  // pointer to the object
  object_pointer *pointer;

};

extern "C" {
  object* object_new();
  void object_delete(object *Ob);
  double object_do_something(object *Ob);
}

object.cpp

#include "object.h"
double object::do_something() { return pointer->do_something(); }

extern "C" {
    object *object_new() { return new object(); }
    void object_delete(object *Ob) { delete Ob; }
    double object_do_something(object *Ob) { return Ob->do_something(); }
}

object.py

from ctypes import *

lib = cdll.LoadLibrary('./lib_object.so')
lib.object_new.argtypes = ()
lib.object_new.restype = c_void_p
lib.special_delete.argtypes = c_void_p,
lib.special_delete.restype = None
lib.object_pointer.argtypes = c_void_p
lib.object_pointer.restype = c_void_p

class Object:
    def __init__(self):
        self.obj = lib.object_new()
        print self.obj
    def __del__(self):
        lib.object_delete(self.obj)
    def do_something(self):
        lib.object_do_something(self.obj)

s = Object()
>> 94549743086144
s.do_something()
>> Segfault

Any help would be greatly appreciated!


Solution

  • Notes:

    • None of the files (.cpp, .py) from the question compiled. They contain syntax errors and also semantic errors

    • I don't know what the pointer role was intended to be (it generated syntax errors). I can only assume that a Singleton implementation was attempted

    • Considering the above, instead of pointing the errors (there are many of them) in the existing files, I'm creating a brand new basic example

    • Although CTypes is not the only area to improve, I'm also pointing it out: [Python.Docs]: ctypes - A foreign function library for Python

    object.hpp:

    #pragma once
    
    #if defined(_WIN32)
    #  if defined(OBJECT_EXPORTS)
    #    define OBJECT_EXPORT_API __declspec(dllexport)
    #  else
    #    define OBJECT_EXPORT_API __declspec(dllimport)
    #  endif
    #else
    #  define OBJECT_EXPORT_API
    #endif
    
    
    class Object {
    public:
        Object();
        virtual ~Object();
        virtual double doSomething();
    
    private:
        double m_double;
    };
    
    
    extern "C" {
        OBJECT_EXPORT_API void* objectNew();
        OBJECT_EXPORT_API void objectDel(void *pObj);
        OBJECT_EXPORT_API double objectDoSomething(void *pObj);
    }
    

    object.cpp:

    #define OBJECT_EXPORTS
    #include "object.hpp"
    #include <iostream>
    
    #define DBG_MSG0() std::cout << "CPP - [" << __FILE__ << "] " << __LINE__ << " (" << __FUNCTION__ << ")\n"
    
    
    Object::Object():
        m_double(2.718282)
    {
        DBG_MSG0();
    }
    
    Object::~Object()
    {
        DBG_MSG0();
    }
    
    double Object::doSomething()
    {
        DBG_MSG0();
        return m_double;
    }
    
    
    extern "C" {
    
    void* objectNew()
    {
        DBG_MSG0();
        return new Object();
    }
    void objectDelete(void *pObj)
    {
        DBG_MSG0();
        delete reinterpret_cast<Object*>(pObj);
    }
    
    double objectDoSomething(void *pObj)
    {
        DBG_MSG0();
        if (pObj) {
            return (reinterpret_cast<Object*>(pObj))->doSomething();
        }
        return 0.0;
    }
    
    }
    

    code00py:

    #!/usr/bin/env python
    
    import ctypes as cts
    import sys
    
    
    DLL_NAME = "./libobject.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")
    
    dll = cts.CDLL(DLL_NAME)
    
    objectNew = dll.objectNew
    objectNew.argtypes = ()
    objectNew.restype = cts.c_void_p
    objectDelete = dll.objectDelete
    objectDelete.argtypes = (cts.c_void_p,)
    objectDoSomething = dll.objectDoSomething
    objectDoSomething.argtypes = (cts.c_void_p,)
    objectDoSomething.restype = cts.c_double
    
    
    class ObjectWrapper:
        def __init__(self):
            self.obj = objectNew()
            print("`Object` instance (as a `void *`): 0x{:016X}".format(self.obj))
    
        def __del__(self):
            print("Deleting instance")
            objectDelete(self.obj)
            self.obj = None
    
        def do_something(self):
            print("Doing something")
            return objectDoSomething(self.obj)
    
    
    def main(*argv):
        obj = ObjectWrapper()
        ret = obj.do_something()
        print("do_something() returned: {:.6f}".format(ret))
    
    
    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:

    [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackExchange/StackOverflow/q054594122]> . ~/sopr.sh 
    ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###
    
    [064bit prompt]> ls
    code00.py  object.cpp  object.hpp
    [064bit prompt]> 
    [064bit prompt]> gcc -shared -fPIC -o libobject.so object.cpp -lstdc++
    [064bit prompt]> ls
    code00.py  libobject.so  object.cpp  object.hpp
    [064bit prompt]> 
    [064bit prompt]> python ./code00.py 
    Python 3.10.12 (main, Jun 11 2023, 05:26:28) [GCC 11.4.0] 064bit on linux
    
    CPP - [object.cpp] 30 (objectNew)
    CPP - [object.cpp] 11 (Object)
    `Object` instance (as a `void *`): 0x0000557F47601910
    Doing something
    CPP - [object.cpp] 41 (objectDoSomething)
    CPP - [object.cpp] 21 (doSomething)
    do_something() returned: 2.718282
    Deleting instance
    CPP - [object.cpp] 35 (objectDelete)
    CPP - [object.cpp] 16 (~Object)
    
    Done.
    

    An alternative to manually creating and destroying the (C++) object, is making it static (automatically initialized then the .dll is loaded, and destroyed then the .dll is unloaded).

    Might want to look over: