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。
在试验一种新的功能时,我一般会从最简单的代码开始。
- //dlltest.c
- int Double(int x)
- {
- return x * 2;
- }
下面的命令行将这个代码编译成 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
- //main.c
- #include <stdio.h>
- int Double(int x);
- int main(void)
- {
- printf("Hello :%d\n", Double(333));
- return 0;
- }
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。那么代码可以这么写:
- //m2.c
- define UNICODE 1
- #include <windows.h>
- #include <stdio.h>
- typedef int (*INT_FUNC)(int);
- int main(void)
- {
- INT_FUNC db;
- HINSTANCE hInstLibrary = LoadLibrary(L"dlltest.dll");
- printf("LoadLibrary\n");
- db = (INT_FUNC)GetProcAddress(hInstLibrary, "Double");
- printf("Hello :%d\n", db(333));
- FreeLibrary(hInstLibrary);
- return 0;
- }
编译的时候更不需要dlltest.lib 了,甚至都不需要 dlltest,dll。
gcc m2.c -o m2.exe
运行的结果也是正确的。
那么这个dll 可以被其他c编译器使用吗?利用VC 2010来测试表明,可以生成exe文件。如果是生成Debug模式的exe文件,执行是正常的。但是改为release模式后,每次运行都会报错。
用VS2010 的调试功能,看了看反汇编的结果。看似都是正常的。
- int _tmain(int argc, _TCHAR* argv[])
- {
- int a;
- a = Double(333);
- 01021000 push 14Dh
- 01021005 call _Double (1021024h)
- printf("Hello :%d\n", a);
- 0102100A push eax
- 0102100B push offset string "Hello :%d\n" (10220F4h)
- 01021010 call dword ptr [__imp__printf (10220A0h)]
- 01021016 add esp,0Ch
- getchar();
- 01021019 call dword ptr [__imp__getchar (102209Ch)]
- return 0;
- 0102101F xor eax,eax
- }
单步跟进_Double 函数后是这样的:
- _Double:
- 01021024 jmp dword ptr ds:[1020000h]
- 0102102A nop
- 0102102B nop
Jmp 语句后:
- 00905A4D ???
- 00905A4E ???
- 00905A4F ???
- 00905A50 ???
- 00905A51 ???
- 00905A52 ???
- 00905A53 ???
可是在Debug 模式下:
- _Double:
- 011B144C jmp dword ptr [__imp__Double (11B8340h)]
- 011B1452 nop
- 011B1453 nop
- 011B1454 int 3
- 011B1455 int 3
Jmp 语句后:
- 6C101560 push ebp
- 6C101561 mov ebp,esp
- 6C101563 mov eax,dword ptr [ebp+8]
- 6C101566 add eax,eax
- 6C101568 pop ebp
- 6C101569 ret
而从下图可以看出,dlltest.dll 被加载到 6C100000 是正确的。
没有想明白为什么会这样,看来还需要努力,到目前为止只成功了一小步。不过,如果是动态调用dll,却没有问题。
- #include <windows.h>
- typedef int (*INT_FUNC)(int);
- int _tmain(int argc, _TCHAR* argv[])
- {
- INT_FUNC db;
- HINSTANCE hInstLibrary = LoadLibrary(L"dlltest.dll");
- printf("LoadLibrary ");
- db = (INT_FUNC)GetProcAddress(hInstLibrary, "Double");
- printf("Hello :%d\n", db(333));
- FreeLibrary(hInstLibrary);
- getchar();
- return 0;
- }
这个代码用 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 函数调用约定需要修改程序代码。
- //dlltest.c
- int _stdcall Double(int x)
- {
- return x * 2;
- }
- //main.c
- #include <stdio.h>
- int _stdcall Double(int x);
- int main(void)
- {
- printf("Hello :%d\n", Double(333));
- return 0;
- }
编译命令是不变的。但是需要注意的是,这时生成的dll 文件中的函数名是有变化的。可以参看下图。原来是Double 现在变成了 Double@4,变成了这种类似 C++ 函数的名字了。但是这样并不影响使用。
网上关于生成和使用dll 的文章都会写到,生成dll 是函数声明需添加 __declspec(dllexport),而使用dll时函数声明要使用__declspec(dllimport)。大家都看到了,我前面的代码中这两个都没有用到。那么这两个声明有什么用呢。下面就做个测试。
首先在生成dll 的代码中增加:
- //dlltest.c
- int __declspec(dllexport) _stdcall Double(int x);
- int _stdcall Double(int x)
- {
- return x * 2;
- }
编译命令如下:
gcc dlltest.c -shared -o dlltest.dll -Wl,--output-def,dlltest.def,--out-implib,dlltest.a
M.c 文件不变:
- //m.c
- #include <stdio.h>
- int _stdcall Double(int x);
- int main(void)
- {
- printf("Hello :%d\n", Double(333));
- return 0;
- }
编译命令如下:
gcc m.c dlltest.a -o m2.exe
编译没有问题,执行也没有问题。
修改一下m.c 。
- //m.c
- #include <stdio.h>
- int __declspec(dllimport) _stdcall Double(int x);
- int main(void)
- {
- printf("Hello :%d\n", Double(333));
- return 0;
- }
编译命令如下:
Gcc m.c dlltest.a -o m2.exe
编译没有问题,执行也没有问题。
再修改一下main.c 。
- //main.c
- #include <stdio.h>
- int __declspec(dllexport) _stdcall Double(int x);
- int main(void)
- {
- printf("Hello :%d\n", Double(333));
- return 0;
- }
编译命令如下:
Gcc main.c dlltest.a -o m2.exe
编译没有问题,执行也没有问题。 这个实验说明__declspec(dllexport)对于函数声明其实是没什么作用的。我也比较过生成的代码的反汇编结果,也是没区别的。并不像有些人所说增加了__declspec(dllexport)之后生成的代码能够更精炼。当然,这也可能是现在编译器的优化能力越来越强的结果。早期编译器跟不上,可能还是有区别的。
但是__declspec(dllexport)对于输出变量是有影响的。看下面的测试代码:
- //dlltest.c
- int Double(int x);
- int xxx = 123;
- int Double(int x)
- {
- return x * 2;
- }
- //m.c
- #include <stdio.h>
- int Double(int x);
- extern int xxx;
- int main(void)
- {
- printf("Hello :%d\n", Double(333));
- printf("%d", xxx);
- return 0;
- }
编译:
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) ,结果就不一样了。
- //dlltest.c
- int Double(int x);
- int __declspec(dllexport) xxx = 123;
- int Double(int x)
- {
- return x * 2;
- }
- //m.c
- #include <stdio.h>
- int Double(int x);
- extern int xxx;
- int main(void)
- {
- printf("Hello :%d\n", Double(333));
- printf("%d", xxx);
- return 0;
- }
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)
- //m.c
- #include <stdio.h>
- int Double(int x);
- int __declspec(dllimport) xxx;
- int main(void)
- {
- printf("Hello :%d\n", Double(333));
- printf("%d", xxx);
- return 0;
- }
VS2010 中编译就可以通过。另外,再多说一句,我试验的结果表明,__declspec(dllimport) 与__declspec(dllexport) 对于编译来说似乎没有任何区别,字面上的区别完全是给程序员自己看的。
至此,MinGW gcc 生成 dll 的常见问题就都解决了。
http://blog.csdn.net/liyuanbhu/article/details/42612365
MinGW gcc 生成动态链接库 dll 的一些问题汇总(由浅入深,很详细)的更多相关文章
- dll = MinGW gcc 生成动态链接库 dll 的一些问题汇总
MinGW gcc 生成动态链接库 dll 的一些问题汇总 https://blog.csdn.net/liyuanbhu/article/details/42612365 网络上关于用 MinGW ...
- MinGW gcc 生成动态链接库 dll 的一些问题汇总 (补充)
我以前写过一个小短文,介绍MinGW gcc 生成动态链接库 dll 的一些问题.当时写的并不全面.近期又遇到写新的问题.这里记录一下,做个补充. 通常情况下,dll 中的函数假设採用 _stdcal ...
- GCC生成动态链接库(.so文件):-shared和-fPIC选项
Linux 下动态链接库(shared object file,共享对象文件)的文件后缀为.so,它是一种特殊的目标文件(object file),可以在程序运行时被加载(链接)进来.使用动态链接库的 ...
- 使用python创建生成动态链接库dll
如今,随着深度学习的发展,python已经成为了深度学习研究中第一语言.绝大部分的深度学习工具包都有python的版本,很多重要算法都有python版本的实现.为了将这些算法应用到具体工程中,这些工具 ...
- gcc 生成动态链接库
http://blog.csdn.net/ngvjai/article/details/8520840 Linux下文件的类型是不依赖于其后缀名的,但一般来讲: .o,是目标文件,相当于windows ...
- MinGW(GCC)编译DLL文件
这两天用CB(Code::Blocks)写个小程序,要编译出DLL供VB(6)使用.CB使用mingw-gcc作为编译器,在库文件的产出上跟VC.VS之类的IDE略有不同. 由于C语言的基础知识不是太 ...
- 引用MinGW生成的.dll.a后出现的问题
以前很少调用MinGW的运行时库,现在用到一个项目,用到了glib和gettext等. 遇到了一个问题,折腾了一个下午. gettext的运行时库之一是intl,MinGW只提供了.dll.a,于是参 ...
- 利用GCC编译器生成动态链接库和静态链接库
转载请标明:http://www.cnblogs.com/winifred-tang94/ 1.编译过程 gcc –fPIC –c xxx.c 其中-fPIC是通知gcc编译器产生位置独立的目标代码. ...
- 动态链接库DLL的创建生成及调用
一.背景 最近在做CANTOUSB底层驱动的调用,是调用别人已经封装好的库,看不到别人写的源程序.程序中调用的是隐式调用即 x.h+x.lib+x.dll,其中DLL即是动态链接库(Dynamic L ...
随机推荐
- javascript动态创建表格:新增、删除行和列
转载:http://www.cnblogs.com/pato/archive/2009/09/02/1559068.html 利用js来动态创建表格有两种格式,appendChild()和insert ...
- 学maven
跟着刚哥深入学maven 前言:目前所有的项目都在使用maven,可是一直没有时间去整理学习,这两天正好有时间,好好的整理一下. 一.为什么使用Maven这样的构建工具[why] ① 一个项目就是 ...
- 第二十一篇:基于WDM模型的AVStream驱动架构研究
基于WDM模型的AVStream驱动架构研 这篇论文2006年早就发表, 与当时开发这个驱动正好几乎相同的时间. 近期实际项目须要, 又回过头来将AVStre ...
- 大型项目linux自动化版本发布脚本(shell)之tomcat、nginx服务脚本
开发十年,就只剩下这套Java开发体系了 >>> 最近,又临近博主所负责的一个大型项目的发版了.之前有提到过,该项目涉及到30-40台服务器的发版.且项目客户规定发版需在晚上10 ...
- linux 时间同步ntp
配置前准备:关闭防火墙,配置好hosts,ssh免密登录 1.选定同步的标准,我是以hadoop002(设置为当前时间)作为同步标准,hadoop003(时间是2018年3月21,使用date -s进 ...
- JackSon fasterxml学习
概述 Jackson框架是基于Java平台的一套数据处理工具,被称为"最好的JavaJson解析器". Jackson框架包含了3个核心库:streaming,databind, ...
- WPF 获得触摸精度和触摸点
原文:WPF 获得触摸精度和触摸点 本文主要告诉大家如何获得所有的触摸设备的触摸精度和触摸点数. 需要通过反射的方法才可以拿到触摸的精度. 使用 Tablet.TabletDevices 可以获得所有 ...
- Matlab Tricks(二十四)—— title 置于图像之下(包括 subplots 的情形)
1. 使用 title 的 'position' 属性进行设置 plot(1:10, 1:10), title('y=x', 'position', [5.5, 0]) 2. 使用 xlabel pl ...
- 科学的解决Http Token拦截器TokenInterceptor实现
1.写在前面 在做项目的时候,有时对接口要求比较严谨.先介绍下情况. 我这边Http 方式采用的是 OKhttp+Retrofit 后台一共分为三种token,分别是实名token(accessTok ...
- hdu4614 二分法+段树
意甲冠军:给你1-n花瓶 .起初,所有的空,今天,有两种操作模式, 1:从花瓶a開始插入b朵花 假设不能插进去 输出字符串 否则输出最多插入的起点和终点: 2:把a-b的花瓶 ...