【问题标题】:Not all symbols of an DLL-exported class is exported (VS9)并非 DLL 导出类的所有符号都被导出 (VS9)
【发布时间】:2011-03-02 03:57:05
【问题描述】:

我正在从一组静态库构建一个 DLL,但我遇到了一个问题,即只导出了部分类。

我正在做的是声明我要使用预处理器定义导出的所有符号,例如:

#if defined(MYPROJ_BUILD_DLL)
//Build as a DLL
#   define MY_API __declspec(dllexport)
#elif defined(MYPROJ_USE_DLL)
//Use as a DLL
#   define MY_API __declspec(dllimport)
#else
//Build or use as a static lib
#   define MY_API
#endif

例如:

class MY_API Foo{ 
   ...
}

然后我使用 MYPROJ_BUILD_DLLMYPROJ_USE_DLL undefined 构建静态库,导致构建静态库。

在另一个构建中,我从这些静态库创建了一个 DLL。所以我定义了MYPROJ_BUILD_DLL,导致我要导出的所有符号都带有__declspec(dllexport) 的属性(这是通过在 DLL 项目源文件中包含所有静态库头来完成的)。

编辑: 关于未引用符号的注意事项:链接器选项保留未引用数据 (/OPT:NOREF) 和不删除冗余 COMDAT (/OPT:NOICF) 已设置为不会删除未引用符号。

好的,现在解决问题。当我使用这个新的 DLL 时,我得到了无法解析的外部,因为并非一个类的所有符号都被导出。例如在这样的类中:

class MY_API Foo{
public:
   Foo(char const* );
   int bar();
private:
   Foo( char const*, char const* );
};

仅导出 Foo::Foo( char const*, char const*);int Foo::bar();。这个怎么可能?我可以理解是否整个班级都失踪了,例如我忘记在 DLL-build 中包含标题。但这只是部分缺失。

另外,如果Foo::Foo( char const*) 没有实现;那么 DLL 构建将有未解决的外部错误。但是构建很好(我还仔细检查了没有实现的声明)。

注意:我正在组合的静态库的组合大小在 30MB 左右,生成的 DLL 为 1.2MB。

我正在使用 Visual Studio 9.0 (2008) 来构建一切。 Depends 检查导出的符号。

编辑: 对于那些想知道为什么我不只是从每个静态库构建 DLL 的人:我不能,因为它们相互交叉引用(这就是为什么我需要将它们组合到一个 DLL 中)。我知道,这太可怕了,我无法真正理解其背后的逻辑。

【问题讨论】:

  • 我们(或至少我)建议您获取静态库的所有源代码,并将其添加到单个 DLL 项目中,然后构建一个 DLL。如果可能,完全删除中间 LIB。拥有一个 DLL,以及所有使用驻留在您的 LIB/LIB 中的源文件。

标签: c++ windows visual-studio-2008 dll


【解决方案1】:

请记住,当您链接到静态 LIB 时,默认情况下,链接器只会拉入客户端(在本例中是您的 DLL)实际引用的函数、类和数据。

所以会发生这样的事情:

  1. 您构建了静态 LIB,这很好。此 LIB 100% 有效。
  2. 您现在围绕原始二进制 LIB 构建静态 DLL。它只引入它实际引用的东西。它不会引入它需要做的整个类定义。此 DLL 虽已生成,但无效。
  3. 然后客户端使用 DLL,并期望看到导出类的完整二进制定义,因为这是客户端在随附的头文件中看到的。
  4. 但是,该类仅被部分导入,这就是您收到这些链接错误的原因。

修复:

  • 如果可以,请不要从静态 LIB 构建 DLL。从原始源代码构建您的 DLL。并根据原始源代码构建您的静态 LIB。
  • 否则您可能会修改链接器设置,但我强烈推荐上面的选项 1。请注意,OPT:NOREF 在这里不起作用,因为 OPT:NOREF 对静态 LIB 没有影响。

什么不能解决:

  • 高级链接器调整,如 OPT:NOREF、任何涉及 COMDAT 等的函数。如果您希望这些函数存在,则必须确保它们被引用,或者通过引用它们,或者通过明确告诉链接器,“嘿, 这个符号 X 被引用”。

附注:

  • 每当我构建一个 DLL 或 LIB 时,我都会同时构建一个 DLL 和一个 LIB,完全使用您正在使用的技术。拥有一个可以通过切换设置来生成 DLL 或 LIB 的代码库是理想的。但是,当您拥有两者的源代码时,从(二进制)静态 LIB 构建 DLL……我很难想象何时需要这种情况。

【讨论】:

  • 你说得对,我忘了在问题中添加该信息。我添加了关于我的链接器设置的注释,以便不会删除未引用的符号。以及我不为每个静态库构建 DLL 的动机;我不能,因为它们相互交叉引用。
  • 只是一个注释(也添加到我的答案中)OPT:NOREF 无论如何对静态 LIB 没有影响。它违背了拥有静态 LIB 的全部目的。这是有道理的。只是不是很多。
  • 应用于 DLL 链接器设置的 OPT:NOREF 设置不是静态库。但这应该没关系。如果将符号标记为导出,则链接器不会对其进行优化。
【解决方案2】:

问题肯定是您在 DLL 项目中使用了已经构建的静态 .lib。这行不通,您必须重建 .lib 以便函数获取 __declspec(dllexport) 声明符并由链接器导出。

此时,首先创建 .lib 的 DLL 兼容版本就不再那么有用了。只需创建两个项目,一个创建静态 .lib,另一个创建 DLL。从技术上讲,仍然可以在 DLL 项目中使用静态 .lib,但您必须使用 .def 文件导出函数。如果库有很多导出,那可能需要大量维护。

【讨论】:

  • 您说得对,__declspec(dllexport) 的另一个选项是使用模块定义文件 (.def)。但是使用 __declspec 导出符号同样有效。此外,在构建静态库时无法“导出”任何符号。静态库可以看作是编译器输出的目标文件的集合。
  • 没有。他的意思是链接器没有将这些函数标记为已导出。这是部分正确的。链接器未将任何类的 PRIVATE 方法标记为已导出。导出公共方法是因为它们源自静态 .LIB。您构建 DLL 所针对的 declspec 无效。它恰好在 public 函数的情况下工作,因为它们无论如何都会被导出。
  • 换句话说,您正处于产生“DLL 地狱”一词的那些无人区 DLL/LIB 场景中。有没有办法简单地制作一个DLL并完成它?
  • 在构建静态库时,您永远不会将任何内容标记为已导出。那么用 __declspec 标记它有什么不同呢?并且在构建 dll 时,使用 __declspec(dllexport) 标记类将导出所有方法和静态成员(c.f. msdn.microsoft.com/en-us/library/81h27t8c.aspx)。此外,它不是私有的,而是未解决的公共方法。
  • 这有所不同,因为链接器将看到对该函数的引用,这是为导出表槽分配值所必需的。使用静态库的程序也会发生同样的事情,如果主程序中的任何代码都没有引用 .lib 中的函数,那么它将不会包含在最终的二进制文件中。不要忘记 .lib 不是最终的二进制文件,它只是一袋 .obj 文件。通过编写一个仅使用库中的一个函数的小程序,您自己可以看到这一点。生成的 .exe 将比 .lib 小得多
【解决方案3】:

这可能为时已晚,但 OP 的抱怨是 private 方法(析构函数)没有从 dllexport'd 类导出。那不是很正常吗?不允许外部用户调用私有方法。

【讨论】:

    猜你喜欢
    • 2010-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-15
    • 1970-01-01
    • 1970-01-01
    • 2012-09-09
    • 1970-01-01
    相关资源
    最近更新 更多