3

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!

5
  • What command did you use to compile object.cpp into lib_object.so? Commented Feb 8, 2019 at 14:24
  • 1
    Do you ever change pointer? Otherwise pointer->do_something() dereferences a nullpointer. Also, why is it S->do_something() and not Ob->do_something? Commented Feb 8, 2019 at 15:06
  • Sorry the S->do_something() is a typo, I've fixed it Commented Feb 8, 2019 at 15:52
  • The pointer does get changed: an object is reconstructed from a character buffer, and then that object is pointed to by pointer Commented Feb 8, 2019 at 15:53
  • argtypes should be a list (or tuple): lib.object_pointer.argtypes = [c_void_p]. Commented Feb 8, 2019 at 22:04

1 Answer 1

3

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:

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you, this is very helpful. I will try to provide a proper stand-alone example in the future, rather than pulling random bits from my code...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.