不一致的DLL链接和DLL导入静态数据成员的定义不允许



假设我有两个文件:

标题.h

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

资料来源.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)使用相同的代码库 - 但编译器 stll 似乎试图定义这些在其声明中标记为__declspec(dllexport)的符号。解决这个问题的常见做法是什么?

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

生成 C4273 警告是为了提醒您,目前还不清楚在运行时实际要执行什么函数。 有两个,一个忙于编译,另一个在 DLL 中。 哪一个会真正执行是一个疯狂的猜测。 C4273 是 1 级警告,符合"这几乎肯定是错误的"类别。 工作正常并非完全不可能,因为人们期望这些函数至少具有相同的代码。 但是,不会造成麻烦的几率并不大,只有当函数没有任何更改内部DLL状态的副作用时,它才能起作用。 顺便说一句,当它这样做时很难诊断错误。

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

不知道你是怎么进入这个泡菜的,显然你想走的路会让你从陡峭的悬崖上掉下来。

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

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

如果我执行以下操作,而不是步骤 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看看会发生什么吗?

虽然这是一个旧线程,但它可能会被其他人阅读。因此,如果你想使这段代码可交叉编译,我通常会定义一个标题"export.h",如下所示:

出口.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,编译器会用__declspec(dllexport)替换DLL_API,这样你的类就可以被你的dll的用户使用。相反,当您包含要在其中使用类的头文件时,由于变量myDLL_EXPORTS未在使用者项目中定义(仅在 DLL 项目中定义),编译器会将myDLL_EXPORT替换为 __declspec(dllimport),因此它知道您的类符号是在其他地方定义的(在本例中, 在您的 DLL/SO 中定义)。

最后,由于__declspec(...)是Windows专用的东西,对于linux,我们用任何东西替换DLL_API。

最新更新