MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)

时间:2021-07-16 15:50:35

网络上关于用 MinGW gcc 生成动态链接库的文章很多。介绍的方法也都略有不同。这次我在一个项目上刚好需要用到,所以就花了点时间将网上介绍的各种方法都实验了一遍。另外,还根据自己的理解试验了些网上没有提到的方法。这里,我就将这两天获得的成果总结一下。

 

首先说一下我的开发环境:

gcc version 4.9.2 (Rev1, Built by MSYS2 project)

Target: i686-w64-mingw32

Thread model: posix

--disable-sjlj-exceptions  --with-dwarf2

 

另外,为了试验生成的 dll 是否通用。测试代码时还用到了 Visual Stdio 2010。

在试验一种新的功能时,我一般会从最简单的代码开始。

 

[cpp]  view plain  copy
 
 MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)
  1. //dlltest.c  
  2. int Double(int x)  
  3. {  
  4.     return x * 2;  
  5. }  

 

 

下面的命令行将这个代码编译成 dll。

gcc dlltest.c -shared -o dlltest.dll -Wl,--out-implib,dlltest.lib

其中 -shared 告诉gcc dlltest.c 文件需要编译成动态链接库。-Wl 表示后面的内容是ld 的参数,需要传递给 ld。 --out-implib,dlltest.lib 表示让ld 生成一个名为 dlltest.lib 的导入库。

如果还需要 .def 文件,则上面的命令行可以写为:

gcc dlltest.c -shared -o dlltest.dll -Wl,--output-def,dlltest.def,--out-implib,dlltest.a

 

[cpp]  view plain  copy
 
 MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)
  1. //main.c  
  2. #include <stdio.h>  
  3. int Double(int x);  
  4. int main(void)  
  5. {  
  6.         printf("Hello :%d\n", Double(333));  
  7.         return 0;  
  8. }  

 

 

gcc main.c dlltest.lib -o main.exe

 

运行结果为:

Hello :666

说明生成的dlltest.dll是正确的。另外,也可以用dependecy walker 查看相互调用的关系。

MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)

 

实际上,如果我们的dll文件只是被MinGW gcc使用。都不需要生成 dlltest.lib。直接在编译的时候将 dlltest.dll 加进去就行了。

gcc main.c dlltest.dll -o main.exe

如果在程序中动态加载dll。那么代码可以这么写:

[cpp]  view plain  copy
 
 MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)
  1. //m2.c  
  2. define UNICODE 1  
  3.   
  4. #include <windows.h>  
  5. #include <stdio.h>  
  6.   
  7. typedef int (*INT_FUNC)(int);  
  8. int main(void)  
  9. {  
  10.     INT_FUNC db;  
  11.     HINSTANCE hInstLibrary = LoadLibrary(L"dlltest.dll");  
  12.     printf("LoadLibrary\n");  
  13.     db = (INT_FUNC)GetProcAddress(hInstLibrary, "Double");  
  14.       
  15.     printf("Hello :%d\n", db(333));  
  16.     FreeLibrary(hInstLibrary);   
  17.       
  18.     return 0;  
  19. }  

编译的时候更不需要dlltest.lib 了,甚至都不需要 dlltest,dll。

gcc m2.c -o m2.exe

运行的结果也是正确的。

那么这个dll 可以被其他c编译器使用吗?利用VC 2010来测试表明,可以生成exe文件。如果是生成Debug模式的exe文件,执行是正常的。但是改为release模式后,每次运行都会报错。


MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)

 

用VS2010 的调试功能,看了看反汇编的结果。看似都是正常的。

[plain]  view plain  copy
 
 MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)
  1. int _tmain(int argc, _TCHAR* argv[])  
  2. {  
  3.     int a;  
  4.     a = Double(333);  
  5. 01021000  push        14Dh    
  6. 01021005  call        _Double (1021024h)    
  7.     printf("Hello :%d\n", a);  
  8. 0102100A  push        eax    
  9. 0102100B  push        offset string "Hello :%d\n" (10220F4h)    
  10. 01021010  call        dword ptr [__imp__printf (10220A0h)]    
  11. 01021016  add         esp,0Ch    
  12.     getchar();  
  13. 01021019  call        dword ptr [__imp__getchar (102209Ch)]    
  14.     return 0;  
  15. 0102101F  xor         eax,eax    
  16. }  

 

单步跟进_Double 函数后是这样的:

[plain]  view plain  copy
 
 MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)
  1. _Double:  
  2. 01021024  jmp         dword ptr ds:[1020000h]    
  3. 0102102A  nop    
  4. 0102102B  nop    

Jmp 语句后:

[plain]  view plain  copy
 
 MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)
  1. 00905A4D  ???    
  2. 00905A4E  ???    
  3. 00905A4F  ???    
  4. 00905A50  ???    
  5. 00905A51  ???    
  6. 00905A52  ???    
  7. 00905A53  ???    

可是在Debug 模式下:

[plain]  view plain  copy
 
 MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)
  1. _Double:  
  2. 011B144C  jmp         dword ptr [__imp__Double (11B8340h)]    
  3. 011B1452  nop    
  4. 011B1453  nop    
  5. 011B1454  int         3    
  6. 011B1455  int         3    

Jmp 语句后:

[plain]  view plain  copy
 
 MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)
  1. 6C101560  push        ebp    
  2. 6C101561  mov         ebp,esp    
  3. 6C101563  mov         eax,dword ptr [ebp+8]    
  4. 6C101566  add         eax,eax    
  5. 6C101568  pop         ebp    
  6. 6C101569  ret    

而从下图可以看出,dlltest.dll 被加载到 6C100000 是正确的。

MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)

 

没有想明白为什么会这样,看来还需要努力,到目前为止只成功了一小步。不过,如果是动态调用dll,却没有问题。

[plain]  view plain  copy
 
 MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)
  1. #include <windows.h>  
  2.   
  3. typedef int (*INT_FUNC)(int);  
  4. int _tmain(int argc, _TCHAR* argv[])  
  5. {  
  6.     INT_FUNC db;  
  7.     HINSTANCE hInstLibrary = LoadLibrary(L"dlltest.dll");  
  8.     printf("LoadLibrary ");  
  9.     db = (INT_FUNC)GetProcAddress(hInstLibrary, "Double");  
  10.       
  11.     printf("Hello :%d\n", db(333));  
  12.     FreeLibrary(hInstLibrary);   
  13.     getchar();  
  14.     return 0;  
  15. }  

 

这个代码用 VC2010 编译执行一点问题都没有。很是奇怪。在网上查找了一番,发现可能是 MinGW gcc 生成的 lib 文件与 VC 生成的lib 文件有些细微的差别,导致在VC环境下,Debug模式下工作正常,而Release 模式工作却不正常。为了验证这个结论,又找了些资料学会了如何从dll文件生成VC下可用的lib文件。

下面的方法参考了这篇博客:

http://blog.sina.com.cn/s/blog_4f183d960100gqfj.html

生成VC下可用的lib文件需要有 def 文件,前面已经说过 -Wl,--output-def,dlltest.def  就可以生成对应的def 文件。

有了def文件之后,利用VS2010 提供的lib.exe可以生成对应的lib文件。

lib /machine:ix86 /def:dlltest.def

将生成的dlltest.lib 文件拷到VC项目中。编译,运行,一切正常。

我们知道 WinAPI 函数是符合 Pascal 函数调用约定的,也就是所谓的 stdcall。而刚才生成的dll 中的函数是使用的 C语言函数调用约定(__cdecl )。如果将其改为Pascal 函数调用约定需要修改程序代码。

 

[plain]  view plain  copy
 
 MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)
  1. //dlltest.c  
  2. int _stdcall Double(int x)  
  3. {  
  4.     return x * 2;  
  5. }  
  6.   
  7. //main.c  
  8. #include <stdio.h>  
  9. int _stdcall Double(int x);  
  10. int main(void)  
  11. {  
  12.         printf("Hello :%d\n", Double(333));  
  13.         return 0;  
  14. }  

编译命令是不变的。但是需要注意的是,这时生成的dll 文件中的函数名是有变化的。可以参看下图。原来是Double 现在变成了 Double@4,变成了这种类似 C++ 函数的名字了。但是这样并不影响使用。

MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)

 

网上关于生成和使用dll 的文章都会写到,生成dll 是函数声明需添加 __declspec(dllexport),而使用dll时函数声明要使用__declspec(dllimport)。大家都看到了,我前面的代码中这两个都没有用到。那么这两个声明有什么用呢。下面就做个测试。

首先在生成dll 的代码中增加:

[cpp]  view plain  copy
 
 MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)
  1. //dlltest.c  
  2. int __declspec(dllexport) _stdcall Double(int x);  
  3.   
  4. int _stdcall Double(int x)  
  5. {  
  6.     return x * 2;  
  7. }  

 

 编译命令如下:

gcc dlltest.c -shared -o dlltest.dll -Wl,--output-def,dlltest.def,--out-implib,dlltest.a

 

M.c 文件不变:

[cpp]  view plain  copy
 
 MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)
  1. //m.c  
  2. #include <stdio.h>  
  3.   
  4. int _stdcall Double(int x);  
  5.   
  6. int main(void)  
  7. {  
  8.         printf("Hello :%d\n", Double(333));  
  9.         return 0;  
  10. }  

 

编译命令如下:

gcc m.c dlltest.a -o m2.exe

 

编译没有问题,执行也没有问题。

修改一下m.c 。

[cpp]  view plain  copy
 
 MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)
  1. //m.c  
  2. #include <stdio.h>  
  3.   
  4. int __declspec(dllimport) _stdcall Double(int x);  
  5.   
  6. int main(void)  
  7. {  
  8.         printf("Hello :%d\n", Double(333));  
  9.         return 0;  
  10. }  

 

编译命令如下:

Gcc m.c dlltest.a -o m2.exe

 

编译没有问题,执行也没有问题。

再修改一下main.c 。

[cpp]  view plain  copy
 
 MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)
  1. //main.c  
  2. #include <stdio.h>  
  3.   
  4. int __declspec(dllexport) _stdcall Double(int x);  
  5.   
  6. int main(void)  
  7. {  
  8.         printf("Hello :%d\n", Double(333));  
  9.         return 0;  
  10. }  

 

编译命令如下:

Gcc main.c dlltest.a -o m2.exe

 

编译没有问题,执行也没有问题。 这个实验说明__declspec(dllexport)对于函数声明其实是没什么作用的。我也比较过生成的代码的反汇编结果,也是没区别的。并不像有些人所说增加了__declspec(dllexport)之后生成的代码能够更精炼。当然,这也可能是现在编译器的优化能力越来越强的结果。早期编译器跟不上,可能还是有区别的。

 

但是__declspec(dllexport)对于输出变量是有影响的。看下面的测试代码:

[cpp]  view plain  copy
 
 MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)
  1. //dlltest.c  
  2. int Double(int x);  
  3. int xxx = 123;  
  4. int Double(int x)  
  5. {  
  6.     return x * 2;  
  7. }  
  8.   
  9. //m.c  
  10. #include <stdio.h>  
  11.   
  12. int Double(int x);  
  13. extern int xxx;  
  14. int main(void)  
  15. {  
  16.         printf("Hello :%d\n", Double(333));  
  17.         printf("%d", xxx);  
  18.         return 0;  
  19. }  


编译:

 

gcc dlltest.c -shared -o dlltest.dll -Wl,--output-def,dlltest.def,--out-implib,dlltest.a

lib /machine:ix86 /def:dlltest.def

gcc m.c dlltest.a -o mm.exe

这样是可以编译执行的,说明dlltest.a 中包含了足够的信息。

但是第三句改为:

gcc m.c dlltest.lib -o mm.exe

 

则会有如下的错误,这也说明dlltest.a 和dlltest.lib 确实有些很小的差异。

undefined reference to `xxx'

collect2.exe: error: ld returned 1 exit status

 

VS2010 编译也是类似的错误,无法找到符号 xxx的定义。即使是main.c中增加了如下的声明:extern int __declspec(dllimport) xxx;

结果也是类似的:error LNK2001: 无法解析的外部符号 __imp__xxx

说明dlltest.lib 中就没有 xxx 的相关信息,不可能访问到dlltest.dll 中的xxx。

如果将dll的全局变量声明中增加 __declspec(dllexport) ,结果就不一样了。

[cpp]  view plain  copy
 
 MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)
  1. //dlltest.c  
  2. int Double(int x);  
  3. int  __declspec(dllexport)  xxx = 123;  
  4. int Double(int x)  
  5. {  
  6.     return x * 2;  
  7. }  
  8.   
  9. //m.c  
  10. #include <stdio.h>  
  11.   
  12. int Double(int x);  
  13. extern int xxx;  
  14. int main(void)  
  15. {  
  16.         printf("Hello :%d\n", Double(333));  
  17.         printf("%d", xxx);  
  18.         return 0;  
  19. }  

gcc dlltest.c -shared -o dlltest.dll -Wl,--output-def,dlltest.def,--out-implib,dlltest.a

lib /machine:ix86 /def:dlltest.def

gcc m.c dlltest.a -o mm.exe

编译成功, 

gcc m.c dlltest.lib -o mm.exe

还是失败的。

 

在VS2010中编译 m.c,仍然是失败的。报的错误是:

 error LNK2001: 无法解析的外部符号 _xxx

m.c 做一些修改。增加 __declspec(dllimport)

[cpp]  view plain  copy
 
 MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)
  1. //m.c  
  2. #include <stdio.h>  
  3.   
  4. int Double(int x);  
  5. int __declspec(dllimport) xxx;  
  6. int main(void)  
  7. {  
  8.         printf("Hello :%d\n", Double(333));  
  9.         printf("%d", xxx);  
  10.         return 0;  
  11. }  

 

VS2010 中编译就可以通过。另外,再多说一句,我试验的结果表明,__declspec(dllimport) 与__declspec(dllexport) 对于编译来说似乎没有任何区别,字面上的区别完全是给程序员自己看的。

至此,MinGW gcc 生成 dll 的常见问题就都解决了。

 

http://blog.csdn.net/liyuanbhu/article/details/42612365