DLL的创建和使用
2013-04-18 21:19
218 查看
DLL的创建和使用
参考:http://blog.csdn.net/btwsmile/article/details/6676802
http://blog.csdn.net/hjsunj/article/details/2047376
http://blog.csdn.net/wujian53/article/details/706975
一、为什么需要dll
代码复用是提高软件开发效率的重要途径。一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块
并在之后的项目中重复使用。比较常见的例子是各种应用程序框架,如ATL、MFC等,它们都以源代码的形式发布。由于
这种复用是“源码级别”的,源代码完全暴露给了程序员,因而称之为“白盒复用”。
“白盒复用”的缺点比较多,总结起来有4点。
1.暴露了源代码;
2.容易与程序员的“普通”代码发生命名冲突;
3.多份拷贝,造成存储浪费;
4.更新功能模块比较困难。
实际上,以上4点概括起来就是“暴露的源代码”造成“代码严重耦合”。为了弥补这些不足,提出了“二进制级别”的代码复用。
使用二进制级别的代码复用一定程度上隐藏了源代码,对于缓解代码耦合现象起到了一定的作用。这样的复用被称为“黑盒
复用”。在Windows操作系统中有两种可执行文件,其后缀名分别为.exe和.dll。它们的区别在于,.exe文件可被独立的装载于
内存中运行;.dll文件却不能,它只能被其它进程调用。然而无论什么格式,它们都是二进制文件。上面说到的“二进制级别”
的代码复用,可以使用.dll来实现。与白盒复用相比,.dll很大程度上弥补了上述4大缺陷。.dll是二进制文件,因此隐藏了源代
码;如果采用“显式调用”(后边将会提到),一般不会发生命名冲突;由于.dll是动态链接到应用程序中去的,它并不会在链
接生成程序时被原原本本拷贝进去;.dll文件相对独立的存在,因此更新功能模块是可行的。
说明:实现“黑盒复用”的途径不只dll一种,静态链接库甚至更高级的COM组件都是。本文只对dll进行讨论
二、静态库与DLL的不同之处
可执行文件的生成(Link期):前者很慢(因为要将库中的所有符号定义Link到EXE文件中),而后者很快(因为后者
被Link的引入库文件无符号定义)可执行文件的大小:前者很大,后者很小(加上DLL的大小就和前者差不多了)
可执行文件的运行速度:前者快(直接在EXE模块的内存中查找符号),后者慢(需要在DLL模块的内存中查找,在另
一个模块 的内存中查找自然较慢)
可共享性:前者不可共享,也就是说如果两个EXE使用了同一个静态库,那么实际在内存中存在此库的两份拷贝,而后
者是可共享的。
可升级性:前者不可升级(因为静态库符号已经编入EXE中,要升级则EXE也需要重新编译),后者可以升级(只要接
口不变, DLL即可被升级为不同的实现)
综合以上,选择静态库还是DLL
1. 静态库适于稳定的代码,而动态库则适于经常更改代码(当然接口要保持不变),当DLL更改(仅实现部分)后,
用户不需要 重编工程,只需要使用新的Dll即可。
2. 由于静态库很吃可执行文件的生成(Link期)时间,所以如果对可执行文件的Link时间比较敏感,那么就用DLL。
三、使用DLL
1. 显式调用(也叫动态调用)
显示调用使用API函数LoadLibrary或者MFC提供的AfxLoadLibrary将DLL加载到内存,再用GetProcAddress()在内存中获取引入函数地址,然后
你就可以象使用本地函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的AfxLoadLibrary释放DLL。
下面是个显示调用的例子,假定你已经有一个Test.dll,并且DLL中有个函数为void Test();
注意
1). 显示调用使用GetProcAddress,所以只能加载函数,无法加载变量和类。
2). 此外GetProcAddress是直接在.dll文件中寻找同名函数,如果DLL中的Test函数是个C++函数,那么由于在.dll文件中的
实际文件名会被修饰(具体被修饰的规则可参考函数调用约定详解或者使用VC自带的Depends.exe查看),所以直接找Test
是找不到的,这时必须把函数名修改为正确的被修饰后的名称,下面是一种可能(此函数在DLL中的调用约定为__cdecl):
const char* funcName = "?Test@@YAXXZ";
2. 隐式调用(也叫静态调用)
隐式调用必须提供DLL的头文件和引入库(可以看作轻量级的静态库(没有符号定义,但是说明了符号处于哪个DLL中))。
有了头文件和引入库,DLL的使用就跟普通静态库的使用没啥区别,只除了DLL要和EXE一起发布。
显示调用与隐式调用的优缺点
显示调用使用复杂,但能更加有效地使用内存,因为DLL是在EXE运行时(run time)加载,必须由用户使用LoadLibrary和
FreeLibrary来控制DLL从内存加载或卸载的时机。此外还可以加载其他语言编写的DLL函数。
静态调用使用简单,但不能控制DLL加载时机,EXE加载到内存同时自动加载DLL到内存,EXE退出时DLL也被卸载
四、vs2010中创建DLL
1、File > New > Project > Win32 > Win32 project,命名为MyDLL.
2、win32应用程序向导>下一步>勾选"DLL(D)"和"导出符号(X)",完成.
(勾选"导出符号(X)"后,vs2010会自动生成MyDLL.h的头文件,主要包含如下内容:
#ifdef MyDLL_EXPORTS
#define MyDLL_API __declspec(dllexport)
#else
#define MyDLL_API __declspec(dllimport)
#endif
)
项目结构图:
![](http://img.my.csdn.net/uploads/201304/18/1366287543_3630.jpg)
3、在MyDLL.h中完成函数声明,在MyDLL.cpp中完成函数实现。此处写一个函数int add(int a,int b)为例。对DLL外部可见的函数,即DLL提供给外部调用的函数,
需要在函数头部加上MyDLL_API(在MyDLL.h中宏定义好的),表示这个函数是可以被外部调用的。
改好后的MyDLL.h如下:
// 下列 ifdef 块是创建使从 DLL 导出更简单的
// 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 MYDLL_EXPORTS
// 符号编译的。在使用此 DLL 的
// 任何其他项目上不应定义此符号。这样,源文件中包含此文件的任何其他项目都会将
// MYDLL_API 函数视为是从 DLL 导入的,而此 DLL 则将用此宏定义的
// 符号视为是被导出的。
#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif
MYDLL_API int add(int,int);//函数声明
改好后 MyDLL.cpp如下:
4、生成项目,在 项目目录\Debug文件下就生成了MyDLL.dll文件
i、如果是C++函数
此时使用MyDLL.dll,以add的名称来访问int add(int a,int b)函数时,很可能失败。原因是源码在编译生成二进制的DLL时,函数是个C++函数,
那么由于在.dll文件中的实际函数名会被修饰,同时包含函数名和参数(以便于重载的实现)(具体被修饰的规则可参考函数调用约定详解或者
使用VC自带的Depends.exe查看),所以直接找Test是找不到的,这时必须把函数名修改为正确的被修饰后的名称来访问,下面是一种可能的修饰后的名称):
"?add@@YAHHH@Z"
--使用VS2010附带工具dumpbin,查看CreateDLL.dll的导出函数名,结果如图所示:
![](http://img.my.csdn.net/uploads/201304/18/1366287730_6104.jpg)
ii、如果是C函数,可以正常调用
虽然可以使用上面查到的DLL中的名字来调用,但很麻烦。
简单的方法有:
a、运用模块定义文件.def:
在项目中添加MyDLL.def文件:
![](http://img.my.csdn.net/uploads/201304/18/1366287804_1649.jpg)
内容为:
LIBRARY MyDLL
EXPORTS
add
LIBRARY是模块定义文件必须的一部分,它告诉链接器(linker)如何命名你的DLL。EXPORTS也是模块定义文件必须的一部分,
这部分使得该函 数可以被其它应用程序访问到并且它创建一个导入库。当你生成这个项目时,不仅是一个.dll文件被创建,而且一
个文件扩展名为.lib的导出 库也会被创建。EXPORTS后面列出要导出函数的名称。可以在.def文件中的导出函数名后加@n,表示
要导出函数的序号为n(对于VB、PB、Delphi用户,通常使用按名称进行调用的方式,这个数关系不大,但是对于使用.lib链接的
VC程序来说,不是按名称进行调用,而是按照这个数进行调用的,所以最好给出。)
从新生成MyDLL.dll文件,查看:
![](http://img.my.csdn.net/uploads/201304/18/1366287908_9793.jpg)
此时就可以直接用源码中的名称调用这个函数了。
b、运用extern "C"修饰函数add(). (extern "C" 作用是,让被其修饰的函数在编译好的文件(如.dll和.lib文件)中按照C语言的方式命名)
参考:http://blog.csdn.net/btwsmile/article/details/6676802
http://blog.csdn.net/hjsunj/article/details/2047376
http://blog.csdn.net/wujian53/article/details/706975
一、为什么需要dll
代码复用是提高软件开发效率的重要途径。一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块
并在之后的项目中重复使用。比较常见的例子是各种应用程序框架,如ATL、MFC等,它们都以源代码的形式发布。由于
这种复用是“源码级别”的,源代码完全暴露给了程序员,因而称之为“白盒复用”。
“白盒复用”的缺点比较多,总结起来有4点。
1.暴露了源代码;
2.容易与程序员的“普通”代码发生命名冲突;
3.多份拷贝,造成存储浪费;
4.更新功能模块比较困难。
实际上,以上4点概括起来就是“暴露的源代码”造成“代码严重耦合”。为了弥补这些不足,提出了“二进制级别”的代码复用。
使用二进制级别的代码复用一定程度上隐藏了源代码,对于缓解代码耦合现象起到了一定的作用。这样的复用被称为“黑盒
复用”。在Windows操作系统中有两种可执行文件,其后缀名分别为.exe和.dll。它们的区别在于,.exe文件可被独立的装载于
内存中运行;.dll文件却不能,它只能被其它进程调用。然而无论什么格式,它们都是二进制文件。上面说到的“二进制级别”
的代码复用,可以使用.dll来实现。与白盒复用相比,.dll很大程度上弥补了上述4大缺陷。.dll是二进制文件,因此隐藏了源代
码;如果采用“显式调用”(后边将会提到),一般不会发生命名冲突;由于.dll是动态链接到应用程序中去的,它并不会在链
接生成程序时被原原本本拷贝进去;.dll文件相对独立的存在,因此更新功能模块是可行的。
说明:实现“黑盒复用”的途径不只dll一种,静态链接库甚至更高级的COM组件都是。本文只对dll进行讨论
二、静态库与DLL的不同之处
可执行文件的生成(Link期):前者很慢(因为要将库中的所有符号定义Link到EXE文件中),而后者很快(因为后者
被Link的引入库文件无符号定义)可执行文件的大小:前者很大,后者很小(加上DLL的大小就和前者差不多了)
可执行文件的运行速度:前者快(直接在EXE模块的内存中查找符号),后者慢(需要在DLL模块的内存中查找,在另
一个模块 的内存中查找自然较慢)
可共享性:前者不可共享,也就是说如果两个EXE使用了同一个静态库,那么实际在内存中存在此库的两份拷贝,而后
者是可共享的。
可升级性:前者不可升级(因为静态库符号已经编入EXE中,要升级则EXE也需要重新编译),后者可以升级(只要接
口不变, DLL即可被升级为不同的实现)
综合以上,选择静态库还是DLL
1. 静态库适于稳定的代码,而动态库则适于经常更改代码(当然接口要保持不变),当DLL更改(仅实现部分)后,
用户不需要 重编工程,只需要使用新的Dll即可。
2. 由于静态库很吃可执行文件的生成(Link期)时间,所以如果对可执行文件的Link时间比较敏感,那么就用DLL。
三、使用DLL
1. 显式调用(也叫动态调用)
显示调用使用API函数LoadLibrary或者MFC提供的AfxLoadLibrary将DLL加载到内存,再用GetProcAddress()在内存中获取引入函数地址,然后
你就可以象使用本地函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的AfxLoadLibrary释放DLL。
下面是个显示调用的例子,假定你已经有一个Test.dll,并且DLL中有个函数为void Test();
#include < iostream > using namespace std; typedef void(*TEST )(); int main( char argc, char* argv[] ) { const char* dllName = "Test.dll"; const char* funcName = "Test"; HMODULE hDLL = LoadLibrary( dllName ); if ( hDLL != NULL ) { TEST func = TEST( GetProcAddress( hDLL, funcName ) ); if ( func != NULL ) { func(); } else { cout << "Unable to find function /'" << funcName << "/' !" << endl; } FreeLibrary( hDLL ); } else { cout << "Unable to load DLL /'" << dllName << "/' !" << endl; } return 0; }
注意
1). 显示调用使用GetProcAddress,所以只能加载函数,无法加载变量和类。
2). 此外GetProcAddress是直接在.dll文件中寻找同名函数,如果DLL中的Test函数是个C++函数,那么由于在.dll文件中的
实际文件名会被修饰(具体被修饰的规则可参考函数调用约定详解或者使用VC自带的Depends.exe查看),所以直接找Test
是找不到的,这时必须把函数名修改为正确的被修饰后的名称,下面是一种可能(此函数在DLL中的调用约定为__cdecl):
const char* funcName = "?Test@@YAXXZ";
2. 隐式调用(也叫静态调用)
隐式调用必须提供DLL的头文件和引入库(可以看作轻量级的静态库(没有符号定义,但是说明了符号处于哪个DLL中))。
有了头文件和引入库,DLL的使用就跟普通静态库的使用没啥区别,只除了DLL要和EXE一起发布。
显示调用与隐式调用的优缺点
显示调用使用复杂,但能更加有效地使用内存,因为DLL是在EXE运行时(run time)加载,必须由用户使用LoadLibrary和
FreeLibrary来控制DLL从内存加载或卸载的时机。此外还可以加载其他语言编写的DLL函数。
静态调用使用简单,但不能控制DLL加载时机,EXE加载到内存同时自动加载DLL到内存,EXE退出时DLL也被卸载
四、vs2010中创建DLL
1、File > New > Project > Win32 > Win32 project,命名为MyDLL.
2、win32应用程序向导>下一步>勾选"DLL(D)"和"导出符号(X)",完成.
(勾选"导出符号(X)"后,vs2010会自动生成MyDLL.h的头文件,主要包含如下内容:
#ifdef MyDLL_EXPORTS
#define MyDLL_API __declspec(dllexport)
#else
#define MyDLL_API __declspec(dllimport)
#endif
)
项目结构图:
![](http://img.my.csdn.net/uploads/201304/18/1366287543_3630.jpg)
3、在MyDLL.h中完成函数声明,在MyDLL.cpp中完成函数实现。此处写一个函数int add(int a,int b)为例。对DLL外部可见的函数,即DLL提供给外部调用的函数,
需要在函数头部加上MyDLL_API(在MyDLL.h中宏定义好的),表示这个函数是可以被外部调用的。
改好后的MyDLL.h如下:
// 下列 ifdef 块是创建使从 DLL 导出更简单的
// 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 MYDLL_EXPORTS
// 符号编译的。在使用此 DLL 的
// 任何其他项目上不应定义此符号。这样,源文件中包含此文件的任何其他项目都会将
// MYDLL_API 函数视为是从 DLL 导入的,而此 DLL 则将用此宏定义的
// 符号视为是被导出的。
#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif
MYDLL_API int add(int,int);//函数声明
改好后 MyDLL.cpp如下:
// MyDLL.cpp : 定义 DLL 应用程序的导出函数。 // #include "stdafx.h" #include "MyDLL.h" // 这是导出函数的一个示例。 MYDLL_API int add(int a,int b) { return a+b; }
4、生成项目,在 项目目录\Debug文件下就生成了MyDLL.dll文件
i、如果是C++函数
此时使用MyDLL.dll,以add的名称来访问int add(int a,int b)函数时,很可能失败。原因是源码在编译生成二进制的DLL时,函数是个C++函数,
那么由于在.dll文件中的实际函数名会被修饰,同时包含函数名和参数(以便于重载的实现)(具体被修饰的规则可参考函数调用约定详解或者
使用VC自带的Depends.exe查看),所以直接找Test是找不到的,这时必须把函数名修改为正确的被修饰后的名称来访问,下面是一种可能的修饰后的名称):
"?add@@YAHHH@Z"
--使用VS2010附带工具dumpbin,查看CreateDLL.dll的导出函数名,结果如图所示:
![](http://img.my.csdn.net/uploads/201304/18/1366287730_6104.jpg)
ii、如果是C函数,可以正常调用
虽然可以使用上面查到的DLL中的名字来调用,但很麻烦。
简单的方法有:
a、运用模块定义文件.def:
在项目中添加MyDLL.def文件:
![](http://img.my.csdn.net/uploads/201304/18/1366287804_1649.jpg)
内容为:
LIBRARY MyDLL
EXPORTS
add
LIBRARY是模块定义文件必须的一部分,它告诉链接器(linker)如何命名你的DLL。EXPORTS也是模块定义文件必须的一部分,
这部分使得该函 数可以被其它应用程序访问到并且它创建一个导入库。当你生成这个项目时,不仅是一个.dll文件被创建,而且一
个文件扩展名为.lib的导出 库也会被创建。EXPORTS后面列出要导出函数的名称。可以在.def文件中的导出函数名后加@n,表示
要导出函数的序号为n(对于VB、PB、Delphi用户,通常使用按名称进行调用的方式,这个数关系不大,但是对于使用.lib链接的
VC程序来说,不是按名称进行调用,而是按照这个数进行调用的,所以最好给出。)
从新生成MyDLL.dll文件,查看:
![](http://img.my.csdn.net/uploads/201304/18/1366287908_9793.jpg)
此时就可以直接用源码中的名称调用这个函数了。
b、运用extern "C"修饰函数add(). (extern "C" 作用是,让被其修饰的函数在编译好的文件(如.dll和.lib文件)中按照C语言的方式命名)
相关文章推荐
- 创建一个动态链接库 (DLL),使用VS2010
- 调用dll给winform程序创建所有用户均可使用的快捷方式
- dll的创建和使用
- vs创建dll并使用
- 创建及调用基于QT5 QML的 DLL(举例QML中使用QZXing识别二维码)
- 创建一个动态链接库 (DLL),使用VS2010
- vs2010 如何让创建和使用动态链接库(dll)
- 纯资源(.rc)DLL创建与使用
- 创建和使用dll最常用方式
- VS中创建dll项目,使用dll文件
- Lib和Dll工程创建和使用
- vs2010创建和使用动态链接库(dll)
- 不使用DLL创建全局系统钩子
- simplest_dll 最简dll的创建与隐式调用(显式调用太麻烦,个人不建议使用)
- 基于VS2008使用def文件创建DLL
- VS2012 创建和使用DLL
- VS2015用C++创建动态库DLL及使用
- 在VS2010平台上创建并使用dll
- vs2010创建和使用动态链接库(dll)
- Codeblocks上dll的创建和使用