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;

时间: 2024-12-09 01:35:40

DLL的相关理解的相关文章

sqlserver2012的审计功能的相关理解

1.sqlserver2012可以做实例的审计,以及数据库的审计,基本包括了所有的操作.可以符合我们的要求. 2.审计功能需要实例级别的配置数据库级别的配置,实例上建立“审核”,数据库上建立“数据库审核规范”,两者同时启动才可以进行审计. 3.审计文件存储对象只有三个,分别为“文件”,“安全日志”,“应用程序日志”.一般我们存储到另外服务器的文件上以保证安全. 4.数据库审核规范只能在读写库上启用,不能再只读库上启用. 5.由于上一条的原因,为了解决在alwayson的只读副本上面的审计,我们需

0220自学Linux_逻辑理解用户进程权限相关+理解文件内各字段(passwd,shadow,group)

11 内核是真正意义上的操作系统 库有动态库也有静态库,Linux的动态库是.so后缀的,也称为共享库 库是不能够独立运行的,只能被调用 Window的动态库是.dll后缀的 我们平时所谓的安装操作系统是装在硬盘上的 我们对于系统而言,最基本的程序就是shell,不然我们无法和系统交互,打开一个命令行窗口,就打开了一个shell进程 硬件之上的是内核,内核之上是进程 如果进程中他需要调用库,那首先启动这个程序,他的进程会把这个调用的库装入内存 ,而如果是共享库,其他进程调用的话就直接在内存调用这

动态链接库DLL的通俗理解

今天主要布置了一个任务要求写一个ocx控件,是用VC开发,虽然一直以来都是用VC写程序,但是使用到的功能仅仅是写一个小程编译运行,连链接都省去了,因为所有的功能都在一个文件里面就省去了链接这一步,所以写个这个什么ocx空间也只能是从零开始,主管给了几个DLL文件和说明文档,在电脑C盘里面看见过许多DLL为后缀的文件,但是不知道是什么东西也没有去了解过,现在要用了,这篇文章就给大家介绍一下我对DLL的了解,我是菜鸟准备入门,前辈勿喷.首先看一百度百科上怎么说: "DLL文件又称"应用程序

C++ 模板特化以及Typelist的相关理解

近日,在学习的过程中第一次接触到了Typelist的相关内容,比如Loki库有一本Modern C++ design的一本书,大概JD搜了一波没有译本,英文版600多R,瞬间从价值上看到了这本书的价值!!这是题外话.这本书十分经典.其内容对于一个C++新手来说需要时间来理解吸收.在这里记录一下自己的理解.日后发现错误会给予更正.如有有人碰巧看到了.欢迎指正. 参考了http://blog.csdn.net/gatieme/article/details/50953564 整篇内容分了三个部分:1

iOS中respondsToSelector与conformsToProtocol的相关理解和使用

respondsToSelector相关的方法:-(BOOL) isKindOfClass: classObj 用来判断是否是某个类或其子类的实例-(BOOL) isMemberOfClass: classObj 用来判断是否是某个类的实例-(BOOL) respondsToSelector: selector 用来判断是否有以某个名字命名的方法(被封装在一个selector的对象里传递)+(BOOL) instancesRespondToSelector: selector 用来判断实例是否有

iOS之即时通讯相关理解

Socket: 1>Socket又称"套接字” 2>网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket. 3>应用程序通常通过"套接字"向网络发出请求或者应答网络请求 网络通信的要素: 1>网络上的请求就是通过Socket来建立连接然后互相通信 2>IP地址(网络上主机设备的唯一标识) 3>端口号(定位程序) 4>用于标示进程的逻辑地址,不同进程的标示 5>有效端口:0~65535,其中0~

浮动相关理解,以及清除浮动的方法总结

1 <!DOCTYPE HTML> 2 <html> 3 <head> 4 <title>清除浮动</title> 5 <meta charset="utf-8"> 6 <style> 7 body { margin:0; padding:0; font:normal 12px/1.5em "Microsoft Yahei", Verdana, Arial, Helvetica, s

android dom解析相关理解

DOM解析XML DOM是Document Object Model的缩写,即文档对象模型.DOM解析器是通过将XML文档解析成树状模型并将其放入内存来完成解析工作的,而后对文档的操作都是在这个树状模型上完成.这个在内存中的文档是实际文档大小的几倍.好处是结构清晰,操作方便,坏处是耗费系统资源.要使用DOM方式来解析xml,需引入下面两个包: importjavax.xml.parsers.*;//包含DOM解析器和SAX解析器的具体实现 import org.w3c.dom.*;//定义了W3

javascript的类方法,对象属性,原型方法的相关理解

1.对象方法,属性:是属于对象实例层次上的方法,没创建一个实例,此实例便有了相应的对象方法,实例可以使用对象方法. eg:function People(name){ //对象属性,公有的(即每个实例都有一个此属性或方法) this.name = name; //对象方法,公有的 this.introduce = function(){ alert('my name = '+this.name); }: } 2.类方法,属性(静态方法,属性):作用在类层次的,不需要生成实例已经存在的属性,在内存