如何使用Cython包装C++类?

31

我有一个C++类。它由一个.ccp文件和一个.h文件组成。它可以编译(我可以编写一个使用它的主方法来成功地使用c++)。我该如何使用Cython包装此类以使其在Python中可用?

我已经阅读了文档,但不明白。他们谈论生成cpp文件。当我试图遵循文档时,我的已经存在的cpp会被清除...

我应该在pyx文件中放什么? 我被告知类定义,但需要放多少? 只需要公共方法吗?

我需要一个.pxd文件吗? 我不明白这个文件何时需要或不需要。

我尝试在#python IRC频道中问这些问题,但无法得到答案。


如果您的问题已得到解答,请将此问题标记为已解答。否则,请再次说明您的问题。 - Niklas R
@NiklasR,你的回答解决了我一些困惑,但还没有完全解决。我已经给你点赞,但打算在有空的时候写一个完整的答案来解决其他问题。 - Endophage
4个回答

22
尽管Cython通常用于与C语言一起使用,但它也可以生成C++代码。编译时,您需要添加--cplus标志。
现在,创建类的包装器很简单,与包装结构体并没有太大的区别。主要的区别就是声明extern,但这并没有太大的区别。
假设您有一个名为MyCppClass的类,在mycppclass.h中定义。
cdef extern from "mycppclass.h":
    cppclass MyCppClass:
        int some_var

        MyCppClass(int, char*)
        void doStuff(void*)
        char* getStuff(int)

cdef class MyClass:

    # the public-modifier will make the attribute public for cython,
    # not for python. Maybe you need to access the internal C++ object from
    # outside of the class. If not, you better declare it as private by just
    # leaving out the `private` modifier.
    # ---- EDIT ------
    # Sorry, this statement is wrong. The `private` modifier would make it available to Python,
    # so the following line would cause an error es the Pointer to MyCppClass
    # couldn't be converted to a Python object.
    #>> cdef public MyCppClass* cobj
    # correct is:
    cdef MyCppClass* obj

    def __init__(self, int some_var, char* some_string):
        self.cobj = new MyCppClass(some_var, some_string)
        if self.cobj == NULL:
            raise MemoryError('Not enough memory.')

    def __del__(self):
        del self.cobj

    property some_var:
        def __get__(self):
            return self.cobj.some_var
        def __set__(self, int var):
            self.cobj.some_var = var

请注意,当设置了--cplus标志时,才能使用new关键字,否则请从<stdlib.h>中使用malloc并将其声明为外部。此外,请注意您不需要解引用指针(->)来调用方法。Cython会跟踪对象的类型并应用适合的内容。
.pxd文件用于将声明与实现分离或避免命名空间冲突。想象一下,如果您想将Python包装器命名为C++类名称,只需在.pxd文件中放置extern声明并在.pyx文件中 pxd文件即可。
cimport my_pxd
cdef my_pxd.SomeExternedType obj

请注意,您不能在.pxd文件中编写实现代码。

所以我的 C++ 代码使用了 OpenSSL 的功能。我需要在我的 pxd 文件中声明这些函数吗? - Endophage
此外,我可以将C++转换为C语言,但这并没有帮助我更进一步解决问题... - Endophage
任何你想在Cython中使用的函数都需要在Cython的作用域内声明。它可以在你的.pyx.pxd文件中声明,这取决于项目的复杂性以及你是否想要将Python函数的名称与它们的C++原始名称完全相同。为了保持整洁,你需要根据项目的实际情况进行选择。 - Niklas R
“cdef MyCppClass* obj” 在 C++ 中看起来像是一个“静态成员”。那么这样做的目的是什么? - Frozen Flame

9

经过许多尝试、试错、尖叫和拔光头发,我终于搞定了这个问题。但首先,我不得不将我的C++代码重写成C语言,这对我来说实际上只是将所有的 std::string 变量转换为 char*,并跟踪一些长度。

完成后,我有了.h和.c文件。我想将C代码中的一个函数作为Python中的单个函数使用。事实证明,Cython可以将您的C文件编译成扩展,并在一次链接中链接任何库,因此从我的setup.py开始,它最终看起来像这样:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules=[
  Extension("myext",
    ["myext.pyx", "../stuff.c"],
    libraries=["ssl", "crypto"]
  )
]

setup(
  name = "myext",
  cmdclass = {"build_ext": build_ext},
  ext_modules = ext_modules
)

如您所见,Extension的第二个参数仅列出需要编译的所有文件,据我所知,Cython会根据它们的文件扩展名来确定如何编译它们。libraries数组告诉Cython编译器需要链接什么(在这种情况下,我正在封装一些加密内容,我似乎无法直接通过现有的Python库进行模拟)。
要在.pyx文件中使我的C函数可用,您需要在.pxd中编写一个小包装器。我的myext.pxd如下所示:
cdef extern from "../stuff.h":
    char* myfunc(char* arg1, char* arg2, char* arg3)

在.pyx文件中,您可以使用cimport声明导入此函数,然后可以像任何其他Python函数一样使用它:
cimport myext

def my_python_func(arg1, arg2, arg3):
    result = myext.myfunc(arg1, arg2, arg3)
    return result

当您在Mac上构建此项目时,会得到一个 .so 文件,您可以将其导入Python并从 .pyx 运行函数。可能有更好、更正确的方法来实现所有这些工作,但这需要经验,并且这是我第一次尝试,我设法把它搞定了。如果我有做错的地方,非常希望得到指点。
更新:
在进一步使用Cython后,我发现将其与C++集成也非常简单,只要你知道你在做什么。将C++的 string 简单地声明为 from libcpp.string cimport string 可以使其在您的 pyx/pyd 中可用。声明C++类也同样容易:
cdef extern from "MyCPPClass.h":
    cdef cppclass MyCPPClass:
        int foo;
        string bar;

当然,你需要以Pythonic的格式重新声明你的类的.h定义,但这是为了获得对已经编写的C++函数的访问而付出的小代价。


请帮我一下!我真的很想使用Python 3.3与Cython和C++,你可能有一些示例文件吗?我已经安装了最新的Cython和Python。 - PascalVKooten
我没有Python 3的任何示例文件,但请确保正确设置编译器标志http://wiki.cython.org/FAQ#WhatPythonversionsdoesCythonsupport.3F。在SO上提出更具体的问题。如果您在评论中链接它,我可以看一下。 - Endophage
不确定这个例子是否特别有帮助,但我为一个C++库编写了这个Python 3.3包装器 - Will
1
FYI,现在支持std::string和char*。 - robertwb

3

Cython主要用于C开发,如果需要将C++与Python集成,我建议使用Boost.Python。他们出色的文档应该能帮助您快速入门。


1
除了Boost.Python,还有来自PySide项目的Shinboken和来自PyQT项目的SIP,我相信SWIG也可以包装C++对象。理论上,您可以使用Cython包装C++代码,但是您必须使用重载名称并手动执行许多操作-我个人不建议这样做。 - synthesizerpatel
2
@spatz,很抱歉我不能同意Boost.Python的出色文档,甚至是Boost整体的文档。我发现他们的文档在过去常常误导或完全缺失。几个月前我正在使用他们的网络/套接字库,那时候需要大量的试错。 - Endophage
@spatz 好的,我再看看。大约六个月前,我曾尝试在其他项目中使用Boost.Python,但是甚至无法编译出hello world示例。顺便说一下,我用的是Mac,有时文档不太匹配... - Endophage
我刚刚下载了boost并尝试构建bjam... -bash: ./bootstrap.sh: /bin/sh^M: bad interpreter: No such file or directory - Endophage
我在Mac上遇到了同样的问题,最终通过从homebrew安装boost-build包来解决了它(bjam)。 - elyase
显示剩余4条评论

2
以上的回答或多或少已经回答了提问者的问题。
但现在已经过了2020年中期,我想在这里贡献一些资源摘要,以便那些想通过可以“pip安装”(或在PyPI中)的Python软件包探索Python-C++绑定的人使用。
我认为对于从Python调用C++ / 封装第三方库,可以看一下cppyy。它是一个相对较新的项目,但它肯定看起来很有前途,并且支持现代编译器。请参见以下链接: https://cppyy.readthedocs.io/en/latest/ 此外,始终存在pybind11... https://github.com/pybind/pybind11 Cython也本地支持C ++(对于大多数C ++语言); 有关更多信息,请参见https://cython.readthedocs.io/en/latest/src/userguide/wrapping_CPlusPlus.html

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接