在Linux上使用gcc和在Windows上使用MinGW构建共享库

19
我遇到了麻烦,无法生成一个构建设置,使得使用gcc和MinGW在Linux和Windows中都可以构建共享库。在Linux中,共享库在编译时不必解决所有依赖关系;而在Windows中,这似乎是必须的。以下是问题的详细描述:
$ cat foo.h 
#ifndef FOO_H
#define FOO_H
void printme();
#endif

$ cat foo.c
#include "foo.h"
#include <stdio.h>
void printme() {
    printf("Hello World!\n");
}

$ cat bar.h
#ifndef BAR_H
#define BAR_H
void printme2();
#endif

$ cat bar.c
#include "bar.h"
#include "foo.h"
void printme2() {
    printme();
    printme();
}

$ cat main.c
#include "bar.h"
int main(){
    printme2();
}

$ cat Makefile 
.c.o:
        gcc -fPIC -c $<

all: foo.o bar.o main.o
        gcc -shared foo.o -o libfoo.so
        gcc -shared bar.o -o libbar.so
        gcc main.o  -Wl,-rpath=. -L . -lbar -lfoo -o main

现在,在 Linux 中编译和运行这个程序是完全没有问题的:

$ make
gcc -fPIC -c foo.c
gcc -fPIC -c bar.c
gcc -fPIC -c main.c
gcc -shared foo.o -o libfoo.so
gcc -shared bar.o -o libbar.so
gcc main.o  -Wl,-rpath=. -L . -lbar -lfoo -o main

$ ./main 
Hello World!
Hello World!

在Windows中,我们需要将so文件转换成dll格式,这个过程非常简单和精细。
$ cat Makefile 
.c.o:
        gcc -fPIC -c $<

all: foo.o bar.o main.o
        gcc -shared foo.o -o libfoo.dll
        gcc -shared bar.o -o libbar.dll
        gcc main.o  -Wl,-rpath=. -L . -lbar -lfoo -o main

然而,当我们尝试构建时,会出现以下错误:
$ make
gcc -fPIC -c foo.c
foo.c:1:0: warning: -fPIC ignored for target (all code is position independent) [enabled by default]
gcc -fPIC -c bar.c
bar.c:1:0: warning: -fPIC ignored for target (all code is position independent) [enabled by default]
gcc -fPIC -c main.c
main.c:1:0: warning: -fPIC ignored for target (all code is position independent) [enabled by default]
gcc -shared foo.o -o libfoo.dll
gcc -shared bar.o -o libbar.dll
bar.o:bar.c:(.text+0x7): undefined reference to `printme'
bar.o:bar.c:(.text+0xc): undefined reference to `printme'
collect2.exe: error: ld returned 1 exit status
make: *** [all] Error 1

现在,我们只需将foo.o的对象包含到libbar.dll中,就可以修复错误:
$ cat Makefile 
.c.o:
        gcc -fPIC -c $<

all: foo.o bar.o main.o
        gcc -shared foo.o -o libfoo.dll
        gcc -shared bar.o foo.o -o libbar.dll
        gcc main.o  -Wl,-rpath=. -L . -lbar -lfoo -o main

$ make
gcc -fPIC -c foo.c
foo.c:1:0: warning: -fPIC ignored for target (all code is position independent) [enabled by default]
gcc -fPIC -c bar.c
bar.c:1:0: warning: -fPIC ignored for target (all code is position independent) [enabled by default]
gcc -fPIC -c main.c
main.c:1:0: warning: -fPIC ignored for target (all code is position independent) [enabled by default]
gcc -shared foo.o -o libfoo.dll
gcc -shared bar.o foo.o -o libbar.dll
gcc main.o -Wl,-rpath=. -L . -lbar -lfoo -o main 

$ ./main 
Hello World!
Hello World!

然而,我不喜欢这种方法,因为libbar.dll现在包含了foo和bar的符号。在Linux中,它仅包含bar的符号。对于库依赖于一些标准数值库(如BLAS)的情况,这种分离非常重要。我希望能够部署共享库,并使其依赖于用户机器上优化版本的数值库,而不是我的机器上的数值库。

无论如何,创建一个部分符号在编译时不都存在的共享库的正确流程是什么?

如果有影响的话,这些示例是在Linux上使用gcc 4.6.3和Windows上的mingw-get-inst-20120426.exe与gcc 4.7.2编译的。


你在 foo.hbar.h 中缺少必需的 __declspec(dllimport)__declspec(dllexport)。可以像这样添加:#if defined __ELF__ #define API __attribute((visibility("default"))) #elif defined EXPORT #define API __declspec(dllexport) #else #define API __declspec(dllimport) #endif 然后在 foo.cbar.c 中添加 #define EXPORT - bit2shift
类似于这个,但不包括extern "C",因为它是C++的构造。 - bit2shift
1个回答

19
在Windows操作系统下,您需要为DLL创建一个导入库。导入库看起来像静态库,因为它定义了所有所需的符号,但是它没有实际的函数实现,只有存根。导入库将解决“未定义引用”错误,并避免静态链接。
要使用MinGW创建导入库,请按照这里的说明进行操作。关键在于构建DLL时,必须向链接器传递选项-Wl,--out-implib,libexample_dll.a以生成导入库libexample_dll.a
然后,在编译主可执行文件时,您可以使用-lexample_dll选项(以及-L。)来链接导入库。因此,使用您的代码,我认为这应该可以工作:
all: foo.o bar.o main.o
    gcc -shared foo.o -o libfoo.dll -Wl,--out-implib,libfoo.a
    gcc -shared bar.o foo.o -o libbar.dll -Wl,--out-implib,libbar.a
    gcc main.o  -Wl,-rpath=. -L. -lbar -lfoo -o main

另外,请注意在 Windows 上,DLL 导出函数的调用约定几乎总是 __stdcall,而不是默认的 __cdecl,因此如果您希望其他软件能够使用您的 DLL,则建议将它们制作成 __cdecl。但这并非绝对必要,只要 DLL 中的代码和头文件就调用约定达成一致即可。


2
这个很好用。作为一个小注释,我使用了以下命令:"gcc -shared bar.o -L . -lfoo -o libbar.dll -Wl,--out-implib,libbar.a" 以便使用导入库。 - wyer33
1
你完全搞错了调用约定部分。Win32 API的约定是__stdcall,但这不是最常用的约定,__cdecl才是。你应该使用空的调用约定(即隐式的__cdecl)与extern "C"配对以获得最佳结果:无需名称修饰和无需.def文件(除非你在做一些恶心的事情,否则应该将其视为过时)。 - bit2shift
__stdcall 主要用于在 Win32 窗口子系统中,通过使用 CALLBACK 宏来定义消息处理的回调函数时,在用户代码中显式地使用。 - bit2shift
5
使用MinGW,不再需要导入库。您可以直接将可执行文件与使用MinGW编译的DLL链接。 - bit2shift

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