来自 class 的受保护静态枚举的链接器错误,即使已经定义

Linker error of an protected static enum from a class, even being defined already

在 Linux 和 Mac 开发项目一段时间后,我终于得到了一台 Windows 机器 Visual Studio 2015,我有两个小库对于一般用途,一个称为 Platform,另一个称为 Foundation,当我尝试 link 带有 Platform 的 Foundation 库时出现问题(Platform 库已正确编译,没有特殊警告)输出以下 linker 错误:

(我省略了一些)

1>TUID.obj : error LNK2001: unresolved external symbol "protected: static enum GameCore::TraceLevels::Type GameCore::Trace::sm_level" (?sm_level@Trace@GameCore@@1W4Type@TraceLevels@2@A)
1>Units.obj : error LNK2001: unresolved external symbol "protected: static enum GameCore::TraceLevels::Type GameCore::Trace::sm_level" (?sm_level@Trace@GameCore@@1W4Type@TraceLevels@2@A)
1>Wildcard.obj : error LNK2001: unresolved external symbol "protected: static enum GameCore::TraceLevels::Type GameCore::Trace::sm_level" (?sm_level@Trace@GameCore@@1W4Type@TraceLevels@2@A)
1>RPC.obj : error LNK2001: unresolved external symbol "protected: static enum GameCore::TraceLevels::Type GameCore::Trace::sm_level" (?sm_level@Trace@GameCore@@1W4Type@TraceLevels@2@A)
1>SmartPtr.obj : error LNK2001: unresolved external symbol "protected: static enum GameCore::TraceLevels::Type GameCore::Trace::sm_level" (?sm_level@Trace@GameCore@@1W4Type@TraceLevels@2@A)
1>Stream.obj : error LNK2001: unresolved external symbol "protected: static enum GameCore::TraceLevels::Type GameCore::Trace::sm_level" (?sm_level@Trace@GameCore@@1W4Type@TraceLevels@2@A)
1>String.obj : error LNK2001: unresolved external symbol "protected: static enum GameCore::TraceLevels::Type GameCore::Trace::sm_level" (?sm_level@Trace@GameCore@@1W4Type@TraceLevels@2@A)
1>Natural.obj : error LNK2001: unresolved external symbol "protected: static enum GameCore::TraceLevels::Type GameCore::Trace::sm_level" (?sm_level@Trace@GameCore@@1W4Type@TraceLevels@2@A)
1>Numeric.obj : error LNK2001: unresolved external symbol "protected: static enum GameCore::TraceLevels::Type GameCore::Trace::sm_level" (?sm_level@Trace@GameCore@@1W4Type@TraceLevels@2@A)
1>Profile.obj : error LNK2001: unresolved external symbol "protected: static enum GameCore::TraceLevels::Type GameCore::Trace::sm_level" (?sm_level@Trace@GameCore@@@@1W4Type@TraceLevels@2@A)

平台库中 Trace.h 的以下 header 如下:

namespace TraceLevels
{
  enum Type
  {
    Debug,    ///< Debug logging messages.
    Info,     ///< General info messages.
    Warning,  ///< Warning messages.
    Error,    ///< Critical error messages.
  };
}
typedef GameCore::TraceLevels::Type TraceLevel;

/// Trace interface.
class GAMECORE_PLATFORM_API Trace
{
public:
  /// Default size for formatted trace message buffers without requiring dynamic memory allocation.
  static const size_t DEFAULT_MESSAGE_BUFFER_SIZE = 1024;

  /// @name Logging Interface
  //@{
  static void SetLevel( TraceLevel level );
  static inline TraceLevel GetLevel();

  static void Output( TraceLevel level, const char* pFormat, ... );
  static void OutputVa( TraceLevel level, const char* pFormat, va_list argList );
  //@}

protected:
  /// Current logging level.
  static TraceLevel sm_level; // This is the missing symbol which the linker complain

  /// Logging level of the last message.
  static TraceLevel sm_lastMessageLevel;

  /// True if logging just started a fresh line.
  static bool sm_bNewLine;

  /// @name Logging Implementation
  //@{
  static void OutputImplementation( const char* pMessage );
  //@}

  /// @name Static Utility Functions
  //@{
  static const char* GetLevelString( TraceLevel level );
  //@}
};

并将它们的定义正确添加到源文件中 (Trace.cpp)

using namespace GameCore;

GameCore::TraceLevel Trace::sm_level = GameCore::TraceLevels::Info;
GameCore::TraceLevel Trace::sm_lastMessageLevel = GameCore::TraceLevels::Debug;
bool GameCore::Trace::sm_bNewLine = true;

我正在使用 CMake 生成我的解决方案文件,输出的 .lib 和 .dll 位于 lib\ 和 bin\ 目录中,在根构建目录(源代码之外)内并设置 lib\作为 link 目录(使用 cmake link_directories)

他们都在两个库中正确设置了 __declspec(导出)。

Both they have set the __declspec(export) properly

这是不对的,它们不都导出 class。只有 DLL 可以。

您的 Trace::sm_level 变量存储在 DLL 的数据部分。对于同一模块中的代码,从同一模块读写全局变量很简单,链接器知道变量的地址。但如果另一个模块中的代码需要访问它,那就不简单了。地址不再可预测,DLL 可能已从其首选基地址重新定位。

需要额外的间接级别。目标地址固定为实际 DLL 地址的指针。编译器会自动处理这个问题,但它确实需要知道这不再是一个"normal"全局变量。所以它将使用指针而不是尝试从它自己的数据部分读取它。

链接器抱怨的是什么,您的代码说它在您正在构建的同一个模块中,#include 就是这样做的。但是链接器无法在您正在构建的模块的数据部分中找到 Trace::sm_level 变量。这当然是准确的,它存在于 DLL 中。

您通过声明 class __declspec(dllimport) 告诉编译器这一点。现在知道了。

我们看不到GAMECORE_PLATFORM_API是什么样子,但问题出在通心粉上。它应该是这样的:

#if BUILDING_GAMECORE
#  define GAMECORE_PLATFORM_API __declspec(dllexport)
#else
#  define GAMECORE_PLATFORM_API __declspec(dllimport)
#endif

并更改构建 DLL 的项目,使用项目 > 属性 > C/C++ > 预处理器 > 预处理器定义,添加 BUILDING_GAMECORE。在任何使用 DLL 的项目中无需进行任何更改,他们现在将看到 __declspec(dllimport).