【问题标题】:Exporting symbols in static library that is linked to dynamic library导出链接到动态库的静态库中的符号
【发布时间】:2019-05-27 18:28:46
【问题描述】:

我在MSVC2017中有以下场景:

  1. 带有函数bool foo()的静态库
  2. 一个动态链接库,链接到上面的静态库
  3. 使用显式运行时链接加载动态链接库并通过GetProcAddress 调用foo() 的应用程序

在静态库中,foo()定义如下:

extern "C" __declspec(dllexport) bool foo() 
{
    return true;   
}

现在,因为foo() 没有被动态链接库使用,它的符号不会被导出,因此当应用程序使用GetProcAddress 时无法找到它。

我试过了:

#pragma comment(linker, "/include:foo")

和:

#pragma comment(linker, "/export:foo")

如果我将定义移动到动态链接库(不是可行的解决方案),我可以使用 Dependency Walker 看到导出的 foo(),但是当我将定义保存在静态库中时,我似乎无法导出符号以上链接器开关。我认为这是因为该符号仍未使用,因此无论如何仍未导出?

我想要一个适用于 Windows 上的 MSVC 和 Linux 上的 Clang 的解决方案。谢谢!

【问题讨论】:

  • 您是否同时尝试了/include:foo(强制在DLL 中包含该符号)和/export:foo(导出该符号)选项?您可能需要使用损坏的名称(_foo@0 或类似名称)。
  • @1201ProgramAlarm 我刚刚尝试过,但无济于事。我没有按照docs修饰名字:“注意在64位环境中,函数没有修饰”
  • 你的静态库有 .h 文件吗?

标签: c++ linker static-libraries dynamic-linking dllexport


【解决方案1】:

你做错了什么(或者至少不是你在问题中描述的那样)。当然,您在答案中发布的内容也有效,但这只是一种解决方法,因为“常规”方式应该有效。
这是一个小例子。

lib.cpp

extern "C" __declspec(dllexport) bool foo() {
    return true;
}

dll.cpp

extern "C" __declspec(dllexport) bool bar() {
    return false;
}

输出

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q056330888]> sopr.bat
*** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***

[prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity\2017\VC\Auxiliary\Build\vcvarsall.bat" x64
**********************************************************************
** Visual Studio 2017 Developer Command Prompt v15.9.13
** Copyright (c) 2017 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x64'

[prompt]> dir /b
dll.cpp
lib.cpp

[prompt]> cl /c /nologo /D_LIB /DSTATIC /Folib.obj lib.cpp
lib.cpp

[prompt]> lib /nologo /out:lib.lib lib.obj

[prompt]>
[prompt]> cl /c /nologo /DDLL /Fodll.obj dll.cpp
dll.cpp

[prompt]> link /nologo /dll /out:dll.dll dll.obj lib.lib
   Creating library dll.lib and object dll.exp

[prompt]> dir /b
dll.cpp
dll.dll
dll.exp
dll.lib
dll.obj
lib.cpp
lib.lib
lib.obj

[prompt]> dumpbin /nologo /exports dll.dll

Dump of file dll.dll

File Type: DLL

  Section contains the following exports for dll.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           1 number of functions
           1 number of names

    ordinal hint RVA      name

          1    0 00001000 bar

  Summary

        2000 .data
        1000 .pdata
        9000 .rdata
        1000 .reloc
        B000 .text

[prompt]>
[prompt]> :: ----- Re-link dll, instructing it to include foo -----
[prompt]>
[prompt]> link /nologo /dll /include:foo /out:dll.dll dll.obj lib.lib
   Creating library dll.lib and object dll.exp

[prompt]> dumpbin /nologo /exports dll.dll

Dump of file dll.dll

File Type: DLL

  Section contains the following exports for dll.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           2 number of functions
           2 number of names

    ordinal hint RVA      name

          1    0 00001000 bar
          2    1 00001010 foo
  Summary

        2000 .data
        1000 .pdata
        9000 .rdata
        1000 .reloc
        B000 .text

注意事项

  • 如前所述,我使用了命令行,但 VStudio IDE 调用了相同的命令(更多参数)
  • 添加 /include:foo(2ndlink 命令)导出 foo 以及(在下一个 dumpbin 输出中可以看到):
    • 指定此选项等同于添加#pragma comment(linker, "/include:foo")(在dll.cpp - 或任何直接传递给链接器的文件中)
    • /export:foo 不是必需的,因为该函数已经由 __declspec(dllexport) 导出
  • 我没有完成最后(应用程序),因为 foo 存在于 dumpbin 输出中就足够了(从 Dependency Walker)



更新#0

毕竟你可能没有做错事。但请记住,它不可扩展(如果您有数百个这样的符号)。查看[MS.Docs]: Overview of LIB,它在导出内容方面提供了与link 相同的选项。但它们似乎被忽略了。

在构建一个库时,可能希望在构建 时指定在链接时包含的所有符号(通过选项或通过 #pragma 注释) .lib,而不是在链接时。显然,它们被忽略(我已经测试过了),除非在直接传递给链接器的 .obj 文件(或选项)中指定了东西。这是因为[MS.Docs]: Building an Import Library and Export File强调是我的):

请注意,如果您在初步步骤中创建导入库,则在创建 .dll 之前,您必须在构建 .dll 时传递与构建导入库时相同的一组目标文件强>。

所以在将 .obj 文件传递​​给链接器时是有区别的:

  • 直接(命令行):包含在.dll(或.exe)中
  • 间接(通过命令行传递的 .lib 的一部分):它不包含在 .dll 中,仅搜索符号

这完全有道理,因为 lib 只是 .obj 文件的集合(存档)(在 Nix 上,存档器是 ar (以前称为 ranlib))。一个例子:

输出

[prompt]> del *.obj *.exp *.lib *.dll

[prompt]> dir /b
dll.cpp
lib.cpp

[prompt]> cl /c /nologo /D_LIB /DSTATIC /Folib.obj lib.cpp
lib.cpp

[prompt]> cl /c /nologo /DDLL /Fodll.obj dll.cpp
dll.cpp

[prompt]> :: Pass lib.obj directly to linker
[prompt]> link /nologo /dll /out:dll.dll dll.obj lib.obj
   Creating library dll.lib and object dll.exp

[prompt]> lib /nologo /out:lib.lib lib.obj

[prompt]>
[prompt]> dir
 Volume in drive E is SSD0-WORK
 Volume Serial Number is AE9E-72AC

 Directory of e:\Work\Dev\StackOverflow\q056330888

20/04/08  14:28    <DIR>          .
20/04/08  14:28    <DIR>          ..
19/06/30  20:03               114 dll.cpp
20/04/08  14:27            88,576 dll.dll
20/04/08  14:27               729 dll.exp
20/04/08  14:27             1,764 dll.lib
20/04/08  14:27               604 dll.obj
20/04/08  14:04                68 lib.cpp
20/04/08  14:28               822 lib.lib
20/04/08  14:27               604 lib.obj
               8 File(s)         93,281 bytes
               2 Dir(s)  83,419,111,424 bytes free

[prompt]> dumpbin /nologo /exports dll.dll

Dump of file dll.dll

File Type: DLL

  Section contains the following exports for dll.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           2 number of functions
           2 number of names

    ordinal hint RVA      name

          1    0 00001000 bar
          2    1 00001010 foo

  Summary

        2000 .data
        1000 .pdata
        9000 .rdata
        1000 .reloc
        B000 .text

[prompt]> :: Now do the same with the one from inside the .lib
[prompt]> del lib.obj

[prompt]> lib lib.lib /extract:lib.obj
Microsoft (R) Library Manager Version 14.16.27038.0
Copyright (C) Microsoft Corporation.  All rights reserved.


[prompt]> dir lib.obj
 Volume in drive E is SSD0-WORK
 Volume Serial Number is AE9E-72AC

 Directory of e:\Work\Dev\StackOverflow\q056330888

20/04/08  14:28               604 lib.obj
               1 File(s)            604 bytes
               0 Dir(s)  83,419,107,328 bytes free

[prompt]> link /nologo /dll /out:dll.dll dll.obj lib.obj
   Creating library dll.lib and object dll.exp

[prompt]> dumpbin /nologo /exports dll.dll

Dump of file dll.dll

File Type: DLL

  Section contains the following exports for dll.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           2 number of functions
           2 number of names

    ordinal hint RVA      name

          1    0 00001000 bar
          2    1 00001010 foo

  Summary

        2000 .data
        1000 .pdata
        9000 .rdata
        1000 .reloc
        B000 .tex



更新#1

我和[MS.Docs]: Linker options/INCLUDE/EXPORT)玩了一会儿。增加了一些复杂性。

lib0.cpp

//#pragma comment(linker, "/include:foo1")  // Apparently, has no effect in an .obj contained by a .lib
#pragma comment(linker, "/export:foo01")

#if defined(__cplusplus)
extern "C" {
#endif


__declspec(dllexport) bool foo00() {
    return true;
}

bool foo01() {
    return true;
}

bool foo02() {
    return true;
}

#if defined(__cplusplus)
}
#endif

lib1.cpp

#pragma comment(linker, "/export:foo11")

#if defined(__cplusplus)
extern "C" {
#endif


__declspec(dllexport) bool foo10() {
    return true;
}

bool foo11() {
    return true;
}

bool foo12() {
    return true;
}

#if defined(__cplusplus)
}
#endif

输出

[prompt]> del *.obj *.exp *.lib *.dll

[prompt]> cl /c /nologo /D_LIB /DSTATIC /Folib0.obj lib0.cpp
lib0.cpp

[prompt]> cl /c /nologo /D_LIB /DSTATIC /Folib1.obj lib1.cpp
lib1.cpp

[prompt]> lib /nologo /out:lib.lib lib0.obj lib1.obj

[prompt]> cl /c /nologo /DDLL /Fodll.obj dll.cpp
dll.cpp

[prompt]> :: ----- "Regular" behavior -----
[prompt]> link /nologo /dll /out:dll.dll dll.obj lib.lib
   Creating library dll.lib and object dll.exp

[prompt]> dumpbin /nologo /exports dll.dll

Dump of file dll.dll

File Type: DLL

  Section contains the following exports for dll.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           1 number of functions
           1 number of names

    ordinal hint RVA      name

          1    0 00001000 bar

  Summary

        2000 .data
        1000 .pdata
        9000 .rdata
        1000 .reloc
        B000 .text

[prompt]>
[prompt]> :: ----- /export a symbol -----
[prompt]> link /nologo /dll /out:dll.dll /export:foo02 dll.obj lib.lib
   Creating library dll.lib and object dll.exp

[prompt]> dumpbin /nologo /exports dll.dll

Dump of file dll.dll

File Type: DLL

  Section contains the following exports for dll.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           2 number of functions
           2 number of names

    ordinal hint RVA      name

          1    0 00001000 bar
          2    1 0000BB60 foo02

  Summary

        2000 .data
        1000 .pdata
        9000 .rdata
        1000 .reloc
        B000 .text

[prompt]>
[prompt]> :: ----- /include a symbol -----
[prompt]> link /nologo /dll /out:dll.dll /include:foo02 dll.obj lib.lib
   Creating library dll.lib and object dll.exp

[prompt]> dumpbin /nologo /exports dll.dll

Dump of file dll.dll

File Type: DLL

  Section contains the following exports for dll.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           3 number of functions
           3 number of names

    ordinal hint RVA      name

          1    0 00001000 bar
          2    1 00001010 foo00
          3    2 00001020 foo01

  Summary

        2000 .data
        1000 .pdata
        9000 .rdata
        1000 .reloc
        B000 .text

如所见(就像在 docs 中一样):

  • /EXPORT:搜索(在 .lib 中)符号 (foo02) 并简单地导出它李>
  • /INCLUDE:搜索(在.lib中)符号(foo02),获取包含的目标文件(lib0.obj),并将其包含在 .dll 中:
    • 因此,.obj 文件中标记为导出的其他 2 个符号(foo00foo01)被导出

结论

深入研究后发现 [MS.Docs]: /WHOLEARCHIVE (Include All Library Object Files) 声明(强调是我的):

/WHOLEARCHIVE 选项强制链接器包含来自指定静态库的每个目标文件,或者如果未指定库,则来自所有静态库指定给链接命令。

...

Visual Studio 2015 Update 2 中引入了 /WHOLEARCHIVE 选项。

输出

[prompt]> :: ----- YAY ----- /wholearchive ----- YAY -----
[prompt]> link /nologo /dll /out:dll.dll /wholearchive:lib.lib dll.obj lib.lib
   Creating library dll.lib and object dll.exp

[prompt]> dumpbin /nologo /exports dll.dll

Dump of file dll.dll

File Type: DLL

  Section contains the following exports for dll.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           5 number of functions
           5 number of names

    ordinal hint RVA      name

          1    0 00001000 bar
          2    1 00001040 foo00
          3    2 00001050 foo01
          4    3 00001010 foo10
          5    4 00001020 foo11

  Summary

        2000 .data
        1000 .pdata
        9000 .rdata
        1000 .reloc
        B000 .text

【讨论】:

  • 这里有没有办法只使用源代码完成? /包括适合我的需要。但是如果有很多符号要导出,维护 /INCLUDE 的列表就会变得复杂。而且,/wholearchive 绝对不可用,因为它导出所有内容。
【解决方案2】:

我最终的解决方案是创建一个名为 foo() 的虚拟函数来强制导出该编译单元中的所有符号。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-05
    • 1970-01-01
    相关资源
    最近更新 更多