在Windows构建Python C扩展时导出符号出错

3
我是一名有用的助手,可以为您进行文本翻译。以下是需要翻译的内容:

我正在将一个Python模块移植到Windows上。我有一个玩具示例如下。

文件夹结构如下:

foo/
  libfoo/
    foo.c
  setup.py

setup.py

from setuptools import setup, Extension

sources = ['libfoo/foo.c']

foo = Extension('libfoo',
                 sources = sources,
                 define_macros = None,
                 include_dirs = ['./libfoo'],
                 libraries = None,
                 library_dirs = None,
                 )

setup(name          = 'foo',
      ext_modules      = [foo],
      install_requires = ['setuptools'],
)

libfoo/foo.c(为了完整性)

#include <stdio.h>

void foo() {
  printf("Hello World!");
}

当我尝试安装软件包时,遇到了一个错误。
C:\Users\user\foo>python setup.py install
running install
running bdist_egg
running egg_info
creating foo.egg-info
writing requirements to foo.egg-info\requires.txt
writing foo.egg-info\PKG-INFO
writing top-level names to foo.egg-info\top_level.txt
writing dependency_links to foo.egg-info\dependency_links.txt
writing manifest file 'foo.egg-info\SOURCES.txt'
reading manifest file 'foo.egg-info\SOURCES.txt'
writing manifest file 'foo.egg-info\SOURCES.txt'
installing library code to build\bdist.win32\egg
running install_lib
running build_ext
building 'libfoo' extension
creating build
creating build\temp.win32-2.7
creating build\temp.win32-2.7\Release
creating build\temp.win32-2.7\Release\libfoo
c:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\BIN\cl.exe /c /nologo /Ox
/MD /W3 /GS- /DNDEBUG -I./libfoo -IC:\Python27\include -IC:\Python27\PC /Tclibfo
o/foo.c /Fobuild\temp.win32-2.7\Release\libfoo/foo.obj
foo.c
creating build\lib.win32-2.7
c:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\BIN\link.exe /DLL /nologo
/INCREMENTAL:NO /LIBPATH:C:\Python27\libs /LIBPATH:C:\Python27\PCbuild /EXPORT:i
nitlibfoo build\temp.win32-2.7\Release\libfoo/foo.obj /OUT:build\lib.win32-2.7\l
ibfoo.pyd /IMPLIB:build\temp.win32-2.7\Release\libfoo\libfoo.lib /MANIFESTFILE:b
uild\temp.win32-2.7\Release\libfoo\libfoo.pyd.manifest
LINK : error LNK2001: unresolved external symbol initlibfoo
build\temp.win32-2.7\Release\libfoo\libfoo.lib : fatal error LNK1120: 1 unresolv
ed externals
error: command 'c:\\Program Files (x86)\\Microsoft Visual Studio 9.0\\VC\\BIN\\l
ink.exe' failed with exit status 1120

似乎distutils包(在本例中为setuputils)将始终从共享扩展中导出一个符号,即“init + extension_name” [链接]
这在Windows链接器的“EXPORT”选项[链接]中指定,但它找不到该符号。
需要帮助吗?
编辑:C代码不使用Python C API,即“#include ”。这是因为项目的目标是通过Python扩展将现有的C库封装在Python包装器中。该软件包适用于Unix / Linux。

你是不是真的在尝试构建一个“libfoo”扩展模块,需要定义一个initlibfoo函数,以便Python在导入扩展时调用?还是只是使用setuptools编译共享库?如果是后者,对于Windows DLL,你需要做的第一件事就是定义导出符号,可以使用__declspec(dllexport).DEF文件 - Eryk Sun
@eryksun 我相信是后者。意图是在安装包时构建“libfoo”扩展。当解释器导入包“foo”,即“import foo”时,类对象将加载DLL(或.pyd?)。如果这不合理,请告诉我。 - nbui
一个 .pyd 文件就是一个 DLL 文件。看起来这正是你想要的,即链接到 libfoo.dll 并封装其中函数的 foo.pyd。首先要让 libfoo.dll 单独工作,在命令行上手动构建它(不需要使用 Python 或 setuptools)。然后按照“创建扩展模块”的文档进行操作。文档链接:https://docs.python.org/2/extending/index.html。 - Eryk Sun
4个回答

11

经过一番尝试,我终于理解了问题所在。

解决方案:在Windows中,你必须为你的扩展定义一个初始化函数,因为setuptools会构建一个.pyd文件,而不是DLL。

要解决这个问题,在Python 2中,你需要在源文件中定义initlibfoo()方法。

#include <Python.h>

PyMODINIT_FUNC initlibfoo(void) {
    // do stuff...
}

为了在Python 3中解决这个问题,您需要在源代码中定义PyInit_libfoo()方法。
#include <Python.h>

PyMODINIT_FUNC PyInit_libfoo(void) {
    // do stuff...
}

所以现在,foo.c看起来像:

#include <stdio.h>
#include <Python.h>

void foo() {
  printf("Hello World!");
}

PyMODINIT_FUNC initlibfoo(void) // Python 2.7
//PyMODINIT_FUNC PyInit_libfoo(void) // Python 3.5
{
    // do stuff...
}

解释

在为 Windows 编译 Python 扩展时,Extension 类会告诉 链接器导出一个函数,以便其他程序调用。这可以在终端输出中看到:

c:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\BIN\link.exe /DLL /nologo
/INCREMENTAL:NO /LIBPATH:C:\Python27\libs /LIBPATH:C:\Python27\PCbuild /EXPORT:i
nitlibfoo build\temp.win32-2.7\Release\libfoo/foo.obj /OUT:build\lib.win32-2.7\l
ibfoo.pyd /IMPLIB:build\temp.win32-2.7\Release\libfoo\libfoo.lib /MANIFESTFILE:b
uild\temp.win32-2.7\Release\libfoo\libfoo.pyd.manifest

(重点在于/EXPORT:initlibfoo)

这是C/C++扩展所必需的函数,以便它们可以作为模块导入。换句话说,当你import libfoo时,Python会在模块初始化过程中调用.pyd文件上的initlibfoo函数。这里遇到的错误是由于链接器被告知导出initlibfoo函数,但是在C对象文件中找不到该符号,因为它在源代码中从未定义!

当然,应该能够拒绝导出符号选项,对吧?显然不行。回到Extension类文档,有一个export_symbols参数。我尝试将其传递给None,但链接器选项仍在使用。似乎没有办法规避这个链接器选项。

不建议这样做:如果由于某种原因,需要使用Python.h让您感到困扰,那么有一种方法可以解决这个问题。您仍然需要定义上面的“init”方法,但可以执行以下操作:

void initlibfoo() {} //Python 2.7
void PyInit_libfoo() {} //Python 3.5

但现在你不能通过 import libfoo 使用该库。你可以使用 ctypes 模块,并使用 ctypes.PyDLL('/path/to/pyd') 加载它。本质上,你使用 Python 为自己构建了一个 DLL,这种情况下,它确实构建了一个特殊的 DLL,即 .pyd 文件。然后,你必须通过 ctypes 模块加载它。


2
另一个可能的解决方案:

另外一种可能的解决方案:

from distutils.command.build_ext import build_ext as _du_build_ext
from unittest.mock import Mock
mockobj = _du_build_ext
mockobj.get_export_symbols = Mock(return_value=None)

1

只需要编写一个函数,将路径转换为不同操作系统下的相应格式。(Windows 是 \\,类Unix系统是 /)。


0

我在这里设置了一个最小的Python包,带有ctypes扩展: https://github.com/himbeles/ctypes-example 它可以在Windows、Mac和Linux上运行,并且您不需要在C/C++中包含Python.h

  • 它采用了memeplexTomasz Hemperek的方法,覆盖了build_ext.get_export_symbols()
  • 它强制所有操作系统使用相同的库扩展名(.so)。
  • 此外,在c / c++源代码中的编译器指令确保在Windows vs. Unix的情况下正确导出共享库符号。
  • 作为奖励,二进制轮毂会自动由GitHub Action编译适用于所有操作系统 :-)

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