在C++程序中链接Cython模块

5

能否使用一些 cdef 函数构建一个 Cython 模块,并将生成的共享库链接到 C++ 程序中?

我尝试了一个概念验证:

cymod.pyx:

# distutils: language=c++

from libcpp.string cimport string

cdef public string simple_echo(string test_string):
    return test_string

cpp_test.cpp:

#define PyMODINIT_FUNC void
#include <iostream>
#include "cymod.h"

int main(int argc, char const *argv[])
{
    std::cout << simple_echo("test") << std::endl;
    return 0;
}

setup.py:

from setuptools import setup, Extension
from Cython.Build import cythonize

setup(
    name='cymod',
    ext_modules=cythonize(
        Extension(
            "cymod", ["cymod.pyx"],
        ),
    )
)

cython模块构建成功,但是当我尝试构建将使用cython函数的c++代码时,出现以下错误:

$ g++ -L. -l:cymod.so cpp_test.cpp -o cpp_test
/tmp/cc48Vc2z.o: In function `main':
cpp_test.cpp:(.text+0x51): undefined reference to `simple_echo'
collect2: error: ld returned 1 exit status

这很奇怪。生成的头文件中有它:

cymod.h:

  /* Generated by Cython 0.29.1 */

  #ifndef __PYX_HAVE__cymod
  #define __PYX_HAVE__cymod


  #ifndef __PYX_HAVE_API__cymod

  #ifndef __PYX_EXTERN_C
    #ifdef __cplusplus
      #define __PYX_EXTERN_C extern "C"
    #else
      #define __PYX_EXTERN_C extern
    #endif
  #endif

  #ifndef DL_IMPORT
    #define DL_IMPORT(_T) _T
  #endif

  __PYX_EXTERN_C std::string simple_echo(std::string);

  #endif /* !__PYX_HAVE_API__cymod */

  /* WARNING: the interface of the module init function changed in CPython 3.5. */
  /* It now returns a PyModuleDef instance instead of a PyModule instance. */

  #if PY_MAJOR_VERSION < 3
  PyMODINIT_FUNC initcymod(void);
  #else
  PyMODINIT_FUNC PyInit_cymod(void);
  #endif

  #endif /* !__PYX_HAVE__cymod */

我看到我的功能在cymod.so中:

nm cymod.so| grep simple_echo
0000000000001e50 T simple_echo

注意:我意识到要使它实际工作,我需要链接python库并初始化解释器等。 为了使这个例子更短,我忽略了这些步骤,但无论如何我得到了相同的错误。


谢谢分享。我对此很好奇。 - Austin Mullins
3
你可以把你的更新/解决方案转移到答案中。回答自己的问题完全没有问题。 - ead
这里有两个链接与Cython和C代码相关:https://cython.readthedocs.io/en/latest/src/userguide/external_C_code.html#using-cython-declarations-from-c 和 https://dev59.com/C6Pia4cB1Zd3GeqP5-xY。 - ead
谢谢你的好建议。如果时间允许,我会发布一个带有我的经验教训的答案。 - Stephen
1个回答

1
简短的答案是,我在编译命令中将-l参数放得太早了。处理库查找路径也很重要。最简单的方法是使用rpath。我将rpath设置为可执行文件所在的目录,即.
此外,有必要链接Python库并设置包含和库路径。这些可以通过使用python-config实用程序的输出在编译时确定。以下是最终解决问题的编译命令:
g++ cpp_test.cpp -o cpp_test -L. -l:cymod.so $(python-config --libs) $(python-config --includes) $(python-config --cflags) -Wl,-rpath,"\$ORIGIN"

我还更新了C++文件,加入了#include "Python.h",并添加了对Py_Initialize()Py_Finalize()initcymod()的调用:

#include <iostream>
#include "Python.h"
#include "cymod.h"

int main(int argc, char *argv[])
{
    Py_Initialize();
    initcymod();
    std::cout << simple_echo("test") << std::endl;
    Py_Finalize();
    return 0;
}

注意:调用initcymod()是必要的,但只适用于python2。在python3中,您应该在调用Py_Initialize()之前调用PyImport_AppendInittab("cymod", PyInit_cymod);cymod部分是模块名称,替换为您的模块名称。

感谢@ead提供的有关此主题的文档https://cython.readthedocs.io/en/latest/src/userguide/external_C_code.html#using-cython-declarations-from-c和他对相关问题的回答https://dev59.com/C6Pia4cB1Zd3GeqP5-xY#45424720

在阅读链接的文档时,我遇到了这个:

注意 在像Linux这样的某些操作系统上,也可以按照通常的方式首先构建Cython扩展,然后链接到生成的.so文件,就像动态库一样。请注意,这不是可移植的,因此应该避免使用。

事实证明,你不应该做我试图做的事情。

相反,我应该运行:

cython --cplus cymod.pyx

然后使用生成的文件编译。不需要链接cython共享库,这样做并不是一个好主意。

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