dll = MinGW gcc 生成动态连接库 dll 的一些问题汇总

 

MinGW gcc 生成动态连接库 dll 的一些问题汇总

https://blog.csdn.net/liyuanbhu/article/details/42612365html

 

网络上关于用 MinGW gcc 生成动态连接库的文章不少。介绍的方法也都略有不一样。此次我在一个项目上恰好须要用到,因此就花了点时间将网上介绍的各类方法都实验了一遍。另外,还根据本身的理解试验了些网上没有提到的方法。这里,我就将这两天得到的成果总结一下。程序员

 

首先说一下个人开发环境:windows

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 2010ui

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

  1. //dlltest.c
  2. int Double(int x)
  3. {
  4. return x * 2;
  5. }

 

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

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

 

  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 查看相互调用的关系。

 

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

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

若是在程序中动态加载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模式后,每次运行都会报错。


 

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

  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 函数后是这样的:

  1. _ Double:
  2. 01021024 jmp dword ptr ds:[1020000h]
  3. 0102102 A nop
  4. 0102102 B nop

Jmp 语句后:

00905A4D  ???  
00905A4E  ???  
00905A4F  ???  
00905A50  ???  
00905A51  ???  
00905A52  ???  
00905A53  ???

但是在Debug 模式下:

  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 语句后:

  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 是正确的。

 

没有想明白为何会这样,看来还须要努力,到目前为止只成功了一小步。不过,若是是动态调用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 函数调用约定须要修改程序代码。


  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++ 函数的名字了。可是这样并不影响使用。

 

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

首先在生成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 文件不变:

  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 

  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 

  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)对于输出变量是有影响的。看下面的测试代码:

  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) ,结果就不同了。

  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)

  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 的常见问题就都解决了。

相关文章
相关标签/搜索