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

Error exporting symbol when building Python C Extension in Windows

我正在努力将 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" [Link].

它在 Windows 链接器 "EXPORT" 选项中指定 [Link] 但找不到符号。

帮忙?

编辑:C 代码不使用 Python C API 即“#include”。这是因为该项目的目标是采用现有的 C 库并通过 Python 扩展封装在 Python 包装器中。该软件包适用于 Unix/Linux.

经过大量的尝试,我能够理解这个问题。

解决方案:在 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 class will tell the linker to export a function供其他程序调用。可以在终端输出中看到:

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 目标文件中找到该符号,因为它从未在源代码中定义过!

当然,应该可以拒绝导出符号的选项吧?显然不是。回顾扩展 class 文档,有一个参数 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,在这种情况下,它会构建一个称为 .pyd 文件的特殊 DLL。然后要使用它,你必须通过 ctypes 模块加载它。

另一种可能的解决方案:

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)

我在这里设置了一个带有 ctypes 扩展名的最小工作 python 包: https://github.com/himbeles/ctypes-example 适用于 Windows、Mac、Linux 并且不需要在 C/C++

中包含 Python.h
  • 需要覆盖build_ext.get_export_symbols()approach of memeplex and
  • 它强制所有操作系统的库扩展名相同 (.so)。
  • 此外,c / c++ 源代码中的编译器指令可确保在 Windows 与 Unix 的情况下正确导出共享库符号。
  • 作为奖励,二进制轮由所有操作系统的 GitHub 操作自动编译 :-)

只需要写一个函数将不同OS下的路径转换成对应的格式即可。 (Windows 是 \,类 Unix 是 /)。