我有一个 C++ 程序,它有一种插件结构:当程序启动时,它会在插件文件夹中寻找具有某些导出函数签名的 dll,例如:
void InitPlugin(FuncTable* funcTable);
然后程序会调用dll中的函数进行初始化,并将函数指针传递给dll。从那时起,dll 就可以与程序对话了。
我知道 Cython 允许您在 Python 中调用 C 函数,但我不确定我是否可以编写 Cython 代码并将其编译为 dll,以便我的 C++ 程序可以使用它进行初始化。示例代码会很棒。
最佳答案
在 dll 中使用 cython-module 与 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.c
和 cyfun.h
可以生成
cython -3 cyfun.pyx
这些文件将用于构建 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);
所以有必要的 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);
}
值得注意的细节:
- 我们定义
BUILDING_DLL
所以DLL_PUBLIC
变成__declspec(dllexport)
. - 我们使用
cyfun.h
由 cython 从cyfun.pyx
生成. -
cyfun_init
初始化 python 解释器并导入内置模块cyfun
.有点复杂的代码是因为从 Cython-0.29 开始,PEP-489是默认的。更多信息可以在 this SO-post 中找到.如果 Python 解释器未初始化或模块cyfun
未导入,很有可能从cyfun.h
调用功能将以段错误结束。 -
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 现已构建,但以下细节值得注意:
-
<other_coptions>
和<other_loptions> can vary from installation to installation. An easy way is to see them is to run
cythonize some_file.pyx` 并检查日志。 - 我们不需要传递 python-dll,因为它将是 linked automatically , 但我们需要设置正确的库路径。
- 我们对 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 帖子。
IIRC,初始化,然后完成然后再次初始化 Python 解释器不是一个好主意(这可能适用于某些场景,但不是全部,例如参见 this)- 解释器应该是仅初始化一次并保持事件状态直到程序结束。
因此,将初始化/清理代码放入 DllMain
可能是有意义的(并将 cyfun_init()
和 cyfun_finalize()
设为私有(private)),例如
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 时可能已经太晚了。
关于c++ - Cython 代码可以编译成 dll 以便 C++ 应用程序可以调用它吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18515275/