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

AZ.*_*AZ. 6 c++ python dll plugins cython

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

void InitPlugin(FuncTable* funcTable);
Run Code Online (Sandbox Code Playgroud)

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

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

ead*_*ead 10

在 dll 中使用 cython-module 与在嵌入式 python 解释器中使用 cython-module 没有什么不同。

第一步是标记cdef应该从外部 C 代码使用的 -function 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);
Run Code Online (Sandbox Code Playgroud)

cyfun.c并且cyfun.h可以生成

cython -3 cyfun.pyx
Run Code Online (Sandbox Code Playgroud)

这些文件将用于构建 dll。

dll 需要一个函数来初始化 python 解释器,另一个函数来完成它,之前应该只调用一次double_me并且print_me可以使用(好吧,double_me没有解释器也可以工作,但这是一个实现细节)。注意:初始化/清理也可以放入DllMain- 请参阅下面的此类版本。

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);
Run Code Online (Sandbox Code Playgroud)

因此有必要的 init/finalize-functions 和符号通过DLL_PUBLIC(需要完成,请参阅此SO-post)导出,因此它可以在 dll 之外使用。

实现如下cyfun_dll.c-file:

//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);
}
Run Code Online (Sandbox Code Playgroud)

值得注意的细节:

  1. 我们这样定义BUILDING_DLLDLL_PUBLIC变成了__declspec(dllexport)
  2. 我们使用cyfun.h由 cython 生成的cyfun.pyx.
  3. cyfun_init初始化 python 解释器并导入内置模块cyfun。有点复杂的代码是因为从 Cython-0.29 开始, PEP-489是默认的。更多信息可以在这个 SO-post 中找到。如果 Python 解释器未初始化或模块cyfun未导入,则很有可能调用 from 的功能cyfun.h将以分段错误结束。
  4. 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>
Run Code Online (Sandbox Code Playgroud)

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,因为它会自动链接,但我们需要设置正确的库路径。
  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;
}
Run Code Online (Sandbox Code Playgroud)

可以通过构建

...
:: 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>
Run Code Online (Sandbox Code Playgroud)

现在调用test_prog.exe会导致预期的输出“我是 4”。

根据您的安装,必须考虑以下事项:

  • test_prog.exe取决于pythonX.Y.dll哪个应该在路径中的某个地方,以便可以找到它(最简单的方法是将其复制到 exe 旁边)
  • 嵌入式 python 解释器需要安装,请参阅和/或SO-posts。

IIRC,初始化,然后完成,然后再次初始化 Python 解释器并不是一个好主意(这可能适用于某些场景,但不是全部,请参见例如这个) - 解释器应该只初始化一次并保持不变直到程序结束。

因此,将初始化/清理代码放入DllMain(并制作cyfun_init()cyfun_finalize()私有)中可能是有意义的,例如

BOOL WINAPI DllMain(
    HINSTANCE hinstDLL,  // handle to DLL module
    DWORD fdwReason,     // reason for calling function
    LPVOID lpReserved )  // reserved
{
    // Perform actions based on the reason for calling.
    switch( fdwReason ) 
    { 
        case DLL_PROCESS_ATTACH:
            return cyfun_init()==0;

        case DLL_PROCESS_DETACH:
            cyfun_finalize();
            break;
        case DLL_THREAD_ATTACH:
         // Do thread-specific initialization.
            break;

        case DLL_THREAD_DETACH:
         // Do thread-specific cleanup.
            break;   
    }
    return TRUE;
}
Run Code Online (Sandbox Code Playgroud)

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


F.X*_*.X. 1

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

您最好的选择是将 Python 解释器直接嵌入到您的应用程序中,例如,如本答案中所述,并从解释器调用您的 Cython 代码。这就是我会做的。


归档时间:

查看次数:

1195 次

最近记录:

12 年 前