如何在ctypes中使用C++类?

74 投票
5 回答
67753 浏览
提问于 2025-04-15 15:19

我刚开始接触ctypes,想在Python中使用一个我从C++导出的类,这个类在一个dll文件里。假设我的C++代码大概是这样的:

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

我会创建一个包含这个类的.dll文件,然后在Python中用ctypes加载这个.dll文件。现在,我该如何创建一个MyClass类型的对象,并调用它的测试函数呢?用ctypes能做到吗?另外,我也考虑过使用SWIG或Boost.Python,但ctypes对小项目来说似乎是最简单的选择。

5 个回答

20

AudaAero的回答很好,但对我来说还不够完整。

在我的系统上(使用的是Debian Stretch x64,GCC和G++ 6.3.0,Python 3.5.3),每当我调用一个成员函数去访问类的成员值时,就会出现段错误(segfault)。我通过打印指针值到标准输出发现,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个声望点。

65

除了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

49

简单来说,C++没有像C语言那样的标准二进制接口。不同的编译器生成的C++动态库的二进制文件是不一样的,这主要是因为名字修饰(name mangling)和处理库函数调用时栈的方式不同。

所以,很遗憾,通常情况下没有一种通用的方法可以访问C++库。不过,对于某一个编译器来说,使用起来是没问题的。

这篇博客也简单介绍了为什么现在这样做行不通。也许在C++0x发布后,我们会有一个标准的ABI(应用二进制接口)用于C++?在那之前,你可能无法通过Python的ctypes来访问C++类。

撰写回答