【问题标题】:Can Cython code be compiled to a dll so C++ application can call it?Cython 代码可以编译为 dll 以便 C++ 应用程序可以调用它吗?
【发布时间】:2013-08-29 15:36:55
【问题描述】:

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

void InitPlugin(FuncTable* funcTable);

然后程序会调用dll中的函数进行初始化,并将函数指针传递给dll。从那时起,dll 就可以与程序对话了。

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

【问题讨论】:

    标签: c++ python plugins dll cython


    【解决方案1】:

    在 dll 中使用 cython-module 与 using a cython-module in an embeded python interpreter 没有什么不同。

    第一步是将cdef-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);
    

    cyfun.ccyfun.h 可以用

    生成
    cython -3 cyfun.pyx
    

    这些文件将用于构建 dll。

    dll 需要一个函数来初始化 python 解释器,另一个函数来完成它,在使用 double_meprint_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);
    

    所以有必要的 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);
    }
    

    值得注意的细节:

    1. 我们定义了BUILDING_DLL,所以DLL_PUBLIC变成了__declspec(dllexport)
    2. 我们使用由 cython 从cyfun.pyx 生成的cyfun.h
    3. cyfun_init初始化python解释器并导入内置模块cyfun。有点复杂的代码是因为从 Cython-0.29 开始,PEP-489 是默认的。更多信息可以在this SO-post 中找到。如果 Python 解释器未初始化或模块 cyfun 未导入,则从 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>
    

    现在已经构建了dll,但是以下细节值得注意:

    1. &lt;other_coptions&gt;&lt;other_loptions&gt; 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) - 解释器应该是仅初始化一次并保持活动状态直到程序结束。

    因此,将初始化/清理代码放入 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;
    }
    

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

    【讨论】:

    • 感谢这个非常完整的答案! PS:“我们不需要传递python-dll,因为它会自动链接” vs.“我们对python-dll有依赖”,做你的意思是在分发 DLL 时仍然需要 python37.dll 吗?
    • 从鸟瞰的角度来看,您是否认为可以使用 Cython 作为 C/C++ 中的编码的替代品,受益于 Python/Cython 语法 + import json 等模块。使用这种技术构建 DLL,将 DLL 作为单个文件提供,不需要最终用户安装 Python? (示例用例:第三方程序的插件,作为 DLL 文件)
    • @Basj python37.dll 和整个 Python 安装都是必需的。
    • @Basj 仅供参考:stackoverflow.com/a/59389683/5769463。我认为甚至有一些项目具有真正独立的 Python,即使支持 numpy,所以可以为 dll 做类似的事情。但是,IMO 更简单、更明智的选择是使用您的 dll 发布整个 python 安装。
    【解决方案2】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-04
      • 2012-01-25
      • 1970-01-01
      相关资源
      最近更新 更多