在DLL中导出静态数据

36
我有一个包含静态成员的类的DLL。我使用__declspec(dllexport)来使用这个类的方法。但是当我将其链接到另一个项目并尝试编译它时,我会得到静态数据的"未解析外部符号"错误。 例如,在DLL中,Test.h。
class __declspec(dllexport) Test{
protected:
    static int d;
public:
    static void m(){int x = a;}
}

在 DLL 中,Test.cpp 文件中:

#include "Test.h"

int Test::d;
在使用Test的应用程序中,我调用了m()方法。
我还尝试为每个方法单独使用__declspec(dllexport),但对于静态成员仍然会得到相同的链接错误。
如果我使用dumpbin检查DLL(.lib),我可以看到符号已被导出。
例如,应用程序在链接时会给出以下错误:
1>Main.obj : error LNK2001: unresolved external symbol "protected: static int CalcEngine::i_MatrixRow" (?i_MatrixRow@CalcEngine@@1HA)

但是.lib文件的dumpbin包含:

Version      : 0
  Machine      : 14C (x86)
  TimeDateStamp: 4BA3611A Fri Mar 19 17:03:46 2010
  SizeOfData   : 0000002C
  DLL name     : CalcEngine.dll
  Symbol name  : ?i_MatrixRow@CalcEngine@@1HA (protected: static int CalcEngine::i_MatrixRow)
  Type         : data
  Name type    : name
  Hint         : 31
  Name         : ?i_MatrixRow@CalcEngine@@1HA

我无法解决这个问题。我做错了什么?如何克服这些错误?

顺便说一下,这段代码最初是为Linux开发的,.so/binary组合没有任何问题

编辑:在给定的情况下,静态变量并不是由应用直接引用的,但是由于该方法被内联到头文件中,因此出现了链接错误。通过将这些方法移动到.cpp文件中,我成功地解决了链接错误。


我的第一反应是归咎于名称混淆,但是从检查dumpbin的结果来看,似乎混淆后的名称是匹配的。为了确保,你是否使用相同版本的编译器进行编译? - Ramon Zarazua B.
是的。我不确定编译器版本,但我使用Visual C++ 2008 Express来编译DLL和应用程序。 - Gayan
6个回答

21

此页面中,建议静态变量是局部于dll而非被导出的。

以下为讨论总结:

调用应用程序中的代码不直接访问静态成员,只能通过dll中的类成员函数进行访问。然而,有几个内联函数访问静态成员。这些函数将被内联展开到调用应用程序的代码中,使调用应用程序可以直接访问静态成员。这将违反上述发现,即静态变量是局部于dll中的,不能从调用应用程序引用它们。


1
是的,我遇到了这个问题(我已经尝试解决了半天)。但是给出的解决方案并不适用于我的情况,因为我在应用程序中没有使用dll中定义的静态成员。它们被隐藏起来,只被本地方法使用。 - Gayan
1
有没有任何内联方法访问静态成员?如果是这种情况,内联展开的代码将被放置在您的应用程序中,直接访问静态成员。 - Anders Abel
2
尝试将所有使用静态成员的方法体移动到编译为dll且无法在应用程序中内联的.cpp文件中,这可能会解决问题。 - Anders Abel
1
经过大量的工作,我终于成功编译可执行文件。我不得不将许多方法从.h文件移动到.cpp文件中。这并不能解释为什么导出的符号被报告为未解决。 - Gayan
1
我稍微有点犹豫把这个设为答案,因为在链接的答案中,显示静态成员的符号“可以”被导出。应用程序和dll会创建两个实例,但该符号并没有被标记为“未解决”,这就是我的情况。请更正这篇文章,我会接受它。 - Gayan
显示剩余3条评论

16

我猜测使用DLL的类应该在头文件中看到dllimport, 而不是dllexport。如果我正确的话,通常可以通过定义一个预处理器宏来实现:

#ifdef EXPORTING
#define DECLSPEC __declspec(dllexport)
#else
#define DECLSPEC __declspec(dllimport)
#endif

然后在类声明中使用它:

class DECLSPEC Test{
protected:
    static int d;
public:
    static void m(){}
}

这样,在Test.cpp(或者在你的DLL项目中有意义的任何地方),你可以指定它是要使用dllexport导出:

#define EXPORTING
#include "Test.h"

int Test::d;

另一个项目没有定义EXPORTING,将会看到dllimport

这有意义吗?


我曾认为dllimport是可选的。我在某处读到,它是一种机制,可以帮助链接器和编译器执行更好的优化,从而提高性能。如果我错了,请纠正我,因为我在这个领域的经验有限。 - Gayan
3
是的,我也看到过dllimport是可选的。我的理解是你可以省略它,但是对于导入dll的应用程序来说,不应该有dllexport:否则编译器会认为它是被应用程序导出的,寻找相应的定义,但其实并不存在,这会导致错误消息的产生。 - Ghislain Fourny
如果导出的成员将被更高级别的程序集(dll/exe)使用,我认为dllimport不是可选的,否则你会得到一个链接器错误。刚刚一分钟前,我在我的项目中进行了实验,因为我也需要导出一个静态公共成员,它是一个字符串。 - jxramos

5

在Windows DLL中,__declspec(dllexport)__declspec(dllimport)之间有一个特定的区别,编译DLL时应该使用dllexport,而编译链接到此DLL的程序时应该使用dllimport。定义这个的标准方式是使用宏。

以下是Visual Studio的示例:

// The following ifdef block is the standard way of creating macros which make exporting 
// from a DLL simpler. All files within this DLL are compiled with the DLL_EXPORTS
// symbol defined on the command line. this symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see 
// DLL_API functions as being imported from a DLL, whereas this DLL sees symbols
// defined with this macro as being exported.
#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif

1

虽然这是一个有点老的帖子。但我想补充一下,为了解决这个问题,我需要在(Windows)__declspec(dllexport)/(import) 也在 .cpp 文件内部添加,以便外部代码可以访问它。

// From another answer to ilustrate this =)
#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif

some_app.h

class DLL_API SomeApp
{
    static void MyFunc();
}

some_app.cpp

DLL_API void SomeApp::MyFunc()
{
    // Some impl of my app. 
}

在我的情况下,这是特别需要的,因为我的函数是模板化的。也许对其他人也有用。

0

尽管摘要如此,但可以从DLL导出静态数据。然而,使用Visual Studio DLL项目提供的标准宏时会遇到问题:

#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif

如果您有多个DLL调用代码从一个DLL到另一个或在EXE和DLL之间,这个宏会存在问题,因为每个头文件都会被导出。需要使用处理__declspec的唯一宏。解决此问题的最安全方法如下:
#ifdef MYPROJECT_DLL_EXPORTS
     #define MYPROJECT_API __declspec(dllexport)
#else
     #define MYPROJECT_API __declspec(dllimport)
#endif

然后只需在编译器预处理器选项中为DLL项目定义MYPROJECT_API。 在您的头文件代码中:

struct/class MYPROJECT_API myclass {
   static int counter;
};

在一个 .cpp 文件中:

int myclass::counter = 0;

-2

使用C++17的内联函数 static inline std::string static_variable


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