2014-05-17 24 views

回答

5

总之你想要做什么,你了解C++和Python之间的差异,并让C++和Python的处理语言之间的差异是不是只要十分困难。我发现最简单和最安全的方法是使用Python ctypes为您的C++类定义一个Python类包装器,并定义一个 - extern“C”包装器来将您的C++类连接到Python类。

这种方法的优点是Python可以处理所有的内存管理,引用计数,等等;而C++可以处理所有的类型转换和错误处理。此外,如果将来有任何对Python C API的更改,则不需要担心。相反,您可以专注于重要的代码。

相比于Python的C API中包裹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++类&外部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不幸的是我没有足够高的声誉,还发表评论,因为我新的堆栈溢出。否则,我想先问你是否可以在你嵌入式Python环境中使用Python ctypes。其实这是我的第一篇文章。

+0

我的编译器是Borland C++ Builder 6,所以我们讨论的是C++ 99标准。 Boost在前段时间停止支持这个编译器,所以这就是为什么我不想引入Boost。 实际上网上有一篇文章如何轻松地将python解释器扩展为一个类,但我忘记了它在哪里。我会搜索它并在此发布,因为我在那里提出的特定方式几乎没有问题。 – Trog

0

为了使您可以使用SWIG扩展模块。它会生成除python本身之外没有依赖关系的自我一致的代码。获得的绑定可以用旧编译器编译,因为没有模板或其他先进的东西。然而,SWIG并不是最容易学习的东西。你也可以看看PyBindGen

相关问题