能否将Cython代码编译成dll,以便C++应用可以调用它?

问题描述 投票:7回答:1

我有一个C++程序,它有一种插件结构:当程序启动时,它在插件文件夹中寻找带有某些导出函数签名的dll,例如。

void InitPlugin(FuncTable* funcTable);

然后程序会调用dll中的函数进行初始化,并向dll传递函数指针。从此,dll就可以和程序对话了。

我知道Cython让你在Python中调用C函数,但我不知道能否写一段Cython代码并编译成dll,这样我的C++程序就可以用它来初始化。如果能提供一个示例代码就更好了。

c++ python plugins dll cython
1个回答
4
投票

在dll中使用cython-module,这与在程序中使用的 在嵌入的python解释器中使用cython模块。.

第一步将是标记 cdef-函数,它应该在外部C代码中使用,并带有 public比如说。

#cyfun.pyx:

#doesn't need python interpreter
cdef public int double_me(int me):
    return 2*me;

#needs initialized python interpreter
cdef public void print_me(int me):
    print("I'm", me);

cyfun.ccyfun.h 可以产生

cython -3 cyfun.pyx

这些文件将用于构建dll。

dll需要一个函数来初始化python解释器,另一个函数用来最终确定它,在调用之前,只需要调用一次 double_meprint_me 可以使用(Ok。double_me 没有解释器也可以工作,但这是一个实现细节)。)

dll的头文件如下。

//cyfun_dll.h
#ifdef BUILDING_DLL
    #define DLL_PUBLIC __declspec(dllexport) 
#else
    #define DLL_PUBLIC __declspec(dllimport) 
#endif

//return 0 if everything ok
DLL_PUBLIC int cyfun_init();
DLL_PUBLIC void cyfun_finalize();

DLL_PUBLIC int cyfun_double_me(int me);
DLL_PUBLIC void cyfun_print_me(int me);

所以有必要的initfinalize-functions和符号是通过 DLL_PUBLIC (需要做的见此) SO-post),所以它可以在dll之外使用。

具体实现如下 cyfun_dll.c-文件。

//cyfun_dll.c
#define BUILDING_DLL
#include "cyfun_dll.h"

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "cyfun.h"

DLL_PUBLIC int cyfun_init(){
  int status=PyImport_AppendInittab("cyfun", PyInit_cyfun);
  if(status==-1){
    return -1;//error
  } 
  Py_Initialize();
  PyObject *module = PyImport_ImportModule("cyfun");

  if(module==NULL){
     Py_Finalize();
     return -1;//error
  }
  return 0;   
}


DLL_PUBLIC void cyfun_finalize(){
   Py_Finalize();
}

DLL_PUBLIC int cyfun_double_me(int me){
    return double_me(me);
}

DLL_PUBLIC void cyfun_print_me(int me){
    print_me(me);
}

值得注意的细节:

  1. 我们定义了 BUILDING_DLL 所以 DLL_PUBLIC 成为 __declspec(dllexport).
  2. 我们使用 cyfun.h 由cython生成 cyfun.pyx.
  3. cyfun_init 统一python解释器并导入内置模块 cyfun. 代码有点复杂是因为自从Cython-0.29之后,PEP-489是默认的。更多信息可以在 这个帖子.
    1. cyfun_double_me 只是包装 double_me 所以它在dll之外也是可见的。

现在我们可以构建dll了!

:: set up tool chain
call "<path_to_vcvarsall>\vcvarsall.bat" x64

:: build cyfun.c generated by cython
cl  /Tccyfun.c /Focyfun.obj /c <other_coptions> -I<path_to_python_include> 

:: build dll-wrapper
cl  /Tccyfun_dll.c /Focyfun_dll.obj /c <other_coptions> -I<path_to_python_include>

:: link both obj-files into a dll
link  cyfun.obj cyfun_dll.obj /OUT:cyfun.dll /IMPLIB:cyfun.lib /DLL <other_loptions> -L<path_to_python_dll>

dll现在已经构建完成,但以下细节值得注意。

  1. <other_coptions><other_loptions> can vary from installation to installation. An easy way is to see them is to runcythonize some_file.pyx`并检查日志。
  2. 我们不需要传递python-dll,因为它将成为 自动链接但我们需要设置正确的库路径。
  3. 我们对python-dll有依赖性,所以以后必须在某个地方找到它。

从这里开始要看你的任务了,我们用一个简单的main.dll来测试我们的dll。

//test.c
#include "cyfun_dll.h"

int main(){
   if(0!=cyfun_init()){
      return -1;
   }
   cyfun_print_me(cyfun_double_me(2));
   cyfun_finalize();
   return 0;
}

可以通过

...
:: build main-program
cl  /Tctest.c /Focytest.obj /c <other_coptions> -I<path_to_python_include>

:: link the exe
link test.obj cyfun.lib /OUT:test_prog.exe <other_loptions> -L<path_to_python_dll>

现在打电话 test_prog.exe 导致预期的输出 "我是4"。

根据你的安装情况,必须考虑以下事项。

  • test_prog.exe 取决于... pythonX.Y.dll 它应该在路径中的某个地方,这样就可以找到它(最简单的方法是把它复制到exe旁边)。
  • 嵌入的Python解释器需要安装,见 这个这个 SO-posts。

IIRC,先初始化,然后再最终化,然后再重新初始化Python解释器,这不是一个好主意 (这在某些情况下可能会有效,但不是所有情况,见例如 这个)--解释器应该只被初始化一次,并且在程序结束前保持活力。

所以,如果你的CC++程序已经有一个初始化的Python解释器,那么提供一个只导入模块的函数是有意义的。cyfun 并且不初始化。在这种情况下,我将定义 CYTHON_PEP489_MULTI_PHASE_INIT=0因为 PyImport_AppendInittab 须先 Py_Initialize,当 dll 被加载时可能已经太晚了。


0
投票

我想直接调用它是很困难的,因为Cython非常依赖于Python运行时。

你最好的办法是在你的应用程序中直接嵌入一个Python解释器,例如在 本回答然后从解释器中调用你的Cython代码。这就是我想做的。

© www.soinside.com 2019 - 2024. All rights reserved.