动态链接库,即dll -- dynamic linkable library
一般来说,dll是一种文件,其扩展名为.dll。它是由全局数据,服务函数和资源组成;在运行的时候被加载到进程的虚拟空间中。dll中包含了各种导出函数,用于向外界提供服务,dll可以有自己的数据段,但是没有自己的堆栈,它的吏用与调用它的程式有相同的堆栈模式。在win32环境中,每一个进程都复制自己的读写全局变量,如果要想与其它进程共享内存,必须在声明一个共享数据段!
dll也是一种可执行文件,只不过它不能像exe文件那样直接执行,而是做为其它可执行文件的共享函数库!使用dll的应用程序可以调用dll中导出函数(imported function),不过应用程序并不包含这些函数的可执行代码,而是它们经过编译后独立的保存在dll中!这和以前的静态链接不同,使用静态链接库(static link library)的应用程序从库中得到所引用的函数的可执行代码,然后放在自身的可执行文件中,这就可使其size变大,应用程序运行不在需要静态函数库的支持!
而使用dll的应用程序只包含用于dll中定位所引用的函数信息,没有函数的实现,其size相对静态要小的多,但是其运行要从dll中获得函数的实现代码,要dll的支持!
dll的优点:
1. dll提供一种共享数据和代码的途径,由于多个应用程序共享同一个dll中的函数,可以缩小应用程式的可执行文件的大小,使的dll可以显著的节省磁盘空间。
2. dll是独立于可执行文件的,如果要向dll中增加新的函数和功能,只要原有函数的参数和返回值不变,所有使用这个dll的应用程序升级后都不要重新编译。
3. 标准的dll可以被其它语言来使用!
dll有多种类型,非 MFC dll,静态链接到MFC的常规dll,动态链接到MFC的常规dll. MFC 扩展dll。dll文件包括一个导出表,导出表中给出可从dll中导出函数的名字,dll中定义了两种函数,导出函数(export function)和内部函数(internal function),其中导出函数可以被其它的模块调用,而内部函数只能在内部使用。dll结构可以用VC++工具dumpbin和depends查看!
编写dll的主要步骤
我们在用C++定制dll文件时,要编写包含导出函数的模块定义文件.def和实现导出函数功能的C++文件。
1. 模块定义文件(.def)是由一个或者多个用于描述dll属性的语句块组成的文本文件,每个.def文件必须包含以下模块定义语句。
第一个语句必须是LIBRARY语句,其指出dll的名字。
第二个EXPORTS语句列出被导出函数的名字,";"对一行进行注释!
LIBRARY mydll
EXPORTS
ulDataInDll DATA
myfunction @number
其中@number指出导出函数的顺序值。
2. 实现文件,在入口表函数的.cpp文件中,包含dll入口点处理的API函数和导出函数的代码。入口函数LibMain()就像C中的main(),windows每次加载dll时都要执行此函数。
dll中函数导出的方法有两种,1. 在创建dll时使用.DEF文件。2. 在定义函数时使用关键字_declspec(dllexport)。仅仅知道导出数的名称并不足以从dll中导出函数。若在应用程序中使用显式链接,至少还要知道导出函数的返回值及参数。若用隐函链接,必须括有导出函数的定义头文件(.h) 和 引入库文件(import library, .LIB文件)。
如。1使用DEF文件导出DLL中的函数。
prog.DEF文件定义一个函数。
LIBRARY mydll
EXPORTS
ShowMsgBox @1
头文件showmsgbox.h
#include <windows>
extern "C"
{
int ShowMsgBox(LPCTSTR lpText = 'example', LPCTSTR lpCaption = 'EXAMPLE');
}
实现文件showmsgbox.cpp
#include "showmsgbox.h"
int ShowMsgBox(LPCTSTR lpText = 'example', LPCTSTR lpCaption = 'EXAMPLE')
{
return MessageBox(NULL, lpText, lpCaption);
}
然后编译后生成showmsgbox.lib, showmsgbox.dll.
2. 使用_declspec(dllexport)导出dll中的函数,此时不用DEF文件。
头文件showmsgbox.h
#include <windows>
extern "C"
{
_declspec(dllexport) int ShowMsgBox(LPCTSTR lpText = 'example', LPCTSTR lpCaption = 'EXAMPLE');
}
.cpp文件还是上面那个,然后编译后生成showmsgbox.lib, showmsgbox.dll.
dll加载与调用
dll加载顺序,1. 系统dll,如kennel32.dll, user32.dll 2. 工作盘当前目录 3. windows系统目录,用GetsystemDirectory()可以获得。4. windows所在目录。
dll有两种调用方法,静态调用,动态调用。静态调用是编译系统完成加载dll和应用程序结束时卸载dll,这种方法简单但不灵活。动态调用方式使用API加载卸载dll。
隐含链接,显式链接
隐含链接要dll文件外还要一个包含导出函数或C++类头文件和相应的LIB
showmsgbox.dll对应的头文件showmsgbox.h
#include <windows.h>
extern "C"
{
_declspec(dllimport) int ShowMsgBox(LPCTSTR lpText = 'example', LPCTSTR lpCaption = 'EXAMPLE');
}
使不使用_declspec(dllimport)都可以,但是使用它会产生更高效的代码。
dlltest.cpp
#include "showmsgbox.h"
int _stdcall winmain(......)
{
return ShowMsgBox();
}
然后把showmsgbox.dll文件放到工程的debug\下,同时把showmsgbox.LIB放到链接器的命令行中。就OK了!!
显式链接
用API加载dll,LoadLibrary() --> GetProcAddress() --> FreeLibrary().
dlltest.cpp
#include <windows>
typedef (callback *DLLFUNC)(LPCTSTR lpText = 'example', LPCTSTR lpCaption = 'EXAMPLE');
int _stdcall winmain(......)
{
HINSTANCE hDll; //DLL句柄
DLLFUNC ShowMsgBox; //函数指针
hDll = LoadLibrary("mydll');
if (hDLL != NULL)
{
ShowMsgBox = (DLLFUNC)GetProcAddress(hDll, "mydll");
}
FreeLibrary(hDLL);
}
OK!
1、静态调用方式:由编译系统完成对DLL的加载和应用程序结束时DLL卸载的编码(如还有其它程序使用该DLL,则Windows对DLL的应用记录减1,直到所有相关程序都结束对该DLL的使用时才释放它),简单实用,但不够灵活,只能满足一般要求。
隐式的调用:需要把产生动态连接库时产生的.LIB文件加入到应用程序的工程中,想使用DLL中的函数时,只须说明一下。隐式调用不需要调用LoadLibrary()和FreeLibrary()。程序员在建立一个DLL文件时,链接程序会自动生成一个与之对应的LIB导入文件。该文件包含了每一个DLL导出函数的符号名和可选的标识号,但是并不含有实际的代码。LIB文件作为DLL的替代文件被编译到应用程序项目中。当程序员通过静态链接方式编译生成应用程序时,应用程序中的调用函数与LIB文件中导出符号相匹配,这些符号或标识号进入到生成的EXE文件中。LIB文件中也包含了对应的DLL文件名(但不是完全的路径名),链接程序将其存储在EXE文件内部。当应用程序运行过程中需要加载DLL文件时,Windows根据这些信息发现并加载DLL,然后通过符号名或标识号实现对DLL函数的动态链接。所有被应用程序调用的DLL文件都会在应用程序EXE文件加载时被加载在到内存中。可执行程序链接到一个包含DLL输出函数信息的输入库文件(.LIB文件)。操作系统在加载使用可执行程序时加载DLL。可执行程序直接通过函数名调用DLL的输出函数,调用方法和程序内部其他的函数是一样的。
2、动态调用方式:是由编程者用API函数加载和卸载DLL来达到调用DLL的目的,使用上较复杂,但能更加有效地使用内存,是编制大型应用程序时的重要方式。
显式的调用:是指在应用程序中用LoadLibrary或MFC提供的AfxLoadLibrary显式的将自己所做的动态连接库调进来,动态连接库的文件名即是上面两个函数的参数,再用GetProcAddress()获取想要引入的函数。自此,你就可以象使用如同本应用程序自定义的函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的AfxFreeLibrary释放动态连接库。直接调用Win32 的LoadLibary函数,并指定DLL的路径作为参数。LoadLibary返回HINSTANCE参数,应用程序在调用GetProcAddress函数时使用这一参数。GetProcAddress函数将符号名或标识号转换为DLL内部的地址。程序员可以决定DLL文件何时加载或不加载,显式链接在运行时决定加载哪个DLL文件。使用DLL的程序在使用之前必须加载(LoadLibrary)加载DLL从而得到一个DLL模块的句柄,然后调用GetProcAddress函数得到输出函数的指针,在退出之前必须卸载DLL(FreeLibrary)。
Windows将遵循下面的搜索顺序来定位DLL:
1.包含EXE文件的目录,
2.进程的当前工作目录,
3.Windows系统目录,
4.Windows目录,
5.列在Path环境变量中的一系列目录。