【问题标题】:What's the proper way to link against an executable on Windows?在 Windows 上链接可执行文件的正确方法是什么?
【发布时间】:2015-08-08 12:35:10
【问题描述】:

我需要使用插件中主要可执行文件中的一些符号。

链接可执行文件会导致以下链接器错误:

i686-w64-mingw32-g++ example.cpp -shared -I.. -std=c++11 -o test.dll ../../test.exe -static-libgcc -static-libstdc++ -fvisibility=hidden
[..]/test.exe:cygming-crtbegin.c:(.text+0x500): multiple definition of `__gcc_register_frame'
/usr/lib/gcc/i686-w64-mingw32/5.1.0/crtbegin.o:cygming-crtbegin.c:(.text+0x0): first defined here
[..]/test.exe:cygming-crtbegin.c:(.text+0x560): multiple definition of `__gcc_deregister_frame'
/usr/lib/gcc/i686-w64-mingw32/5.1.0/crtbegin.o:cygming-crtbegin.c:(.text+0x60): first defined here
[..]/test.exe: In function `ZlsRSoRK5Color':
[..]src/tools.h:212: multiple definition of `operator<<(std::ostream&, Color const&)'
/tmp/ccC97Hkz.o:example.cpp:(.text$_ZlsRSoRK5Color[__ZlsRSoRK5Color]+0x0): first defined here
../../test.exe: In function `ZN7MessageILb0EElsIcEERS0_OT_':
[..]/src/tools.h:241: multiple definition of `Message<false>& Message<false>::operator<< <char>(char&&)'
/tmp/ccC97Hkz.o:example.cpp:(.text$_ZN7MessageILb0EElsIcEERS0_OT_[__ZN7MessageILb0EElsIcEERS0_OT_]+0x0): first defined here
[..]/test.exe:crtexe.c:(.idata+0x3f0): multiple definition of `_imp__GeoIP_country_code'
[..]/test.exe:crtexe.c:(.idata+0x3f0): first defined here
[..]/test.exe:crtexe.c:(.idata+0x3f4): multiple definition of `_imp__GeoIP_country_name'
[..]/test.exe:crtexe.c:(.idata+0x3f4): first defined here
/usr/lib/gcc/i686-w64-mingw32/5.1.0/crtbegin.o:cygming-crtbegin.c:(.text+0x22): undefined reference to `_Jv_RegisterClasses'
collect2: error: ld returned 1 exit status

现在,如果我使用 -shared -Wl,--export-all-symbols 构建主可执行文件,那么链接 test.exe 就可以了, 但是 Windows 加载程序(或至少是 wine 加载程序)抱怨 test.exe 是一个 dll。

所以我需要在没有-shared 的情况下再次重新链接test.exe,这样我才能运行test.exe

即:

# produce the import executable
i686-w64-mingw32-g++ tools.o main.o [...] -o ../test.exe -shared -Wl,--export-all-symbols [...] -static-libgcc -static-libstdc++

# produce the real executable
i686-w64-mingw32-g++ tools.o main.o [...] -o ../test.exe -Wl,--export-all-symbols [...] -static-libgcc -static-libstdc++

这太骇人听闻了,但最后我确实有一个工作插件......

来回答我的问题:

有没有更好的方法来实现这一点(不传递函数指针)?

我知道MSVC 能够输出可执行文件的导入库,MinGW 有类似的方法吗?

我已尝试将-Wl,--out-implib,test.a 添加到链接器标志以获取可执行文件的导入库, 但是--out-implib 在链接可执行文件时似乎被忽略了。

【问题讨论】:

    标签: c++ windows mingw ld mingw-w64


    【解决方案1】:

    在这种情况下,您可能确实想要使用__declspec(dllexport) 属性限定.exe 中的回调符号。在我的 Linux Mint Debian 机器上进行交叉编译,以下最小示例适用于我:

    $ cat foo.c
    #include <stdio.h>
    
    int __declspec(dllexport) foo( int bar ){ return bar << 2; }
    int main(){ printf( "%d\n", foo( 4 ) ); return 0; }
    
    $ mingw32-gcc -o ~/src/exp/foo.exe -Wl,--out-implib=libfoo.dll.a foo.c
    

    这会产生both一个工作可执行文件,一个导入库来映射其导出的符号,用于链接插件时使用,只是在前面的命令中一次调用链接器,(如在 wine 下运行可执行文件并使用本机 linux nm 工具列出导入库时所见):

    $ ~/src/exp/foo.exe
    16
    
    $ nm -A libfoo.dll.a
    libfoo.dll.a:d000002.o:00000000 I _foo_exe_iname
    libfoo.dll.a:d000002.o:00000000 i .idata$4
    libfoo.dll.a:d000002.o:00000000 i .idata$5
    libfoo.dll.a:d000002.o:00000000 i .idata$7
    libfoo.dll.a:d000000.o:         U _foo_exe_iname
    libfoo.dll.a:d000000.o:00000000 I __head_foo_exe
    libfoo.dll.a:d000000.o:00000000 i .idata$2
    libfoo.dll.a:d000000.o:00000000 i .idata$4
    libfoo.dll.a:d000000.o:00000000 i .idata$5
    libfoo.dll.a:d000001.o:00000001 a @feat.00
    libfoo.dll.a:d000001.o:00000000 T _foo
    libfoo.dll.a:d000001.o:         U __head_foo_exe
    libfoo.dll.a:d000001.o:00000000 i .idata$4
    libfoo.dll.a:d000001.o:00000000 i .idata$5
    libfoo.dll.a:d000001.o:00000000 i .idata$6
    libfoo.dll.a:d000001.o:00000000 i .idata$7
    libfoo.dll.a:d000001.o:00000000 I __imp__foo
    libfoo.dll.a:d000001.o:00000000 t .text
    

    同样,可执行文件在 WinXP 中运行良好,(在 LMDE 机器上的 VirtualBox 中运行,在 WinXP VM 中将 ~/src/exp 映射为驱动器 E:,并从 MSYS shell 调用):

    $ /e/foo.exe
    16
    

    FWIW,当将-shared 属性添加到链接器调用时,我可以重现您未能创建可运行的可执行文件;正如您所注意到的,它用于创建 DLL,(与格式上的可执行文件不同,在标头中嵌入了不同的幻数;否则它们基本相同)。

    总结:

    • 链接可执行文件时不要指定-shared

    • 使用 __declspec(dllexport) 属性。

    • 一定要指定-Wl,--out-implib=lib&lt;exename&gt;.dll.a 属性,当 链接可执行文件。

    【讨论】:

    • 感谢您的回答。很高兴知道链接器仅在链接可执行文件时为显式导出的符号发出导入库。但是,我真的很想避免仅仅因为 Windows 而用__declspec(dllexport) / API defines 污染很多符号。但这确实是比我更好的解决方案。也许 GNU 人应该考虑添加一个选项来为可执行文件生成一个导入库,而无需明确指定 __declspec(dllexport) - 对他们来说应该不是很多工作,因为代码已经存在于 dll 中。
    • @Thomas:我同意过多的__declspec(dllexport) 属性简直丑陋。 OTOH,您真的不想想要从可执行文件中导出所有符号,所以我认为当前的 GNU ld 行为是正确的。也许更优雅的是声明一个宏,#define EXE_CALLBACK __declspec(dllexport),并使用它来限定您的导出;优点是它强调了这些符号在您的代码中的特殊意义。 (甚至可以考虑有条件地定义它,在可执行文件中定义为__declspec(dllexport),在你的插件中定义为__declspec(dllimport)
    • @Thomas:FWIW,mingw32-gcc -o foo.exe -Wl,--export-all-symbols -Wl,--out-implib=libfoo.dll.a foo.c 对我来说很好用。它确实创建了导入库libfoo.dll.a;我看到的问题是这个导入库包含 IMO 不属于那里的符号。
    • 你是对的,链接器确实输出了一个导入库。当我测试它时,我一定忘记了-Wl,--export-all-symbols。挺尴尬的。无论如何,我找到了避免__declspec(dllexport) 的解决方案,请参阅下面的答案。
    【解决方案2】:

    就像 Keith Marshall 在 cmets 中所说的那样,-Wl,--out-implib 确实可以与以下两者结合使用:

    • -Wl,--export-all-symbols

    • 通过使用__declspec(dllexport) 声明符号

    • 或通过提供 .def 文件

    我选择了第三个选项,并编写了一个 bash 脚本来即时生成一个 def 文件/版本脚本,以避免导出大量不需要的符号。

    脚本可以在here找到。

    像这样使用它:

    export SYMBOLS_TO_EXPORT="*tools* *network* _Z8compressPvRjPKvjib ..." # use mangled names and skip leading underscores on i686
    export HOSTPREFIX=i686-w64-mingw32 # unneeded on Windows
    i686-w64-mingw32-g++ $(OBJS) `./gen_export_file $(OBJS)` -Wl,--out-implib=foo.exe.a -o foo.exe
    

    【讨论】:

    • 是的,创建一个.def 文件,而不是使用__declspec(dllexport) 属性限定,也可以。但是,它不是 我的 首选,因为它 a) 为额外的 .def 文件增加了维护负担,并且 b) 它牺牲了具有 的自我记录优势精心挑选的宏名称,扩展为__declspec(dllexport)清楚地识别您的回调,就在您的源代码中定义它们的位置。
    • @KeithMarshall:对,你的解决方案确实更可取。
    猜你喜欢
    • 2014-07-04
    • 2023-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-12
    • 1970-01-01
    相关资源
    最近更新 更多