DLL的相关理解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DLL的相关理解相关的知识,希望对你有一定的参考价值。

 一种优雅的动态链接库DLL的使用

1. 什么是DLL(动态链接库)?

  动态链接库(DLL)是从C语言函数库和Pascal库单元的概念发展而来的。所有的C语言标准库函数都存放在某一函数库中。在链接应用程序的过程中,链接器从库文件中拷贝程序调用的函数代码,并把这些函数代码添加到可执行文件中。这种方法同只把函数储存在已编译的OBJ文件中相比更有利于代码的重用。但随着Windows这样的多任务环境的出现,函数库的方法显得过于累赘。如果为了完成屏幕输出、消息处理、内存管理、对话框等操作,每个程序都不得不拥有自己的函数,那么Windows程序将变得非常庞大。Windows的发展要求允许同时运行的几个程序共享一组函数的单一拷贝。动态链接库就是在这种情况下出现的。动态链接库不用重复编译或链接,一旦装入内存DLL函数可以被系统中的任何正在运行的应用程序软件所使用,而不必再将DLL函数的另一拷贝装入内存。

  DLL是一个包含可由多个程序同时使用的代码和数据的库。例如:在Windows操作系统中,Comdlg32 DLL执行与对话框有关的常见函数。

  因此,每个程序都可以使用该DLL中包含的功能来实现“打开”对话框。这有助于促进代码重用和内存的有效使用。这篇文章的目的就是让你一次性就能了解和掌握DLL。

2. 为什么要使用DLL(动态链接库)?

  代码复用是提高软件开发效率的重要途径

  一般而言,只要某部分代码具有通用性,就可以将它构造成相对独立的功能模块并在之后的项目中重复使用。

  比较常见的例子是各种应用程序框架,它们都以源代码的形式发布。由于这种复用是源代码级别的,源代码完全暴露给了程序员,因而称之为“白盒复用”。白盒复用有以下三个缺点:

1.暴露源代码,多份拷贝,造成存储浪费;
2.容易与程序员的本地代码发生命名冲突;
3.更新模块功能比较困难,不利于问题的模块化实现;

  为了弥补这些不足,就提出了“二进制级别”的代码复用了。使用二进制级别的代码复用一定程度上隐藏了源代码,对于“黑盒复用”的途径不只DLL一种,静态链接库,甚至更高级的COM组件都是。

  使用DLL主要有以下优点:

1.使用较少的资源;当多个程序使用同一函数库时,DLL可以减少在磁盘和物理内存中加载的代码的重复量。

  这不仅可以大大影响在前台运行的程序,而且可以大大影响其它在Windows操作系统上运行的程序;
2.推广模块式体系结构;
3.简化部署与安装。

  优雅创建DLL,与优雅使用DLL库 步骤

3.1 创建DLL

  用VS2013新建工程,

、  技术分享

  技术分享

  点击确定,新建工程完毕

  这个时候VS2013自动帮我们添加了dllmain文件,

  技术分享

 

  Windows在加载DLL时,需要一个入口函数,就像控制台程序需要main函数一样。有的时候,DLL并没有提供DllMain函数,应用程序也能成功引用DLL,这是因为Windows在找不到DllMain的时候,系统会从其它运行库中引入一个不做任何操作的默认DllMain函数版本,并不意味着DLL可以抛弃DllMain函数。(因此我们可以不使用该dllmain文件,或者删除即可根据编写规范,Windows必须查找并执行DLL里的DllMain函数作为加载DLL的依据,它使得DLL得以保留在内存里。这个函数并不属于导出函数,而是DLL的内部函数这就说明不能在客户端直接调用DllMain函数,DllMain函数是自动被调用的

  DllMain函数在DLL被加载和卸载时被调用,在单个线程启动和终止时,DllMain函数也被调用。参数ul_reason_for_call指明了调用DllMain的原因,有以下四种情况:

DLL_PROCESS_ATTACH:当一个DLL被首次载入进程地址空间时,系统会调用该DLL的DllMain函数,传递的ul_reason_for_call参数值为DLL_PROCESS_ATTACH。这种情况只有首次映射DLL时才发生;

DLL_THREAD_ATTACH:该通知告诉所有的DLL执行线程的初始化。当进程创建一个新的线程时,系统会查看进程地址空间中所有的DLL文件映射,之后用DLL_THREAD_ATTACH来调用DLL中的DllMain函数。要注意的是,系统不会为进程的主线程使用值DLL_THREAD_ATTACH来调用DLL中的DllMain函数;

DLL_PROCESS_DETACH:当DLL从进程的地址空间解除映射时,参数ul_reason_for_call参数值为DLL_PROCESS_DETACH。当DLL处理DLL_PROCESS_DETACH时,DLL应该处理与进程相关的清理操作。如果进程的终结是因为系统中有某个线程调用了TerminateProcess来终结的,那么系统就不会用DLL_PROCESS_DETACH来调用DLL中的DllMain函数来执行进程的清理工作。这样就会造成数据丢失;

DLL_THREAD_DETACH:该通知告诉所有的DLL执行线程的清理工作。注意的是如果线程的终结是使用TerminateThread来完成的,那么系统将不会使用值DLL_THREAD_DETACH来执行线程的清理工作,这也就是说可能会造成数据丢失,所以不要使用TerminateThread来终结线程

3.2 编写DLL的函数

  编写DLL时的函数与一般的函数方法基本一样。但要对库中的函数进行必要的声明,以说明哪些函数是可以导出的,哪些函数是不可以导出的。

  把DLL中的函数声明为导出函数的方法有两种:

一是使用关键字_declspec(dllexport)来声明。

二是在.def文件中声明。

一:使用关键字来声明创建

  (1)普通函数

 1 #ifndef _MYCODE_H_
 2 #define _MYCODE_H_
 3 #ifdef DLLDEMO1_EXPORTS
 4 #define EXPORTS_DEMO _declspec( dllexport )
 5 #else
 6 #define EXPORTS_DEMO _declspec(dllimport)
 7 #endif
 8      EXPORTS_DEMO int Add (int a , int b);
 9 #endif
10 
11 或者:
12 
13 #ifndef _MYCODE_H_
14 #define _MYCODE_H_
15 #ifdef DLLDEMO1_EXPORTS
16 #define EXPORTS_DEMO _declspec( dllexport )
17 #else
18 #define EXPORTS_DEMO _declspec(dllimport)
19 #endif
20 extern "C" EXPORTS_DEMO int Add (int a , int b);
21 #endif

  【1】这里采用宏定义的方法进行声明,这样就可以把头文件作为与 生成的DLL、lib 三者一起进行打包发送给使用者。使用者根据宏定义的使用,即可直接包含  

  【2】extern C

  为了使一个用C++语言编写的DLL函数可以在C语言编写的应用程序中使用,在关键字_declspec(dllexport)  之前要附加另一个关键字:extern “C”,以通知编译器采用C链接方式。

 为什么要使用extern “C”呢?C++之父在设计C++时,考虑到当时已经存在了大量的C代码,为了支持原来的C代码和已经写好的C库,需要在C++中尽可能的支持C,而extern “C”就是其中的一个策略。在声明函数时,注意到我也使用了extern “C”,这里要详细的说说extern “C”。

extern “C”包含两层含义,首先是它修饰的目标是”extern”的;其次,被它修饰的目标才是”C”的。先来说说extern;在C/C++中,extern用来表明函数和变量作用范围(可见性)的关键字,这个关键字告诉编译器,它申明的函数和变量可以在本模块或其它模块中使用。extern的作用总结起来就是以下几点:

1.在一个文件内:

  如果外部变量不在文件的开头定义,其有效范围只限定在从定义开始到文件的结束处。如果在定义前需要引用该变量,则要在引用之前用关键字”extern”对该变量做”外部变量声明”,表示该变量是一个已经定义的外部变量。有了这个声明,就可以从声明处起合理地使用该变量了。

#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
      extern int a;
      cout<<a<<endl;
}
int a = 100;

2.在多文件的程序中:

  如果多个文件都要使用同一个外部变量,不能在各个文件中各定义一个外部变量,否则会出现“重复定义”的错误

  正确的做法是在任意一个文件中定义外部变量,其它文件用extern对变量做“外部变量声明”

  在编译和链接时,系统会知道该变量是一个已经在别处定义的外部变量,并把另一文件中外部变量的作用域扩展到本文件,这样在本文件就可以合法地使用该外部变量了。写过MFC程序的人都知道,在在CXXXApp类的头文件中,就使用extern声明了一个该类的变量,而该变量的实际定义是在CXXXApp类的实现文件中完成的;

3. 外部函数:

  在定义函数时,如果在最左端加关键字extern,表示此函数是外部函数。C语言规定,如果在定义时省略extern,则隐含为外部函数。而内部函数必须在前面加static关键字。在需要调用此函数的文件中,用extern对函数作声明,表明该函数是在其它文件中定义的外部函数。

   综上,因为是对C++ 的类的dll导出,则可以采用下述形式,且可以用添加extern c 的形式:

 1 #ifndef AMF_H
 2 #define AMF_H
 3 
 4 #if defined(_WIN32) || defined(_WIN64) /*Windows*/
 5     #ifdef DLL_EXPORT
 6         #define LIBSPEC __declspec(dllexport)
 7     #elif defined(DLL_IMPORT)
 8         #define LIBSPEC __declspec(dllimport)
 9     #else
10         #define LIBSPEC //什么都没有定义,直接使用源码
11     #endif
12 #else /*non-windows*/
13     #define LIBSPEC /*TODO: Allow this header file to generate (and be distributed) with non-windows shared objects*/
14 #endif
15 
16 
17 class LIBSPEC Amf /*LIBSPEC macro allows windows users the option of using this class in DLL form */
18 {
19 public:
20     Amf();
21     ~Amf();
22     Amf(const Amf& In);
23     Amf& operator=(const Amf& In);
24 
25 
26     //Amf I/O
27     bool Save(std::string AmfFilePath, bool Compressed = true); 
28     bool Load(std::string AmfFilePath, bool StrictLoad = true); 
29 }
30 
31 #undef LIBSPEC
32 #endif //AMF_WIN_H

  把上述作为 CXXXXX.h 的头文件内容,因为该文件最终要副带给使用者因此,不合适在临面进行函数实现。

  而函数或者类的实现作为CXXXXX.cpp 源文件中。

1 extern "C" __declspec(dllexport) void SayHello()
2 
3 {
4 
5     ::MessageBoxW(NULL, L"Hello", L"fangyukuan", MB_OK);
6 
7 }

或者

  普通的类的实现源文件相同。

#include “CXXXX.h”

//类的实现,且不需要额外修饰
CXXX::CXXX()
{
    
}

CXXX::XXXX()
{

}

编译即可生成相相应的DLL:

  一般生成四个文件,其中最重要的额DLL是动态链接库, lib是函数接口的映射,以及 包含导出生命的头文件.h

  技术分享

4 使用DLL

   基本上要把三个文件进行使用: 

  (1)XXX.dll   文件,放在程序生成exe的目录下面;

  (2)XXX.lib  文件,可以像在项目中进行加载,也可以放在程序使用的代码中加载。

  放在程序中加载:告诉连接器,lib文件的位置,以及需要连接加载的lib名称

  技术分享

  技术分享

  放在代码中加载:

 1 #include <windows.h>
 2 #include <iostream>
 3 //(1)添加dll的头文件
 4 #include "..\\\\DLLDemo1\\\\CXXX.h"
 5 using namespace std;
 6 //(2)加载lib文件
 7 #pragma comment(lib, "..\\\\debug\\\\DLLDemo1.lib")
 8 // (3)dll放在 exe目录即可
 9 int main(int argc, char *argv[])
10 {
11       cout<<Add(2, 3)<<endl;
12       return 0;
13 }

   在使用上,一般没什么差异,直接利用include的头文件中的类型进行使用即可。

 

5. 使用VS6中的工具进行检测

  depend.exe 可以用来打开dll文件,并查看dll的依赖文件,以及dll中的导出的函数接口。

  技术分享

  注意:如果没有使用extern c声明的导出接口,其depends开发的效果如下:

  即名字为 [email protected]@[email protected] 

   技术分享

  如果用extern C声明, 可函数名称就正常了。

   技术分享

  不过,一般如果不采用直接对单个导出函数进行单独使用的方式,上述两种差异不大。

  为了简洁,且一般开发的C++ 的dll ,如果包含类,则没必要使用 extern c 因为,C是没办法调用C++ 的dll 的。

 

 

 

 

 

endl;





以上是关于DLL的相关理解的主要内容,如果未能解决你的问题,请参考以下文章

给定一个文档,选择一个相关的片段

金蝶handler中 collection 代码片段理解

调用模板化成员函数:帮助我理解另一个 *** 帖子中的代码片段

如何理解这段代码片段中的两对括号?

链接器链接过程及相关概念解析

无法找到指定dll库文件skinH_EL.dll中的输出命令skinH_Attach_EX_