link 针对 Windows 上的可执行文件的正确方法是什么?
What's the proper way to link against an executable on Windows?
我需要在插件中使用主要可执行文件中的一些符号。
针对可执行文件的链接会导致以下 linker 错误:
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
构建主可执行文件,那么 link 对 test.exe
有效,
但是 Windows 加载程序(或者至少是 wine 加载程序)抱怨 test.exe
是一个 dll。
所以我需要重新 link test.exe
再一次 -shared
所以我可以 运行 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
添加到 linker 标志以获取可执行文件的导入库,
但是 --out-implib
在 link 执行可执行文件时似乎被忽略了。
就像评论中的 Keith Marshall 一样,-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
在这种情况下,您可能 想要在 .exe
中使用 __declspec(dllexport)
属性限定回调符号。 Cross-compiling 在我的 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 一个 working 可执行文件,and 一个导入库来映射其导出符号,在链接 plug-ins 时使用,只需 one 在前面的命令中调用链接器,(如 运行 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
libfoo.dll.a:d000002.o:00000000 i .idata
libfoo.dll.a:d000002.o:00000000 i .idata
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
libfoo.dll.a:d000000.o:00000000 i .idata
libfoo.dll.a:d000000.o:00000000 i .idata
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
libfoo.dll.a:d000001.o:00000000 i .idata
libfoo.dll.a:d000001.o:00000000 i .idata
libfoo.dll.a:d000001.o:00000000 i .idata
libfoo.dll.a:d000001.o:00000000 I __imp__foo
libfoo.dll.a:d000001.o:00000000 t .text
同样,可执行文件在 WinXP 中运行得很好,(运行 在 LMDE 框上的 VirtualBox 中,~/src/exp 在 WinXP VM 中映射为驱动器 E:,并从 MSYS 调用shell):
$ /e/foo.exe
16
FWIW,在将 -shared
属性添加到链接器调用时,我可以重现您创建可运行可执行文件的失败;正如您所注意到的,它用于创建 DLL,(它与 格式的可执行文件的不同之处在于 header 中嵌入了不同的幻数;否则它们基本上是相同的).
总结:
链接可执行文件时不要指定-shared
。
限定要从可执行文件中导出的符号
__declspec(dllexport)
属性。
请指定 -Wl,--out-implib=lib<exename>.dll.a
属性,当
链接可执行文件。
我需要在插件中使用主要可执行文件中的一些符号。
针对可执行文件的链接会导致以下 linker 错误:
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
构建主可执行文件,那么 link 对 test.exe
有效,
但是 Windows 加载程序(或者至少是 wine 加载程序)抱怨 test.exe
是一个 dll。
所以我需要重新 link test.exe
再一次 -shared
所以我可以 运行 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
添加到 linker 标志以获取可执行文件的导入库,
但是 --out-implib
在 link 执行可执行文件时似乎被忽略了。
就像评论中的 Keith Marshall 一样,-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
在这种情况下,您可能 想要在 .exe
中使用 __declspec(dllexport)
属性限定回调符号。 Cross-compiling 在我的 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 一个 working 可执行文件,and 一个导入库来映射其导出符号,在链接 plug-ins 时使用,只需 one 在前面的命令中调用链接器,(如 运行 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
libfoo.dll.a:d000002.o:00000000 i .idata
libfoo.dll.a:d000002.o:00000000 i .idata
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
libfoo.dll.a:d000000.o:00000000 i .idata
libfoo.dll.a:d000000.o:00000000 i .idata
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
libfoo.dll.a:d000001.o:00000000 i .idata
libfoo.dll.a:d000001.o:00000000 i .idata
libfoo.dll.a:d000001.o:00000000 i .idata
libfoo.dll.a:d000001.o:00000000 I __imp__foo
libfoo.dll.a:d000001.o:00000000 t .text
同样,可执行文件在 WinXP 中运行得很好,(运行 在 LMDE 框上的 VirtualBox 中,~/src/exp 在 WinXP VM 中映射为驱动器 E:,并从 MSYS 调用shell):
$ /e/foo.exe
16
FWIW,在将 -shared
属性添加到链接器调用时,我可以重现您创建可运行可执行文件的失败;正如您所注意到的,它用于创建 DLL,(它与 格式的可执行文件的不同之处在于 header 中嵌入了不同的幻数;否则它们基本上是相同的).
总结:
链接可执行文件时不要指定
-shared
。限定要从可执行文件中导出的符号
__declspec(dllexport)
属性。请指定
-Wl,--out-implib=lib<exename>.dll.a
属性,当 链接可执行文件。