cython生成dll踩坑记(一)---基础篇

cython生成dll踩坑记(一)—基础篇

2023-10-14

最近研究了一下cython,花了3天时间才写出了一点成果来(太菜了~~~)。关于cython的教程还是比较少的,大部分还是得自己摸索。(指修完一个bug又出现两个bug)。不过也确实感觉收获了许多。

  • 坑点一,pyx并不能直接导出函数

pyx的本质是一个动态链接库,所以,我猜想能不能直接loadlibrary这个动态链接库。于是我写了一个简单的代码测试一下。

cdef public func1():
print("hello world")

然后python setup.py build_ext生成pyd文件,拿pe工具一查,这导出表压根就没有func1函数…..

所以还是得老老实实的,先生成c代码,再生成dll。

  • 坑点二,初始化写在了DLL_PROCESS_ATTACHDLL_PROCESS_DETACH

一开始,我是这样的

    
#define PY_MOUDLE_NAME "test"
#define PY_MOUDLE_INIT PyInit_test

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:

PyObject* pmodule = NULL;

PyImport_AppendInittab(PY_MOUDLE_NAME, PY_MOUDLE_INIT);

Py_Initialize();

pmodule = PyImport_ImportModule(PY_MOUDLE_NAME);

if (pmodule == NULL) {

return FALSE;
}

}

break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
Py_FinalizeEx()
break;
}
return TRUE;
}

结果发现在自己的电脑里总报错,后来,我选择手动初始化,手动释放,就没报错了

#define PY_MOUDLE_NAME "test"
#define PY_MOUDLE_INIT PyInit_test
#define EDLL extern "C" _declspec(dllexport)

EDLL int py_init() {

PyObject* pmodule = NULL;

PyImport_AppendInittab(PY_MOUDLE_NAME, PY_MOUDLE_INIT);

Py_Initialize();

pmodule = PyImport_ImportModule(PY_MOUDLE_NAME);

if (pmodule == NULL) {
return 0;
}
else
{
return 1;
}
}


EDLL int py_exit() {
return Py_FinalizeEx();
}


BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

  • 坑点三,Py_Init_...()函数不管用

本来开始时,我的代码是

    
Py_Initialize();
PyInit_test();

结果发现一直报指针访问不能为nullptr。
后来改成下面这样就好了。

PyImport_AppendInittab("test", PyInit_test);
Py_Initialize();
PyImport_ImportModule("test");

小提示: cython3.x版本用Py_Init_...()用这个过不了编译

  • 坑点四,__PYX_EXTERN_C不能加_declspec(dllexport)

看了cython生成的头文件,发现每个导出函数都有__PYX_EXTERN_C,于是添了个_declspec(dllexport)

#define __PYX_EXTERN_C extern "C" _declspec(dllexport)

结果发现无效
只能老老实实写函数去包装了

  • 坑点五,无返回值时,返回值位PyObject问题

在编写cython函数时,无返回值的函数最好加上void而不是不写。

# 建议不要这样写
cdef public func1():
print("hello world")
# 而应该这样写
cdef pubilc void func1():
print("hello world")
  • 坑点六,字符串问题

c语言的wchar_t能直接当作cython的str类型来用。
c语言的char *能直接当作cython的bytes类型来用。

但是cython的str类型不能在c++文件里直接使用,要用Py_UNICODE。(bytes还没测试过)

其中Py_UNICODEwchar_t的别名

typedef wchar_t Py_UNICODE

Py_UNICODEpython.h头文件和cython里均有定义

所以本人就直接在cython和c++文件统一用Py_UNICODE了(懒得思考类型转换问题)

举个例子

# test.pyx

cdef public void test1(Py_UNICODE *a):
print(a)

// dllmain.cpp

#define EDLL extern "C" _declspec(dllexport)

EDLL void py_test1(Py_UNICODE *a){
return test1(a);
}
...

生成dll后调用py_test1,传参wchar_t,就能在控制台打印出字符串了。

注意: char* 在cython中的类型是bytes,而且还需要考虑末尾填'\0'

大体上我编译时遇到的问题就这些了。
基础篇就这些内容了,下一次就谈谈dll生成后的打包方式与变量共享问题吧

  • 最后给个小提示,在开发时,把多用try把代码包起来,然后在except中print错误信息,能极大提高找bug效率