zoukankan      html  css  js  c++  java
  • MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)

    网络上关于用 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
     
     在CODE上查看代码片派生到我的代码片
    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
     
     在CODE上查看代码片派生到我的代码片
    1. //main.c  
    2. #include <stdio.h>  
    3. int Double(int x);  
    4. int main(void)  
    5. {  
    6.         printf("Hello :%d ", 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。那么代码可以这么写:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    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 ");  
    13.     db = (INT_FUNC)GetProcAddress(hInstLibrary, "Double");  
    14.       
    15.     printf("Hello :%d ", 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 的调试功能,看了看反汇编的结果。看似都是正常的。

    [plain] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    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 ", a);  
    8. 0102100A  push        eax    
    9. 0102100B  push        offset string "Hello :%d " (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
     
     在CODE上查看代码片派生到我的代码片
    1. _Double:  
    2. 01021024  jmp         dword ptr ds:[1020000h]    
    3. 0102102A  nop    
    4. 0102102B  nop    

    Jmp 语句后:

    [plain] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. 00905A4D  ???    
    2. 00905A4E  ???    
    3. 00905A4F  ???    
    4. 00905A50  ???    
    5. 00905A51  ???    
    6. 00905A52  ???    
    7. 00905A53  ???    

    可是在Debug 模式下:

    [plain] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    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
     
     在CODE上查看代码片派生到我的代码片
    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,却没有问题。

    [plain] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    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 ", 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
     
     在CODE上查看代码片派生到我的代码片
    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 ", Double(333));  
    13.         return 0;  
    14. }  

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

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

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

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    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
     
     在CODE上查看代码片派生到我的代码片
    1. //m.c  
    2. #include <stdio.h>  
    3.   
    4. int _stdcall Double(int x);  
    5.   
    6. int main(void)  
    7. {  
    8.         printf("Hello :%d ", Double(333));  
    9.         return 0;  
    10. }  

    编译命令如下:

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

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

    修改一下m.c 。

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    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 ", Double(333));  
    9.         return 0;  
    10. }  

    编译命令如下:

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

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

    再修改一下main.c 。

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    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 ", Double(333));  
    9.         return 0;  
    10. }  

    编译命令如下:

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

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

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

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    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 ", 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
     
     在CODE上查看代码片派生到我的代码片
    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 ", 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
     
     在CODE上查看代码片派生到我的代码片
    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 ", 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

  • 相关阅读:
    nmap 查看内网存活主机
    msf ms17_010 port:445
    nmap 检测ms17-010 port:445
    msf mysql port:3306
    msf ssh port:22
    Wireshark的两种过滤器与BPF过滤规则
    Wireshark使用记录
    过滤搜索引擎的抓取数据
    WEB容器开启、关闭OPTIONS方法
    代码泄露到Github
  • 原文地址:https://www.cnblogs.com/findumars/p/6546091.html
Copyright © 2011-2022 走看看