不一致的 dll 链接和 dllimport 静态数据成员的定义不允许

Posted

技术标签:

【中文标题】不一致的 dll 链接和 dllimport 静态数据成员的定义不允许【英文标题】:inconsistent dll linkage & definition of dllimport static data member not allowed 【发布时间】:2013-11-12 10:32:40 【问题描述】:

假设我有这两个文件:

Header.h

class DLL ExportClass
public:
  ExportClass();
  static int test;
;

Source.cpp

#ifdef EXPORT
    #define DLL __declspec(dllexport)
#else
    #define DLL __declspec(dllimport)
#endif

#include "Header.h"

int ExportClass::test = 0;
ExportClass::ExportClass()

而且我不会定义EXPORT(导入已导出的具有static 成员的类),为什么会收到这些警告:

1>source.cpp(11): warning C4273: 'test' : inconsistent dll linkage
1>          header.h(4) : see previous definition of 'public: static int ExportClass::test'
1>source.cpp(13): warning C4273: 'ExportClass::ExportClass' : inconsistent dll linkage
1>          header.h(3) : see previous definition of 'ctor'

还有这个错误:

1>source.cpp(11): error C2491: 'ExportClass::test' : definition of dllimport static data member not allowed

如果我定义 EXPORT 它可以工作。我有点理解这些警告,但我认为编译器可以忽略静态变量和 ctor,因为无论如何整个类都被声明为__declspec(dllimport)。我想为__declspec(dllexport)__declspec(dllimport) 使用相同的代码库——但编译器似乎试图定义这些在声明中标记为__declspec(dllexport) 的符号。解决这个问题的常见做法是什么?

【问题讨论】:

【参考方案1】:

您希望编译器忽略一个非常严重的事故。它在类声明中遇到了 __declspec(dllimport) 属性,该属性非常明确地表明类实现存在于将在运行时绑定的不同模块中。但后来它也遇到了定义,完全出乎意料,因为属性契约说它是在一个完全不同的项目中编译的。

生成 C4273 警告是为了提醒您在运行时实际上将执行什么函数非常不清楚。有两个,一个在忙编译,另一个在DLL中。 实际上会执行哪一个是一个疯狂的猜测。 C4273 是 1 级警告,属于“这几乎肯定是错误的”类别。并非完全不可能正常工作,因为有些期望函数至少具有相同的代码。然而,不会引起麻烦的可能性并不大,它只有在函数没有任何改变内部 DLL 状态的副作用的情况下才能工作。顺便说一句,很难诊断错误。

然后它遇到了导出的变量。同样的情况,有两个。这是编译器程序员放弃的地方,让代码随机使用其中一个不再是可以忽略的事情。那永远行不通,变量不能具有相同的值。所以 C2491 是一个硬错误。

不知道你是怎么掉进这个泡菜里的,很明显你要走的路会让你掉下陡峭的悬崖。

【讨论】:

【参考方案2】:

我可以重现您的问题的唯一方法是执行以下操作:

    创建一个 Win32 DLL 项目,命名为 Project1 按照您的描述添加源代码 编译 DLL 和 LIB 更改项目属性以从预处理器定义中删除 EXPORT 尝试再次编译(然后我看到您的错误/警告)

如果我不执行第 4 步和第 5 步,而是执行以下操作,则没有看到错误:

创建一个 Win32 控制台应用程序,命名为 Project2

添加源代码如下:

#include "Project1.h"

#pragma comment(lib, "Project1.lib")

int _tmain(int argc, _TCHAR* argv[])

    ExportClass pClass;
    return 0;

我怀疑您看到这些错误是因为您正在从同一个 DLL 项目中执行所有操作,并且它正在覆盖它之前创建的 LIB,然后尝试导入它。

如果我猜对了你的所作所为,你能否尝试使用另一个项目中的 DLL/LIB 看看会发生什么?

【讨论】:

【参考方案3】:

虽然是老帖子,但很可能会被其他人阅读。因此,如果你想让这段代码可以交叉编译,我通常会定义一个头文件“export.h”,比如:

export.h

#pragram once

#if ! defined(DLL_API)
#   if defined(_WIN32)  // for windows builds
#       if defined(myDLL_EXPORTS)
#           define DLL_API __declspec(dllexport)
#       else
#           define DLL_API __declspec(dllimport)
#       endif
#   else               // for linux builds
#       define DLL_API
#   endif
#endif

并将其包含在您要从 dll 中导出的所有类 (.h) 中。您还必须将变量 myDLL_EXPORTS 定义为 dll 项目的编译器参数。

它的工作方式很简单,当你在编译你的动态库(dll/so)时,因为定义了变量myDLL_EXPORTS,编译器会将DLL_API替换为__declspec(dllexport),这样你的类就可以被您的 dll 的用户。相反,当您包含要在其中使用类的头文件时,因为变量 myDLL_EXPORTS 未在使用者项目中定义(仅在 DLL 项目中定义),编译器会将 myDLL_EXPORT 替换为 __declspec(dllimport) ,因此它知道您的类符号是在其他地方定义的(在这种情况下,在您的 dll/so 中定义)。

最后,由于 __declspec(...) 仅适用于 Windows,因此对于 linux,我们将 DLL_API 替换为空。

【讨论】:

以上是关于不一致的 dll 链接和 dllimport 静态数据成员的定义不允许的主要内容,如果未能解决你的问题,请参考以下文章

Xerces链接错误原因之/Zc:wchar_t-设置不一致

GetProcAddress与__declspec(dllimport)

在 C# DllImport 中使用 32 位或 64 位 dll

c语言 不允许 dllimport 函数 的定义 怎么办

尽管 dllimport 在两个 DLL 中导出的数据符号

DLL-导出模板基类的静态成员