网络上关于用 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。

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

  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

  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. #include <windows.h>
  4. #include <stdio.h>
  5. typedef int (*INT_FUNC)(int);
  6. int main(void)
  7. {
  8. INT_FUNC db;
  9. HINSTANCE hInstLibrary = LoadLibrary(L"dlltest.dll");
  10. printf("LoadLibrary\n");
  11. db = (INT_FUNC)GetProcAddress(hInstLibrary, "Double");
  12. printf("Hello :%d\n", db(333));
  13. FreeLibrary(hInstLibrary);
  14. return 0;
  15. }

编译的时候更不需要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. 0102102A  nop
  4. 0102102B  nop

Jmp 语句后:

  1. 00905A4D  ???
  2. 00905A4E  ???
  3. 00905A4F  ???
  4. 00905A50  ???
  5. 00905A51  ???
  6. 00905A52  ???
  7. 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. typedef int (*INT_FUNC)(int);
  3. int _tmain(int argc, _TCHAR* argv[])
  4. {
  5. INT_FUNC db;
  6. HINSTANCE hInstLibrary = LoadLibrary(L"dlltest.dll");
  7. printf("LoadLibrary ");
  8. db = (INT_FUNC)GetProcAddress(hInstLibrary, "Double");
  9. printf("Hello :%d\n", db(333));
  10. FreeLibrary(hInstLibrary);
  11. getchar();
  12. return 0;
  13. }

这个代码用 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. //main.c
  7. #include <stdio.h>
  8. int _stdcall Double(int x);
  9. int main(void)
  10. {
  11. printf("Hello :%d\n", Double(333));
  12. return 0;
  13. }

编译命令是不变的。但是需要注意的是,这时生成的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. int _stdcall Double(int x)
  4. {
  5. return x * 2;
  6. }

编译命令如下:

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. int _stdcall Double(int x);
  4. int main(void)
  5. {
  6. printf("Hello :%d\n", Double(333));
  7. return 0;
  8. }

编译命令如下:

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

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

修改一下m.c 。

  1. //m.c
  2. #include <stdio.h>
  3. int __declspec(dllimport) _stdcall Double(int x);
  4. int main(void)
  5. {
  6. printf("Hello :%d\n", Double(333));
  7. return 0;
  8. }

编译命令如下:

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

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

再修改一下main.c 。

  1. //main.c
  2. #include <stdio.h>
  3. int __declspec(dllexport) _stdcall Double(int x);
  4. int main(void)
  5. {
  6. printf("Hello :%d\n", Double(333));
  7. return 0;
  8. }

编译命令如下:

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. //m.c
  9. #include <stdio.h>
  10. int Double(int x);
  11. extern int xxx;
  12. int main(void)
  13. {
  14. printf("Hello :%d\n", Double(333));
  15. printf("%d", xxx);
  16. return 0;
  17. }

编译:

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. //m.c
  9. #include <stdio.h>
  10. int Double(int x);
  11. extern int xxx;
  12. int main(void)
  13. {
  14. printf("Hello :%d\n", Double(333));
  15. printf("%d", xxx);
  16. return 0;
  17. }

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. int Double(int x);
  4. int __declspec(dllimport) xxx;
  5. int main(void)
  6. {
  7. printf("Hello :%d\n", Double(333));
  8. printf("%d", xxx);
  9. return 0;
  10. }

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

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

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

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

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

    MinGW gcc 生成动态链接库 dll 的一些问题汇总 https://blog.csdn.net/liyuanbhu/article/details/42612365 网络上关于用 MinGW  ...

  2. MinGW gcc 生成动态链接库 dll 的一些问题汇总 (补充)

    我以前写过一个小短文,介绍MinGW gcc 生成动态链接库 dll 的一些问题.当时写的并不全面.近期又遇到写新的问题.这里记录一下,做个补充. 通常情况下,dll 中的函数假设採用 _stdcal ...

  3. GCC生成动态链接库(.so文件):-shared和-fPIC选项

    Linux 下动态链接库(shared object file,共享对象文件)的文件后缀为.so,它是一种特殊的目标文件(object file),可以在程序运行时被加载(链接)进来.使用动态链接库的 ...

  4. 使用python创建生成动态链接库dll

    如今,随着深度学习的发展,python已经成为了深度学习研究中第一语言.绝大部分的深度学习工具包都有python的版本,很多重要算法都有python版本的实现.为了将这些算法应用到具体工程中,这些工具 ...

  5. gcc 生成动态链接库

    http://blog.csdn.net/ngvjai/article/details/8520840 Linux下文件的类型是不依赖于其后缀名的,但一般来讲: .o,是目标文件,相当于windows ...

  6. MinGW(GCC)编译DLL文件

    这两天用CB(Code::Blocks)写个小程序,要编译出DLL供VB(6)使用.CB使用mingw-gcc作为编译器,在库文件的产出上跟VC.VS之类的IDE略有不同. 由于C语言的基础知识不是太 ...

  7. 引用MinGW生成的.dll.a后出现的问题

    以前很少调用MinGW的运行时库,现在用到一个项目,用到了glib和gettext等. 遇到了一个问题,折腾了一个下午. gettext的运行时库之一是intl,MinGW只提供了.dll.a,于是参 ...

  8. 利用GCC编译器生成动态链接库和静态链接库

    转载请标明:http://www.cnblogs.com/winifred-tang94/ 1.编译过程 gcc –fPIC –c xxx.c 其中-fPIC是通知gcc编译器产生位置独立的目标代码. ...

  9. 动态链接库DLL的创建生成及调用

    一.背景 最近在做CANTOUSB底层驱动的调用,是调用别人已经封装好的库,看不到别人写的源程序.程序中调用的是隐式调用即 x.h+x.lib+x.dll,其中DLL即是动态链接库(Dynamic L ...

随机推荐

  1. AppStoreID--安装URL--应用更新URL--应用评分URL

    #define AppStoreID @"987353224" //应用安装URL #define AppStoreInstallURLFormat @"https:// ...

  2. WPF 在绘图控件(Shape)中添加文字 [2018.7.15]

    原文:WPF 在绘图控件(Shape)中添加文字 [2018.7.15] Q:使用Shape的子类Ellipse画一个圆,如何在圆中添加文字? A:Shape类中不包含Text属性.可使用Shape类 ...

  3. hadoop实现购物商城推荐系统

    1,商城:是单商家,多买家的商城系统.数据库是mysql,语言java. 2,sqoop1.9.33:在mysql和hadoop中交换数据. 3,hadoop2.2.0:这里用于练习的是伪分布模式. ...

  4. spring boot 生成 war 包有一个war.original是什么?

    两个坑 .war.original 生成这种格式的文件,是因为在开启了二次打包.具体可以看这里 修改入口文件的配置 , 官方文档看这里 类似下面的代码,要继承SpringBootServletInit ...

  5. java中unicode utf-8以及汉字之间的转换工具类

    1.       汉字字符串与unicode之间的转换 1.1          stringToUnicode /** * 获取字符串的unicode编码 * 汉字"木"的Uni ...

  6. maven hadoop 3.x HADOOP_HOME and hadoop.home.dir are unset Not implemented by the WebHdfsFileSystem FileSystem implementation

    具体异常如下: 解决方案: 删除pom文件中的hadoop-core的依赖,原因:hadoop-core是1.x的产物,在2.x之后已经被hadoop-common取代,我配置的时候同时使用了这两个依 ...

  7. 用Github账号领Ripple币

    最近随着比特币在互联网上的流行,其他各种电子货币也都增加了曝光率. 昨晚在 v2ex 上看到有人发帖,用 20RMB 换取 2013 年 5 月 1 日前使用过的 github 账号得到的一个验证码. ...

  8. 关于hexo的SEO的好文章

    1.hexo高阶教程:想让你的博客被更多的人在搜索引擎中搜到吗? 2.Hexo Seo优化让你的博客在google搜索排名第一 3.hexo 博客 seo 优化 4.HEXO SEO 高级优化 5.H ...

  9. octopress第三方插件:博文同步工具syncPost

    为了增加外链等考虑,独立博客往往有将博文同步到其他博客社区的需求.自己人肉黏贴的方式笨拙.重复,对于程序猿而言,着实不可取. 我在 github 上找到了 syncPost 这个针对 octopres ...

  10. APP和服务端-架构设计(一)

    架构因人而异,不同的架构师大多会有不同的看法:架构也因项目而异,不同的项目需求不同,相应的架构也会不同.然而,有些东西还是通用的,是所有架构师都需要考虑的,也是所有项目都会有的需求,比如API如何设计 ...