使用Cython生成的头文件

5
根据文档,可以使用由Cython生成的C头文件。我已经没有问题地按照Hello World示例进行了操作,现在我想尝试一些不同的东西。我想要使用公共声明来使用自定义方法。我的代码结构如下所示:
  • hello.pyx
  • setup.py
  • main.c
hello.pyx
cdef public void say_hello():
    print("Hello World")

setup.py

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules = [
    Extension("hello", ["hello.pyx", "main.c"]),
]

setup(
  name='Hello app',
  cmdclass={'build_ext': build_ext},
  ext_modules=ext_modules
)

main.c

#include "hello.h"

int main(void){
    say_hello();
}

main.c 作为一个测试文件,用来验证 say_hello() 方法是否按照预期工作。 构建安装文件 python3 setup.py build_ext 会产生以下输出。

    running build_ext
    skipping 'hello.c' Cython extension (up-to-date)
    building 'hello' extension
    x86_64-linux-gnu-gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -I/usr/include/python3.5m -c hello.c -o build/temp.linux-x86_64-3.5/hello.o
    x86_64-linux-gnu-gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -I/usr/include/python3.5m -c main.c -o build/temp.linux-x86_64-3.5/main.o
    In file included from main.c:1:0:
    hello.h:26:1: error: unknown type name ‘PyMODINIT_FUNC’
     PyMODINIT_FUNC inithello(void);
     ^
    error:

 command 'x86_64-linux-gnu-gcc' failed with exit status 1

hello.h文件包含以下内容:

    /* Generated by Cython 0.25.2 */

#ifndef __PYX_HAVE__hello
#define __PYX_HAVE__hello


#ifndef __PYX_HAVE_API__hello

#ifndef __PYX_EXTERN_C
  #ifdef __cplusplus
    #define __PYX_EXTERN_C extern "C"
  #else
    #define __PYX_EXTERN_C extern
  #endif
#endif

#ifndef DL_IMPORT
  #define DL_IMPORT(_T) _T
#endif

__PYX_EXTERN_C DL_IMPORT(void) say_hello(void);

#endif /* !__PYX_HAVE_API__hello */

#if PY_MAJOR_VERSION < 3
PyMODINIT_FUNC inithello(void);  // <-- Line 26
#else
PyMODINIT_FUNC PyInit_hello(void);
#endif

#endif /* !__PYX_HAVE__hello */

据我的理解,似乎gcc不能正确获取Python的版本(我正在使用Python 3.5)。有什么办法可以设置吗?另外,如果确实是这种情况,为什么在运行“python3 setup.py build_ext”命令时没有考虑到这个链接呢?
我对C没有太多经验,可能会漏掉一些东西。
2个回答

10
我认为问题在于您的误解,即 distutils 会为您构建可执行文件。实际上并非如此。
我们的目标是从 C 程序中使用一些 Python 功能。 让我们自己完成必要的步骤:
  1. 使用 Cython 从给定的 pyx 文件生成 hello.hhello.c
  2. 使用创建的 hello.h 在 C 程序(main.c)中导入 Python 功能。
  3. 使用编译器编译两个文件 hello.cmain.c
  4. 使用链接器将在最后一步创建的对象文件链接到可执行文件中。
第一步很容易:
#hello.pyx:
cdef public void say_hello():
    print("Hello World")


>>>python -m cython hello.pyx

现在我们的工作目录中有hello.chello.h文件。您的main.c文件是错误的,应该像下面这样:
//main.c
#include <Python.h> //needed
#include "hello.h"

int main(void){
    Py_Initialize();  //Needed!
    inithello();      //Needed! called PyInit_hello() for Python3
    say_hello();
    Py_Finalize();    //Needed!
} 

重要提示:如果您使用的是Python>=3.5和Cython>=0.29,则必须考虑多阶段模块初始化,如此SO-post中所述,否则结果程序将崩溃。

我不确定为什么Cython的结果不包括Python.h(这将使其独立),但就是这样——您需要在hello.h之前包含它。此外,在使用来自hello.h的功能之前和之后应调用Py_Initialize()Py_Finalize()。同时,您需要使用inithello()初始化模块hello,否则可执行文件在启动时会发生分段错误。

现在我们需要找到编译标志。可以使用实用程序/usr/bin/python-config(我使用python2.7,您需要为python3做同样的操作)和选项--cflags来完成。

>>> /usr/bin/python-config --cflags
-I/usr/include/python2.7 -I/usr/include/x86_64-linux-gnu/python2.7
-fno-strict-aliasing -Wdate-time -D_FORTIFY_SOURCE=2 -g -fstack-protector-strong
-Wformat -Werror=format-security  -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes

那么让我们来构建它吧:
>>>gcc -c hello.c -o hello.o <our cflags>
>>>gcc -c main.c -o main.o <our cflags>

现在我们的工作目录中有两个目标文件hello.omain.o。我们需要将它们链接起来,为了找到正确的标志,我们再次使用python-config实用程序,但这次要使用--ldflags选项:

>>> /usr/bin/python-config --ldflags
--L/usr/lib/python2.7/config-x86_64-linux-gnu -L/usr/lib -lpython2.7 
-lpthread -ldl  -lutil -lm  -Xlinker -export-dynamic -Wl,-O1 -Wl,-Bsymbolic-functions

那意味着:
>>> gcc main.o hello.o -o prog <our ldflags>

现在我们终于有了可执行文件!


实际上,这并不完全符合要求,但还有另一种可能性可以从cython代码生成可执行文件,即使用选项--embed。打开此开关后,不仅模块被cython化,还会创建一个主函数。为此,您的hello.pyx可能如下所示:

#hello.pyx:
cdef public void say_hello():
    print("Hello World")
##main:
say_hello()

可以使用__name__=="__main__"技巧,但不是必需的。

现在运行后:

>>>python -m cython hello.pyx --embed

在生成的hello.c文件中创建了一个main函数,负责设置/初始化Python环境。因此,我们只需要构建和链接它:

>>> gcc hello.c -o prog <our cflags> <our ldflags>

我们完成了 - 不需要知道如何正确初始化整个Python环境!


感谢您的回答,@ead。找到标志的提示非常有用。不幸的是,在编译“main.c”期间,我收到了关于方法“inithello()”的隐式声明警告,稍后在尝试链接时会抛出错误“undefined reference to inithello”。使用Python2.7可以正常工作! - BitWhyz
通过查看hello.h代码,我认为你应该在Python3中使用PyInit_hello()而不是inithello() - ead
成功了!谢谢。我不明白为什么文档没有提到这一点。我希望能有更好的资源来深入挖掘。 - BitWhyz
1
@BitWhyz 写好文档比写好代码还要难,这是相当具有挑战性的。我认为 Cython 的开发人员正在做出不错的工作,但是每个版本之间都有很多变化,跟进起来并不简单。 - ead

0
你可以使用 api 关键字。
cdef api void hello():
    print("hello")
    return;

这将在编译时自动生成一个名为"hello.h"的文件。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接