【问题标题】:How to use C++ classes with ctypes?如何将 C++ 类与 ctypes 一起使用?
【发布时间】:2010-12-09 14:30:54
【问题描述】:

我刚刚开始使用 ctypes,我想使用一个 C++ 类,我使用 ctypes 从 python 中导出到一个 dll 文件中。 所以假设我的 C++ 代码看起来像这样:

class MyClass {
  public:
    int test();
...

我会知道创建一个包含此类的 .dll 文件,然后使用 ctypes 在 python 中加载该 .dll 文件。 现在我将如何创建一个 MyClass 类型的对象并调用它的测试函数? ctypes甚至可能吗?或者,我会考虑使用 SWIG 或 Boost.Python,但 ctypes 似乎是小型项目最简单的选择。

【问题讨论】:

    标签: c++ python ctypes


    【解决方案1】:

    简而言之,C++ 没有像 C 那样的标准二进制接口。不同的编译器为相同的 C++ 动态库输出不同的二进制文件,这是由于名称修改和处理库之间堆栈的不同方式函数调用。

    因此,不幸的是,实际上并没有一种可移植的方式来访问 C++ 库一般。但是,对于一次一个编译器来说,这没问题。

    This blog post 还简要概述了为什么目前这不起作用。也许在 C++0x 出来之后,我们会有一个标准的 C++ ABI?在那之前,您可能无法通过 Python 的ctypes 访问 C++ 类。

    【讨论】:

      【解决方案2】:

      除了 Boost.Python(对于需要 C++ 类到 python 类的一对一映射的大型项目,这可能是一个更友好的解决方案),您可以在 C++ 端提供一个 C 接口。它是众多解决方案中的一种,因此它有自己的权衡取舍,但我将介绍它以使那些不熟悉该技术的人受益。为了全面披露,使用这种方法不会将 C++ 连接到 python,而是将 C++ 连接到 C 到 Python。下面我提供了一个满足您要求的示例,以向您展示 C++ 编译器的 extern "c" 工具的一般概念。

      //YourFile.cpp (compiled into a .dll or .so file)
      #include <new> //For std::nothrow
      //Either include a header defining your class, or define it here. 
      
      extern "C"  //Tells the compile to use C-linkage for the next scope.
      {
          //Note: The interface this linkage region needs to use C only.  
          void * CreateInstanceOfClass( void )
          {
              // Note: Inside the function body, I can use C++. 
              return new(std::nothrow) MyClass;
          }
      
          //Thanks Chris. 
          void DeleteInstanceOfClass (void *ptr)
          {
               delete(std::nothrow) ptr; 
          }
      
          int CallMemberTest(void *ptr)
          {
      
              // Note: A downside here is the lack of type safety. 
              // You could always internally(in the C++ library) save a reference to all 
              // pointers created of type MyClass and verify it is an element in that
              //structure. 
              //
              // Per comments with Andre, we should avoid throwing exceptions.  
              try
              {
                  MyClass * ref = reinterpret_cast<MyClass *>(ptr);
                  return ref->Test();
              }
              catch(...)
              {
                 return -1; //assuming -1 is an error condition. 
              }
          }
      
      } //End C linkage scope.
      

      你可以用

      编译这段代码
      gcc -shared -o test.so test.cpp
      #creates test.so in your current working directory.
      

      在您的 python 代码中,您可以执行以下操作(显示 2.7 的交互式提示):

      >>> from ctypes import cdll
      >>> stdc=cdll.LoadLibrary("libc.so.6") # or similar to load c library
      >>> stdcpp=cdll.LoadLibrary("libstdc++.so.6") # or similar to load c++ library
      >>> myLib=cdll.LoadLibrary("/path/to/test.so")
      >>> spam = myLib.CreateInstanceOfClass()
      >>> spam
      [outputs the pointer address of the element]
      >>> value=CallMemberTest(spam)
      [does whatever Test does to the spam reference of the object] 
      

      我确信 Boost.Python 在底层做了类似的事情,但也许理解较低级别的概念会有所帮助。如果您尝试访问 C++ 库的功能并且不需要一对一映射,我会对这种方法感到更加兴奋。

      有关 C/C++ 交互的更多信息,请查看 Sun 的此页面:http://dsc.sun.com/solaris/articles/mixing.html#cpp_from_c

      【讨论】:

      • 确实,这有点乏味,但确实有效。不过,您会特别想提防异常。我认为假设ctypes 模块可以很好地处理抛出异常的C 函数是不安全的。特别是return new MyClass; 语句非常危险,因为它可以引发std::bad_alloc
      • 请添加DestroyInstanceOfClass() 函数。
      • 这是一个很好的观点。我将编辑示例以使用 no throw 变体。另一个技巧是在函数的 C++ 主体中的 try 块中捕获所有异常。
      • Here is,我猜,同样的事情更简单的例子,extern 将 C++ 类包装成 C 函数。 (这件事上也提到了this stackoverflow question
      • 请考虑下面 Gabriel Devilers 的回答,修复 64 位库的内存问题。
      【解决方案3】:

      answer by AudaAero 非常好,但并不完整(至少对我而言)。

      在我的系统(Debian Stretch x64 with GCC and G++ 6.3.0, Python 3.5.3)上,我调用了一个访问该类的成员值的成员函数时,就会出现段错误。 我通过将指针值打印到标准输出来诊断,在包装器中以 64 位编码的 void* 指针在 Python 中以 32 位表示。因此,当它被传递回成员函数包装器时会出现大问题。

      我找到的解决方法是改变:

      spam = myLib.CreateInstanceOfClass()
      

      进入

      Class_ctor_wrapper = myLib.CreateInstanceOfClass
      Class_ctor_wrapper.restype = c_void_p
      spam = c_void_p(Class_ctor_wrapper())
      

      所以缺少两件事:将返回类型设置为 c_void_p(默认为 int)然后创建一个 c_void_p 对象(不仅仅是一个整数)。

      我希望我能写评论,但我仍然缺少 27 个代表点。

      【讨论】:

        【解决方案4】:

        扩展AudaAero'sGabriel Devillers 答案我将通过以下方式完成类对象实例的创建: stdc=c_void_p(cdll.LoadLibrary("libc.so.6")) 使用 ctypes c_void_p 数据类型可确保在 python 中正确表示类对象指针。

        还要确保 dll 的内存管理由 dll 处理(在 dll 中分配的内存也应该在 dll 中释放,而不是在 python 中)!

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-11-01
          相关资源
          最近更新 更多