如何告诉MinGW链接器不要导出所有符号?

21

我正在使用MinGW工具链构建Windows动态库。

为了构建这个库,我正在静态链接到其他两个提供API的库,并且我有一个.def文件,在其中编写了我想要在我的库中导出的唯一符号。

问题是GCC正在导出所有符号,包括我链接到的库中的符号。有没有办法告诉链接器只导出def文件中的符号?

我知道有选项--export-all-symbols,但似乎没有相反的选项。

现在,构建脚本的最后一行具有以下结构:

g++ -shared CXXFLAGS DEFINES INCLUDES -o library.dll library.cpp DEF_FILE \
OBJECT_FILES LIBS -Wl,--enable-stdcall-fixup

编辑:在关于链接器的文档中,它说--export-all-symbols是默认行为,并且当你没有显式使用该选项时,如果提供了一个def文件,则会禁用它,除非它不被禁用;第三方库中的符号仍然会被导出。
编辑:添加选项--exclude-libs LIBS–exclude-symbols SYMBOLS也无法防止从库中导出符号。

“--exclude-libs ALL” 选项很有意思,值得一试。 - Pavel Shishpor
7个回答

9

不确定为什么没有真正的答案,但这是对我有效的解决方法:

  1. Compilation of your object files:

    g++ -O0 -gdwarf-4 dll\dllmain.cpp -c -o dllmain.o
    
  2. Linking (-Wl,--exclude-all-symbols is important):

    g++ -Wl,--enable-auto-import -Wl,--out-implib,libsomelib.a -Wl,--exclude-all-symbols -shared dllmain.o -o somelib.dll
    

然后您可以选择在DLL源代码中直接导出哪些功能:

#include <windows.h>

__declspec(dllexport) void someExportedFunction() {
    MessageBox(NULL, "msgbox", "msgbox", MB_OK);
}

void nonExportedFunction() {
    MessageBox(NULL, "notexported", "notexported", MB_OK);
}

验证:

C:\libtest>pedump -E somelib.dll

=== EXPORTS ===

# module "somelib.dll"
# flags=0x0  ts="2014-02-20 08:37:48"  version=0.0  ord_base=1
# nFuncs=1  nNames=1

  ORD ENTRY_VA  NAME
    1     1570  _Z20someExportedFunctionv

(pedump = http://pedump.me)


3
这是一个经常出现的问题。以下是 SO 上两个相关问题:

以及 SO 之外:

换句话说,Windows 平台上的全局 / 本地导出不是在链接器级别处理的,而是通过提供补充 .dll 文件的 .def 文件来完成。

缺少好的答案后,我编写了一个 Python 脚本来处理从 DLL 导出表中移除内容,您可以在这里找到它。


3
您可以使用选项-Wl,--retain-symbols-file=file,并在file中列出您想要保留的符号(每行一个)。这将导致链接器丢弃所有其他符号,仅保留您想要的符号。

@paulm:在GNU ld文档中。 - Chris Dodd

2

如果您的binutils分发版(本地或交叉编译)提供了dllwrap,则可以使用它。

它可以使用DEF文件中的接口生成DLL(在底层,它调用gcc、ld和dlltool来完成此操作)。使用此方法与直接将DEF文件传递给GCC的区别在于文件中的定义会被不同地处理。

例如,如果您在导出文件中有符号重命名:

_SomeFuntion = _SomeFunction@12

GCC会创建两个导出项,一个名为_SomeFunction,另一个带有修饰名称,而dllwrap只会导出_SomeFuntion。因此,如果您在DEF文件中仅添加要导出的符号,则最终库中将只包含这些符号。
默认情况下,dllwrap使用C编译器驱动程序,因为它无法了解其他情况。由于您正在链接C++代码,因此必须使用选项--driver-name c++来设置驱动程序。如果您恰好具有具有前缀的MinGW可执行文件,则还必须在驱动程序名称中包含它(例如i686-mingw32-c++而不是c++),并且您可能还需要使用选项--dlltool-name
请尝试使用以下两行代替您发布的一行:
g++ -c CXXFLAGS DEFINES INCLUDES -o library.o library.cpp
dllwrap -o library.dll --driver-name c++ --def DEF_FILE OBJECT_FILES LIBS -Wl,--enable-stdcall-fixup

第一个命令从library.cpp的代码生成一个对象文件,第二个命令组装动态库。我假设OBJECT_FILES是您之前生成的其他对象文件,也应该包括library.o
话虽如此,我必须告诉您,dllwrap在2006年已被弃用,官方binutils软件包中也没有相关文档;如果您需要一些信息,可以像平常一样使用--help进行调用。它还可以生成导入库。

谢谢您的回答!它有效,除非在def文件中指定了符号,否则它们不会被导出,即使是从库中来的。我将使用dllwrap,我正在使用binutils 2.20,而dllwrap仍然存在,所以可能仍在维护。如果它已经过时...难道没有一种只使用gcc和cia就能做同样事情的方法吗? - James R.
1
@James 首选方法是在源代码中使用dllexport/dllimport标记符号,并通过这种方式处理符号可见性。 - rubenvb

2

您是否在提供的页面上阅读到了这篇与 --export-all-symbols 显式使用相关的行为,如果没有使用,则自动导出被禁用的内容:

任何目标文件中的符号都被标记为 __declspec(dllexport) 属性。

您是否尝试过仅显式导出您感兴趣的函数?由于名称混淆,很容易在 DEF 文件中出现错误,因此这种方法应该更加可靠。


我已经标记了我想要导出的符号,它们也被导出了,但是我无法访问我链接到的库的源代码。我不能从那里取消这个标记。 - James R.
@James 很抱歉,一旦导出完成就无法撤销。 - anon
好的,谢谢你的回复。那么,其他库导出的符号必须保留。 - James R.

1

免责声明:我只在Linux上进行过这个操作,但据我所知,它也适用于Windows。

您可以使用-fvisibility = hidden选项;有关更多信息,请参见http://gcc.gnu.org/wiki/Visibility


3
恐怕这并不能在Windows环境下帮上忙(至少对于我的编译器来说是这样)。即使是在一个foo测试中,我也会收到“此配置不支持可见性属性;被忽略”的警告,但还是谢谢你! - James R.

0

你可以在GNU LD中使用版本脚本创建一个导出符号的允许列表。

假设在库文件library.cpp中有一个名为library_somefunc的函数,并且你只想要导出这个符号。你可以准备一个纯文本文件,叫做version-script.txt

{
    global:
        library_somefunc;

    local:
        *;
};

通过将library_somefunc指定为全局变量,您表明该符号应在动态链接库中导出。我们将*指定为本地变量,以便不导出其他符号。
然后,使用以下命令构建dll:
g++ -shared CXXFLAGS DEFINES INCLUDES -o library.dll library.cpp DEF_FILE OBJECT_FILES LIBS -Wl,--enable-stdcall-fixup -Wl,--version-script=version-script.txt

你可以使用 Dependencies 来检查 DLL 文件是否正确导出符号。

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