是否可以将Cython代码编译为dll,以便C ++应用程序可以调用它?

时间:2013-08-29 15:36:55

标签: c++ python plugins dll cython

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

void InitPlugin(FuncTable* funcTable);

然后程序将调用dll中的函数来初始化并将函数指针传递给dll。从那时起,dll可以与程序通信。

我知道Cython允许你在Python中调用C函数,但是我不确定我是否可以编写一个Cython代码并将其编译为dll,这样我的C ++程序就可以用它进行初始化。示例代码很棒。

2 个答案:

答案 0 :(得分:10)

在dll中使用cython模块与using a cython-module in an embeded python interpreter一样。

第一步是用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之前调用一次(确定,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);

因此,有必要的init / finalize函数,并且符号通过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. 我们使用cython从cyfun.h生成的cyfun.pyx
  3. cyfun_init初始化python解释器并导入内置模块cyfun。有点复杂的代码是因为默认为Cython-0.29 PEP-489。可以在this SO-post中找到更多信息。
    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 run cythonize some_file.pyx`并检查日志。
  2. 我们不需要传递python-dll,因为它将是linked automatically,但是我们需要设置正确的库路径。
  3. 我们对python-dll有依赖性,因此以后必须在可以找到它的地方。

您是否要从这里出发取决于您的任务,我们用一个简单的main测试我们的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解释器需要安装,请参见this和/或this SO-posts。

IIRC,初始化,然后完成并再次初始化Python解释器不是一个好主意(这可能在某些情况下适用,但不是全部,请参见this)-解释器应该只初始化一次并保持活动直到程序结束。

因此,如果您的C / C ++程序已经具有初始化的Python解释器,则提供一个仅导入模块cyfun且不初始化的函数将很有意义。在这种情况下,我将定义CYTHON_PEP489_MULTI_PHASE_INIT=0,因为必须在PyImport_AppendInittab之前调用Py_Initialize,而在加载dll时可能已经太晚了。

答案 1 :(得分:0)

我认为直接调用它很困难,因为Cython在Python运行时非常依赖。

您最好的办法是直接在应用内嵌入Python解释器,例如:如this answer中所述,并从解释器调用您的Cython代码。这就是我要做的。