如何使用 ctypes 来使用 C++ 类?

2025-03-20 08:48:00
admin
原创
39
摘要:问题描述:我刚刚开始使用 ctypes,并希望使用我在 python 中使用 ctypes 在 dll 文件中导出的 C++ 类。因此,假设我的 C++ 代码如下所示:class MyClass { public: int test(); ... 我知道要创建一个包含此类的 .dll 文件,然后使...

问题描述:

我刚刚开始使用 ctypes,并希望使用我在 python 中使用 ctypes 在 dll 文件中导出的 C++ 类。因此,假设我的 C++ 代码如下所示:

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

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


解决方案 1:

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

解决方案 2:

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

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

这篇博文还简要概述了为什么目前无法做到这一点。也许在 C++0x 发布后,我们会有一个标准的 C++ ABI?在那之前,您可能无法通过 Python 的 访问 C++ 类ctypes

解决方案 3:

AudaAero 的答案非常好但不完整(至少对我来说)。

在我的系统(Debian Stretch x64,带有 GCC 和 G++ 6.3.0,Python 3.5.3)上,我一调用访问类成员值的成员函数就会出现段错误。我通过将指针值打印到 stdout 来诊断,包装器中 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和Gabriel Devillers 的回答,我将通过以下方式完成类对象实例的创建:
stdc=c_void_p(cdll.LoadLibrary("libc.so.6"))
使用ctypes c_void_p数据类型确保在 python 中正确表示类对象指针。

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

解决方案 5:

我遇到了同样的问题。通过反复试验和一些互联网研究(不一定非常了解 g++ 编译器或 C++),我找到了这个对我来说似乎很有效的解决方案。

//model.hpp
class Model{
public:
    static Model* CreateModel(char* model_name) asm("CreateModel"); // static method, creates an instance of the class
    double GetValue(uint32_t index) asm("GetValue"); // object method
}
#model.py
from ctypes import ...
if __name__ == '__main__':
    # load dll as model_dll

    # Static Method Signature
    fCreateModel = getattr(model_dll, 'CreateModel') # or model_dll.CreateModel
    fCreateModel.argtypes = [c_char_p]
    fCreateModel.restype = c_void_p

   # Object Method Signature
    fGetValue = getattr(model_dll, 'GetValue') # or model_dll.GetValue
    fGetValue.argtypes = [c_void_p, c_uint32] # Notice two Params
    fGetValue.restype = c_double


    # Calling the Methods
    obj_ptr = fCreateModel(c_char_p(b"new_model"))
    val = fGetValue(obj_ptr, c_int32(0)) # pass in obj_ptr as first param of obj method


>>> nm -Dg libmodel.so 
                 U cbrt@GLIBC_2.2.5
                 U close@GLIBC_2.2.5
00000000000033a0 T CreateModel # <----- Static Method
                 U __cxa_atexit@GLIBC_2.2.5
                 w __cxa_finalize@GLIBC_2.2.5
                 U fprintf@GLIBC_2.2.5
0000000000002b40 T GetValue # <----- Object Method
                 w __gmon_start__
...
...
... # Mangled Symbol Names Below
0000000000002430 T _ZN12SHMEMWrapper4HashEPKc
0000000000006120 B _ZN12SHMEMWrapper8info_mapE
00000000000033f0 T _ZN5Model12DestroyModelEPKc
0000000000002b20 T _ZN5Model14GetLinearIndexElll

首先,我可以extern "C"完全避免使用指令,而是使用asm关键字,据我所知,该关键字要求编译器在将函数导出到共享对象库的符号表时使用给定的名称而不是生成的名称。这使我能够避免使用 C++ 编译器自动生成的奇怪符号名称。它们看起来有点像_ZN1...您在上面看到的模式。然后在使用 Python ctypes 的程序中,我能够使用我给它们的自定义名称直接访问类函数。该程序看起来像fhandle = mydll.myfuncfhandler = getattr(mydll, 'myfunc')而不是fhandle = getattr(mydll, '_ZN12...myfunc...')。当然,您可以只使用长名称;这没有什么区别,但我认为较短的名称更干净一些,并且不需要使用nm来读取符号表并首先提取名称。

其次,本着 Python 面向对象编程风格的精神,我决定尝试将我的类的对象指针作为类对象方法的第一个参数传入,就像我们self在 Python 对象方法中将其作为第一个方法传入一样。令我惊讶的是,它成功了!请参阅上面的 Python 部分。显然,如果您将fhandle.argtypes参数中的第一个参数设置为c_void_ptr并传入从类的静态工厂方法中获得的 ptr,程序应该可以干净地执行。类静态方法似乎像在 Python 中一样按预期工作;只需使用原始函数签名即可。

我在 Arch Linux 上使用 g++ 12.1.1、python 3.10.5。希望这对某些人有帮助。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2482  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1533  
  PLM(产品生命周期管理)项目对于企业优化产品研发流程、提升产品质量以及增强市场竞争力具有至关重要的意义。然而,在项目推进过程中,范围蔓延是一个常见且棘手的问题,它可能导致项目进度延迟、成本超支以及质量下降等一系列不良后果。因此,有效避免PLM项目范围蔓延成为项目成功的关键因素之一。以下将详细阐述三大管控策略,助力企业...
plm系统   0  
  PLM(产品生命周期管理)项目管理在企业产品研发与管理过程中扮演着至关重要的角色。随着市场竞争的加剧和产品复杂度的提升,PLM项目面临着诸多风险。准确量化风险优先级并采取有效措施应对,是确保项目成功的关键。五维评估矩阵作为一种有效的风险评估工具,能帮助项目管理者全面、系统地评估风险,为决策提供有力支持。五维评估矩阵概述...
免费plm软件   0  
  引言PLM(产品生命周期管理)开发流程对于企业产品的全生命周期管控至关重要。它涵盖了从产品概念设计到退役的各个阶段,直接影响着产品质量、开发周期以及企业的市场竞争力。在当今快速发展的科技环境下,客户对产品质量的要求日益提高,市场竞争也愈发激烈,这就使得优化PLM开发流程成为企业的必然选择。缺陷管理工具和六西格玛方法作为...
plm产品全生命周期管理   0  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用