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.
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);
}
#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(); }
}
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!
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: