【问题标题】:link error [LNK2019] showed up when trying to compile Cython code with C++ DLL libraries in Windows尝试在 Windows 中使用 C++ DLL 库编译 Cython 代码时出现链接错误 [LNK2019]
【发布时间】:2019-10-29 11:53:04
【问题描述】:
  1. 我尝试从 Python 调用 dll 中的 C++ 函数。
  2. 我已经成功生成了一个 C++ DLL(*.dll、*.lib 和 *.h),如果我尝试从另一个 C++ 代码调用它的 api 函数,我已经测试了 DLL 库是否可以工作。 (它在 C++ 中工作)
  3. 但是我正在努力围绕它制作一个 python 包装器。

我已经为此花费了超过 48 小时,因此非常感谢任何形式的帮助/建议/提示/批评!

我在网上阅读了大部分有关相关内容的可用文档,尽管我并不完全理解所有这些内容。通过调整代码的某些部分并再次编译,问题在于链接器无法解析在 dll 头文件 (*.h) 中声明的外部函数。

知道我测试 *.pyx 和 *.h 之间的“连接”是否正常工作可能很有用,因为如果我在 *.pyx 中调用 api 函数时输入的参数较少,错误消息将显示数字的参数与标题中的不一致。所以问题实际上是 *.pyx 和 *.dll 之间的交互,它应该依赖于 *.lib。至少我是这样认为的。

由于我不确定 *pyx 的 *.h 文件是否应该使用“__declspec(dllimport)”或“__declspec(dllexport)”,所以我都尝试了。他们都没有解决这个问题。

换句话说,链接器闻起来好像无法找到 api 函数(符号)的定义在 *.dll 中的位置,因为即使我更改 setup.py 中的库参数(例如,来自库 = [ 'libcds'] to libraries = ['nonexist']),错误信息是一样的,都是“LNK2019: unresolved external symbol __imp_CalcUpfrontCharge_API referenced in function blabla...

所以我怀疑安装文件缺少某些部分,让编译知道它应该查找 *.lib 文件并在 dll 中找到定义。现在它似乎忽略了 *.lib,因为正如我所说,即使我在 extension() 中完全删除了 library 参数,我也会遇到同样的错误。

我也尝试从源代码(不是 dll)构建 pyd,但也失败了。

it is as simple as this  (updated CDS_API macro):
libcds.h
#ifdef CDS_EXPORTS
#define CDS_API __declspec(dllexport)
#else
#define CDS_API __declspec(dllimport)
#endif
#ifdef __cplusplus
extern "C"
{
#endif

    CDS_API double CalcUpfrontCharge_API(
    char *expiries[], double rates[], double couponRate, double 
parSpread, double recoveryRate, char *today1, char *valueDate1, char 
*effectDate1, char *maturity
    );

#ifdef __cplusplus
}
#endif


cdspricer.pyx
# distutils: language = c++
# distutils: libraries = libcds
# distutils: include_dirs = .
# distutils: library_dirs = .

cimport numpy as np
from libc.stdlib cimport malloc, free
from libcpp.vector cimport vector
from libcpp.string cimport string
from libc.string cimport strcpy, strlen

cdef extern from "libcds.h":
    double CalcUpfrontCharge_API(
        char *expiries[], double rates[], double couponRate, double 
parSpread, double recoveryRate, char *today1, char *valueDate1, char 
*effectDate1, char *maturity);


def CalcUpfrontCharge(vector[char *] expiries,
                  vector[double] rates,
                  double couponRate,
                  double parSpread,
                  double recoveryRate,
                  string today,
                  string valueDate,
                  string effectDate,
                  string maturity
                  ):
     some logic...

cdef double res = CalcUpfrontCharge_API(c_expiries, c_rates, couponRate, 
parSpread, recoveryRate, today1, valueDate1, effectDate1, maturity1)
return res

setup.py
import setuptools
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
import numpy as np

ext_modules = [
Extension('cdspricer',
          ['cdspricer.pyx'],
          # Note here that the C++ language was specified
          # The default language is C
          language="c++",  
          depends = ['N:\Trading\Python\ISDA\libcds.lib'],
          extra_link_args = [],
          include_dirs=["N:\Trading\Python\ISDA"],
          libraries=['libcds'],
          library_dirs=["N:\Trading\Python\ISDA"])
]

setup(
name = 'cdspricer',
cmdclass = {'build_ext': build_ext},
ext_modules = ext_modules,
extra_compile_args=["-O3", "-Wall"],
include_dirs=[np.get_include()] 
)

【问题讨论】:

  • 您是否链接到导入库?
  • __declspec(dllimport) double CalcUpfrontCharge_API( 应该是CDS_API double CalcUpfrontCharge_API(
  • 你需要在构建你的 dll 时定义 CDS_EXPORTS 否则它不会导出你的符号。并且使用时不要定义。
  • @drescherjm 非常感谢您的回复。你说的对。我怀疑问题是我没有“链接”到导入库,但是如何?我将库依赖项放在 extension() 中,并明确地说 cdef extern from "libcds.h": double CalcUpfrontCharge_API in the *.pyx。我如何“链接到导入库”?另外,我看到扩展中的库 keyworld arg 仅控制哪个 *.lib 提供 link.exe。
  • @drescherjm 是的先生.. 我认为 dll 构建正确(我用于构建 dll 的 *.h 文件定义了 CDS_EXPORTS 并因此遵循您的建议并导出了该函数。我还通过在另一个 C++ 代码中调用导出的函数,成功.. 但是你是对的,如果我在使用时没有定义它,那么值将是“_declspec(dllimport)”

标签: c++ visual-c++ dll compiler-errors cython


【解决方案1】:

好的,我回来回答我自己的问题。

经过 36 个小时的努力,我终于让它在我的 Windows7 系统上运行。 由于使用 C/C++ DLL 的 Cython 编译需要如此多的细节和技巧,其中任何一个都非常关键,以至于即使错过一个看似微不足道的步骤也可能导致编译/链接过程的整个失败,我将重点介绍所有容易出错的陷阱我遇到的问题和解决方案:

  1. 您的设置扩展必须遵循如下形状:
    ext_modules = [
        Extension('cdspricer',
              ['cdspricer.pyx'],
              # Note here that the C++ language was specified
              # The default language is C
              language="c++",
              extra_link_args = [],
              include_dirs=["N:\Trading\Python\ISDA"],
              libraries=['cdslib', 'msvcrt', 'msvcmrt'],
              library_dirs=["N:\Trading\Python\ISDA"])
    ]

    setup(
    name = 'cdspricer',
    cmdclass = {'build_ext': build_ext},
    ext_modules = ext_modules,
    extra_compile_args=["-O3", "-Wall"],
    include_dirs=[np.get_include()]  # This gets all the required Numpy core files
    )
  1. 重要的是,如果您总是遇到错误:LNK2001/LNK2019,请检查以确保在构建 DLL 时,不仅头文件在导出的函数声明的前面有 __declspec(dllexport),而且源文件中的函数定义也应该遵循__declspec(dllexport)!!原来我在源文件中缺少前缀,所以 dll 在其他 C++ 项目中工作,但在 Cython 中没有!
#ifdef CDSLIB_EXPORTS
#define CDSLIB_API __declspec(dllexport)
#else
#define CDSLIB_API __declspec(dllimport)
#endif

    extern "C"
    {
        CDSLIB_API double CalcUpfrontCharge_API(
            char *expiries[], double rates[], double couponRate, double parSpread, double 
    recoveryRate, char *today1, char *valueDate1, char *effectDate1, char *maturity
        );
        CDSLIB_API double Test(
            double a
        );
    }

在源代码中,你有:

    CDSLIB_API double Test(double a)
    {
        double b;
        b = a + 1;
        return b;
    }

Test 函数可以工作,而 CalcUpfrontCharge_API 不能。

  1. 记得用 extern "C" 包装纯 C 代码函数的声明
  2. 非常重要的是,如果你的 dll 是在 x86 平台上构建的,它永远不会与 64 位 python 一起工作!最好的解决方案是使用 Visual Studio(我使用的是 VC2015),您可以在其中将解决方案平台从 x86 切换到 x64。

【讨论】:

    【解决方案2】:

    所以我刚刚使用插件 SDK 向导遇到了这个问题,该向导会创建一个插件外壳。它不会仅使用 shell 构建并出现此错误。这将特定于这种类型的设置,但对我来说,解决方法是我收到了这个警告:

    warning C4335: Mac file format detected: please convert the source file to either DOS or UNIX format
    

    以正确格式保存文件后,未解决的外部符号错误消失了,我可以构建解决方案。

    虽然我知道这可能无法解决您的情况,但我希望这可以帮助寻找答案的人。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-02-23
      • 2015-10-17
      • 1970-01-01
      • 1970-01-01
      • 2018-07-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多