Мне нужно выставить класс C ++ во встроенный Python, используя Python C API.
Другими словами что-то вроде этого:
Предоставление экземпляра класса C ++ встроенному интерпретатору Python
но без использования Boost вообще.
Приложение, в которое я хочу поместить это, довольно старое, и компилятор, окружение и т. Д. Не могут обрабатывать Boost.
Короче говоря, то, что вы хотите сделать, не очень сложно, если вы понимаете различия между C ++ и Python и позволяете и C ++, и Python обрабатывать различия между языками. Метод, который я нашел самым простым и безопасным, состоит в том, чтобы использовать типы Python для определения оболочки класса Python для вашего класса C ++ и определения — extern «C» — оболочки для соединения вашего класса C ++ с классом Python.
Преимущества этого подхода в том, что Python может обрабатывать все управление памятью, подсчет ссылок и т. Д .; в то время как C ++ может обрабатывать все преобразования типов и обработку ошибок. Также, если в будущем появятся какие-либо изменения в Python C API, вам не нужно об этом беспокоиться. Вместо этого вы можете просто сосредоточиться на том, что важно для вашего кода.
По сравнению с переносом класса C ++ в Python C API это намного проще! Кроме того, этот метод не требует ничего, не включенного ни в стандартные библиотеки 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_intclass 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 ++ & внешняя обертка 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
Примечание: Трог, к сожалению, у меня пока недостаточно высокой репутации, чтобы оставлять комментарии, так как я новичок в Stack Overflow. В противном случае я хотел бы спросить, был ли Python ctypes доступен в вашей встроенной среде Python. На самом деле это мой первый пост.
Для того, чтобы сделать модуль расширения вы можете использовать SWIG. Он генерирует самосогласованный код без каких-либо зависимостей, кроме самого Python. Полученные привязки могут быть скомпилированы со старым компилятором, так как нет шаблонов или других улучшений. Однако SWIG — не самая простая вещь для изучения. Вы также можете посмотреть на PyBindGen.