Как открыть класс C ++ для Python без использования Boost?

Мне нужно представить класс C ++ во встроенном питоне с помощью Python C. API.

Другими словами, примерно так: Предоставление экземпляра класса C ++ объекту встроенный интерпретатор python, но без использования Boost.

Приложение, в которое я хочу это поместить, довольно старое, а компилятор, среда и т. Д. Не могут обрабатывать Boost.


person Trog    schedule 17.05.2014    source источник
comment
Может СВИГ? swig.org   -  person Hans Then    schedule 17.05.2014


Ответы (2)


Короче говоря, то, что вы хотите сделать, не очень сложно, если вы понимаете различия между C ++ и Python и позволяете как C ++, так и Python обрабатывать различия между языками. Я нашел самый простой и безопасный метод - использовать ctypes Python для определения оболочки класса Python для вашего класса C ++ и определить - extern «C» - оболочку для соединения вашего класса C ++ с классом Python.

Преимущества этого подхода в том, что Python может обрабатывать все управление памятью, счетчики ссылок и т. Д.; в то время как C ++ может обрабатывать все преобразования типов и обработку ошибок. Также, если в Python C API будут внесены какие-либо будущие изменения, вам не нужно будет об этом беспокоиться. Вместо этого вы можете просто сосредоточиться на том, что важно для вашего кода.

По сравнению с оберткой класса C ++ в API Python C это намного проще! Кроме того, для этого метода не требуется ничего, что не входит в стандартные библиотеки C ++ или Python.

Ниже вы найдете произвольный пример, составленный в основном из других сообщений о переполнении стека (цитируется в оболочке Python). Это я создал, когда пытался понять, как взаимодействовать с Python и C ++. Код сильно прокомментирован с подробностями о том, как реализована каждая часть кода. Это один из способов сделать это.

Оболочка Python:

"""
My C++ & Python ctypes test class.  The following Stack Overflow URLs
either answered my questions as I figured this out, inspired code ideas, 
or where just downright informative.  However there are were other useful
pages here and there that I did not record links for.

http://stackoverflow.com/questions/1615813/how-to-use-c-classes-with-ctypes
http://stackoverflow.com/questions/17244756/python-ctypes-wraping-c-class-with-operators
http://stackoverflow.com/questions/19198872/how-do-i-return-objects-from-a-c-function-with-ctypes
"""

# Define imports.
from ctypes import cdll, c_int, c_void_p, c_char_p

# Load the shared library.
lib = cdll.LoadLibrary("MyClass.dll")

# Explicitly define the return types and argument types.
# This helps both clarity and troubleshooting.  Note that
# a 'c_void_p' is passed in the place of the C++ object.
# The object passed by the void pointer will be handled in
# the C++ code itself.
#
# Each one of the below calls is a C function call contained
# within the external shared library.
lib.createClass.restype = c_void_p
lib.deleteClass.argtypes = [c_void_p]

lib.callAdd.argtypes = [c_void_p, c_void_p]
lib.callAdd.restype  = c_int

lib.callGetID.argtypes = [c_void_p]
lib.callGetID.restype  = c_char_p

lib.callGetValue.argtypes = [c_void_p]
lib.callGetValue.restype  = c_int

lib.callSetID.argtypes = [c_void_p, c_char_p]
lib.callSetID.restype  = c_int

lib.callSetValue.argtypes = [c_void_p, c_int]
lib.callSetValue.restype  = c_int


class MyClass(object):
    """A Python class which wraps around a C++ object.
    The Python class will handle the memory management
    of the C++ object.

    Not that only the default constructor is called for
    the C++ object within the __init__ method.  Once the
    object is defined any specific values for the object
    are set through library function calls.
    """

    def __init__(self, id_str = ""):
        """Initialize the C++ class using the default constructor.

        Python strings must be converted to a string of bytes.
        'UTF-8' is used to specify the encoding of the bytes to
        preserve any Unicode characters.  NOTE: this can make
        for unintended side effects in the C++ code.
        """
        self.obj = lib.createClass()

        if id_str != "":
            lib.callSetID(self.obj, bytes(id_str, 'UTF-8'))

    def __del__(self):
        """Allow Python to call the C++ object's destructor."""
        return lib.deleteClass(self.obj)

    def add(self, other):
        """Call the C++ object method 'add' to return a new 
        instance of MyClass; self.add(other).
    """
        r = MyClass()
        lib.callAdd(self.obj, other.obj, r.obj)
        return r

    def getID(self):
        """Return the C++ object's ID.
        C char string also must be converted to Python strings.
        'UTF-8' is the specified format for conversion to
        preserve any Unicode characters.
        """
        return str(lib.callGetID(self.obj), 'utf-8')

    def getValue(self):
        """Return the C++ object's Value."""
        return lib.callGetValue(self.obj)

    def setID(self, id_str):
        """Set the C++ object's ID string.
        Remember that Python string must be converted to 
        C style char strings.
    """
        return lib.callSetID(self.obj, bytes(id_str, 'utf-8'))

    def setValue(self, n):
        """Set the C++ object's value."""
        return lib.callSetValue(self.obj, n)


if __name__ == "__main__":
    x = MyClass("id_a")
    y = MyClass("id_b")
    z = x.add(y)

    z.setID("id_c")

    print("x.getID = {0}".format(x.getID()))
    print("x.getValue = {0}".format(x.getValue()))
    print()
    print("y.getID = {0}".format(y.getID()))
    print("y.getValue = {0}".format(y.getValue()))
    print()
    print("z.getID = {0}".format(z.getID()))
    print("z.getValue = {0}".format(z.getValue()))

Класс C ++ и оболочка extern C:

#include <iostream>
#include <new>
#include <string>
using namespace std;

// Manually compile with:
// g++ -O0 -g3 -Wall -c -fmessage-length=0 -o MyClass.o MyClass.cpp
// g++ -shared -o MyClass.dll "MyClass.o"

// Check to see if the platform is a Windows OS.  Note that
// _WIN32 applies to both a 32 bit or 64 bit environment.
// So there is no need to check for _WIN64.
#ifdef _WIN32
// On Windows platforms declare any functions meant to be
// called from an external program in order to allow the
// function to be able to be called.  Else define a DEF
// file to allow the correct behaviour. (much harder!)
#define DLLEXPORT __declspec(dllexport)
#endif

#ifndef DLLEXPORT
#define DLLEXPORT
#endif

class MyClass {
    // A C++ class solely used to define an object to test
    // Python ctypes compatibility.  In reality this would
    // most likely be implemented as a wrapper around
    // another C++ object to define the right a compatible
    // object between C++ and Python.

public:
    MyClass() : val(42), id("1234567890") {};
    // Notice the next constructor is never called.
    MyClass(string str) : val(42), id(str) {};
    ~MyClass(){};

    int add(const MyClass* b, MyClass* c) {

        // Do not allow exceptions to be thrown.  Instead catch
        // them and tell Python about them, using some sort of
        // error code convention, shared between the C++ code
        // and the Python code.

        try {
            c->val = val + b->val;

            return 0;

        /*
        } catch(ExceptionName e) {
            // Return a specific integer to identify
            // a specific exception was thrown.
            return -99
        */

        } catch(...) {
            // Return an error code to identify if
            // an unknown exception was thrown.
            return -1;
        } // end try
    }; // end method

    string getID() { return id; };
    int getValue() { return val; };

    void setID(string str) { id = str; };
    void setValue(int n) { val = n; };

private:
    int val;
    string id;
}; // end class

extern "C" {
    // All function calls that Python makes need to be made to
    // "C" code in order to avoid C++ name mangling.  A side
    // effect of this is that overloaded C++ constructors must
    // use a separate function call for each constructor that
    // is to be used.  Alternatively a single constructor can
    // be used instead, and then setters can be used to specify
    // any of an object instance specific values.  Which is
    // what was implemented here.

    DLLEXPORT void * createClass(void) {
        // Inside of function call C++ code can still be used.
        return new(std::nothrow) MyClass;
    } // end function

    DLLEXPORT void deleteClass (void *ptr) {
         delete static_cast<MyClass *>(ptr);
    } // end function

    DLLEXPORT int callAdd(void *a, void *b, void *c) {

        // Do not allow exceptions to be thrown.  Instead catch
        // them and tell Python about them.

        try {
            MyClass * x = static_cast<MyClass *>(a);
            MyClass * y = static_cast<MyClass *>(b);
            MyClass * z = static_cast<MyClass *>(c);

            return x->add(y, z);

        /*
        } catch(ExceptionName e) {
            // Return a specific integer to identify
            // a specific exception was thrown.
            return -99
        */

        } catch(...) {
            // Return an error code to identify if
            // an unknown exception was thrown.
            return -1;
        } // end try
    } // end function

    DLLEXPORT const char* callGetID(void *ptr) {

        try {
            MyClass * ref = static_cast<MyClass *>(ptr);

            // Inside of function call C++ code can still be used.
            string temp = ref->getID();

            // A string must be converted to it "C" equivalent.
            return temp.c_str();

        } catch(...) {
            // Return an error code to identify if
            // an unknown exception was thrown.
            return "-1";
        } // end try
    } // end function

    DLLEXPORT int callGetValue(void *ptr) {

        try {
            MyClass * ref = static_cast<MyClass *>(ptr);
            return ref->getValue();

        } catch(...) {
            // Return an error code to identify if
            // an unknown exception was thrown.
            return -1;
        } // end try
    } // end function

    DLLEXPORT int callSetID(void *ptr, char *str) {

        try {
            MyClass * ref = static_cast<MyClass *>(ptr);

            ref->setID(str);

            return 0;

        } catch(...) {
            // Return an error code to identify if
            // an unknown exception was thrown.
            return -1;
        } // end try
    } // end function

    DLLEXPORT int callSetValue(void *ptr, int n) {

        try {
            MyClass * ref = static_cast<MyClass *>(ptr);

            ref->setValue(n);

            return 0;

        } catch(...) {
            // Return an error code to identify if
            // an unknown exception was thrown.
            return -1;
        } // end try
    } // end function

} // end extern

Примечание: Trog, к сожалению, у меня недостаточно высокой репутации, чтобы публиковать комментарии, так как я новичок в Stack Overflow. В противном случае я хотел бы сначала спросить, доступны ли ctypes Python во встроенной среде Python. Собственно это мой первый пост.

person StormCrow    schedule 18.05.2014
comment
Мой компилятор - Borland C ++ Builder 6, поэтому мы говорим о стандарте C ++ 99. Некоторое время назад Boost прекратил поддерживать этот компилятор, поэтому я не хочу вводить Boost. На самом деле в Интернете есть статья о том, как легко расширить интерпретатор Python с помощью класса, но я забыл, где он был. Я найду его и опубликую здесь, так как у меня есть несколько вопросов относительно этого конкретного способа, предложенного там. - person Trog; 18.05.2014

Чтобы создать модуль расширения, вы можете использовать SWIG. Он генерирует самосогласованный код без каких-либо зависимостей, кроме самого python. Полученные привязки можно было скомпилировать старым компилятором, так как нет ни шаблонов, ни других наворотов. Однако SWIG - не самое легкое занятие. Вы также можете посмотреть PyBindGen.

person yesint    schedule 18.05.2014