将静态库中的符号导出到动态链接库

6
我在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都找到解决方案。谢谢!


1
你尝试过同时使用/include:foo(强制将该符号包含在DLL中)和/export:foo(导出该符号)选项吗?你可能需要使用名称混淆(_foo@0或类似的名称)。 - 1201ProgramAlarm
@1201ProgramAlarm 我刚试过了,但是没有成功。我没有按照文档来修饰名称: “请注意,在64位环境中,函数不会被修饰。” - OhMyGodThereArePiesEverywhere
你的静态库有*.h*文件吗? - CristiFati
2个回答

4
你正在做一些错误的事情(或者至少不是你在问题描述中所描述的)。当然,你在答案中发布的内容也可以工作,但那只是一个解决方法,因为“常规”方式应该可以工作。这里有一个小例子。 lib00.cpp
extern "C" __declspec(dllexport) bool foo()
{
    return true;
}

dll00.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 > nul

[prompt]>
[prompt]> dir /b
dll00.cpp
lib00.cpp

[prompt]>
[prompt]> cl /c /nologo /D_LIB /DSTATIC /Folib00.obj lib00.cpp
lib00.cpp

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

[prompt]>
[prompt]> cl /c /nologo /DDLL /Fodll00.obj dll00.cpp
dll00.cpp

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

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

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

Dump of file dll00.dll

File Type: DLL

  Section contains the following exports for dll00.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 .gehcont
        1000 .gxfg
        1000 .pdata
        9000 .rdata
        1000 .reloc
        E000 .text

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

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

Dump of file dll00.dll

File Type: DLL

  Section contains the following exports for dll00.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 .gehcont
        1000 .gxfg
        1000 .pdata
        9000 .rdata
        1000 .reloc
        E000 .text

注:

  • 如您所见,我使用了命令行,但相同的命令(更多参数)可以由 VStudio IDE 调用。

  • 添加 /include:foo(第二个 Link 命令)也会导出 foo(正如下一个 DumpBin 输出中所看到的):

    • 指定此选项等同于添加 #pragma comment(linker, "/include:foo")(在 dll.cpp 或任何直接传递给链接器的文件中)

    • /export:foo 不是必需的,因为该函数已经由 __declspec(dllexport) 导出

  • 我没有一直跟到最后(应用程序),因为 DumpBin 的输出中出现 foo 就足够了(从 Dependency Walker 也可以看到)。



更新 #0

您可能并没有做错事。但请记住,这不可扩展(如果您有数百个这样的符号)。查看 [MS.Learn]: LIB 概述,它提供与导出相关的与 Link 相同的选项。但它们似乎被忽略了。

当构建 .lib 时,也许您想在链接时指定所有要包含的符号(无论是通过选项还是通过 #pragma comment),当构建 .lib 时而不是在链接时。显然,它们被忽略了(我已经测试过了),除非在直接传递给链接器的 .obj 文件(或选项)中指定。这是因为[MS.Learn]: 构建导入库和导出文件强调 是我的):

请注意,如果您在创建 .dll 之前进行了预备步骤以创建您的导入库,则 在构建 .dll 时必须传递与构建导入库时相同的一组对象文件

因此,在将 .obj 文件传递给链接器时存在差异:

  • 直接(命令行):它包含在.dll(或.exe)中。

  • 间接(通过命令行传递的.lib的一部分):它不包括在.dll中,只搜索符号。

这完全是有道理的,因为lib只是一个.obj文件的集合(档案)(在Nix上,archiver是Ar(曾经称为RanLib))。 例如:

输出

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

[prompt]>
[prompt]> dir /b
dll00.cpp
lib00.cpp

[prompt]>
[prompt]> cl /c /nologo /D_LIB /DSTATIC /Folib00.obj lib00.cpp
lib00.cpp

[prompt]>
[prompt]> cl /c /nologo /DDLL /Fodll00.obj dll00.cpp
dll00.cpp

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

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

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

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

23/01/30  09:15    <DIR>          .
23/01/30  09:15    <DIR>          ..
23/01/30  09:12                72 dll00.cpp
23/01/30  09:14           106,496 dll00.dll
23/01/30  09:14               733 dll00.exp
23/01/30  09:14             1,790 dll00.lib
23/01/30  09:14               604 dll00.obj
23/01/30  09:07                71 lib00.cpp
23/01/30  09:15               822 lib00.lib
23/01/30  09:13               604 lib00.obj
               8 File(s)        111,192 bytes
               2 Dir(s)  51,727,843,328 bytes free

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

Dump of file dll00.dll

File Type: DLL

  Section contains the following exports for dll00.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 .gehcont
        1000 .gxfg
        1000 .pdata
        9000 .rdata
        1000 .reloc
        E000 .text

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

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


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

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

23/01/30  09:16               604 lib00.obj
               1 File(s)            604 bytes
               0 Dir(s)  51,727,839,232 bytes free

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

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

Dump of file dll00.dll

File Type: DLL

  Section contains the following exports for dll00.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 .gehcont
        1000 .gxfg
        1000 .pdata
        9000 .rdata
        1000 .reloc
        E000 .text


更新 #1

我简要尝试了一下[MS.Learn]: 链接器选项/INCLUDE/EXPORT)。增加了一些复杂度。

lib01.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

lib10.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]>
[prompt]> cl /c /nologo /D_LIB /DSTATIC /Folib01.obj lib01.cpp
lib01.cpp

[prompt]>
[prompt]> cl /c /nologo /D_LIB /DSTATIC /Folib10.obj lib10.cpp
lib10.cpp

[prompt]>
[prompt]> lib /nologo /out:lib0110.lib lib01.obj lib10.obj

[prompt]>
[prompt]> cl /c /nologo /DDLL /Fodll00.obj dll00.cpp
dll00.cpp

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

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

Dump of file dll00.dll

File Type: DLL

  Section contains the following exports for dll00.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 .gehcont
        1000 .gxfg
        1000 .pdata
        9000 .rdata
        1000 .reloc
        E000 .text

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

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

Dump of file dll00_export.dll

File Type: DLL

  Section contains the following exports for dll00_export.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 0000E1A0 foo02

  Summary

        2000 .data
        1000 .gehcont
        1000 .gxfg
        1000 .pdata
        9000 .rdata
        1000 .reloc
        E000 .text

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

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

Dump of file dll00_include.dll

File Type: DLL

  Section contains the following exports for dll00_include.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 .gehcont
        1000 .gxfg
        1000 .pdata
        9000 .rdata
        1000 .reloc
        E000 .text

如同(就像在 Docs中看到的):

  • /EXPORT:查找(在.lib文件中)符号(foo02),并简单导出它。

  • /INCLUDE:在.lib文件中查找符号(foo02),获取包含的对象文件(lib0.obj),并在.dll包含它

    • 因此,标记为在.obj文件中导出的另外2个符号(foo00, foo01)会被导出。

结论

深入研究后发现[MS.Learn]: /WHOLEARCHIVE(包括所有库对象文件)说明了以下内容(我加粗了强调部分):

/WHOLEARCHIVE选项会强制链接器从指定的静态库中包含每个对象文件,或者如果未指定库,则从指定给LINK命令的所有静态库中都包含

...

/WHOLEARCHIVE选项是在Visual Studio 2015 Update 2中引入的。

输出结果

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

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

Dump of file dll00_wholearchive.dll

File Type: DLL

  Section contains the following exports for dll00_wholearchive.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 .gehcont
        1000 .gxfg
        1000 .pdata
        9000 .rdata
        1000 .reloc
        E000 .text

有没有办法只使用源代码完成?/包含适合我的需求。但如果要导出许多符号,则维护/INCLUDE列表变得复杂。而/wholearchive绝对不能使用,因为它会导出所有内容。 - 欢乐的Xiaox

1

我的解决方案是创建一个虚拟函数,调用foo()以强制导出编译单元中的所有符号。


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