引言
动态链接库(DLL)是WINDOWS平台的基础,Windows API 中的所有函数都包含在D L L中。有3个最重要的D L L是K e r n e l 3 2 . d l l,它包含用于管理内存、进程和线程的各个函数; U s e r 3 2 . d l l,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数; G D I 3 2 . d l l,它包含用于画图和显示文本的各个函数。
它有以下几个优点:
→节约内存;
→使应用程序“变瘦”;
→可单独修改动态链接库而不必与应用程序重新链接;
→可方便实现多语言联合编程(比如用VC++写个dll,然后在VB中调用);
→可将资源打包;
→可在应用程序间共享内存
→可以用于一些特殊的目的。例如安装钩子函数
关于扩展名
一般来说,DLL是一种磁盘文件,以.dll、.DRV、.FON、.SYS和许多以.EXE为扩展名的系统文件都可以是DLL , 但只有扩展名为dll的动态链接库才能被Windows自动载入。如果使用其它扩展名的动态链接库,则调用动态链接库的程序中必须使用LoadLibrary或LoadLibraryEx载入动态链接库模块。
关于调用方式
隐式的调用:需要把产生动态连接库时产生的.LIB文件加入到应用程序的工程中,想使用DLL中的函数时,只须说明一下。隐式调用不需要调用LoadLibrary()和FreeLibrary()。当你建立一个DLL文件时,链接程序会自动生成一个与之对应的LIB导入文件。该文件包含了每一个DLL导出函数的符号名和可选的标识号,但是并不含有实际的代码。LIB文件作为DLL的替代文件被编译到应用程序项目中。当你通过静态链接方式编译生成应用程序时,应用程序中的调用函数与LIB文件中导出符号相匹配,这些符号或标识号进入到生成的EXE文件中。LIB文件中也包含了对应的DLL文件名(但不是完全的路径名),链接程序将其存储在EXE文件内部。当应用程序运行过程中需要加载DLL文件时,Windows根据这些信息发现并加载DLL,然后通过符号名或标识号实现对DLL函数的动态链接。所有被应用程序调用的DLL文件都会在应用程序EXE文件加载时被加载在到内存中。可执行程序链接到一个包含DLL输出函数信息的输入库文件(.LIB文件)。操作系统在加载使用可执行程序时加载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:
包含EXE文件的目录
进程的当前工作目录
Windows系统目录
Windows目录
列在Path环境变量中的一系列目录
关于生成和使用DLL
以VS2005为例,建立一个WIN32工程,工程名为Try,应用程序类型选择“DLL”,附加选项选择“导出符号”。编译器会自动的生成Try.h和Try.cpp文件。
//Try.h
#ifdef TRY_EXPORTS
#define TRY_API __declspec(dllexport)
#else
#define TRY_API __declspec(dllimport)
#endif
#include <iostream>
using namespace std;
// 此类是从 Try.dll 导出的
class TRY_API CTry {
public:
CTry(void) ;
// TODO: 在此添加您的方法。
void print() ;
};
extern TRY_API int nTry;
TRY_API int fnTry(void);
// Try.cpp : 定义 DLL 应用程序的入口点。
//
#include "stdafx.h"
#include "Try.h"
#ifdef _MANAGED
#pragma managed(push, off)
#endif
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
#ifdef _MANAGED
#pragma managed(pop)
#endif
// 这是导出变量的一个示例
TRY_API int nTry = 0;
// 这是导出函数的一个示例。
TRY_API int fnTry(void)
{
return 42;
}
// 这是已导出类的构造函数。
// 有关类定义的信息,请参阅 Try.h
CTry::CTry()
{
return ;
}
void CTry::print()
{
cout<<"CTry"<<endl;
}
// Try.cpp : 定义 DLL 应用程序的入口点。
//
#include "stdafx.h"
#include "Try.h"
#ifdef _MANAGED
#pragma managed(push, off)
#endif
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
#ifdef _MANAGED
#pragma managed(pop)
#endif
// 这是导出变量的一个示例
TRY_API int nTry = 0;
// 这是导出函数的一个示例。
TRY_API int fnTry(void)
{
return 42;
}
// 这是已导出类的构造函数。
// 有关类定义的信息,请参阅 Try.h
CTry::CTry()
{
return ;
}
void CTry::print()
{
cout<<"CTry"<<endl;
}
在头文件的前面使用_ _ d e c l s p e c ( d l l e x p o r t )对M Y L I B A P I进行定义。当编译器看到负责修改变量、函数或C + +类的_ _ d e c l s p e c ( d l l e x p o r t )时,它就知道该变量、函数或C + +类是从产生的D L L模块输出的。
仔细分析一下这段代码:
#ifdefTRY_EXPORTS
#defineTRY_API__declspec(dllexport)
#else
#define TRY_API __declspec(dllimport)
#endif
//如果已定义了TRY_EXPORTS
//用TRY_API替换__declspec(dllexport)
//否则
//将TRY_API替换__declspec(dllimport)
当编译器看到负责修饰变量、函数或C + +类的_ _ d e c l s p e c ( d l l e x p o r t )时,它就知道该变量、函数或C + +类是从产生的D L L模块输出的,当看到_ d e c l s p e c ( d l l im p o r t )时,就知道它是输入的。
那么TRY_EXPORTS有没有定义呢?在哪里定义的?在项目属性—>c/c++—>预处理器中有这么一行,预处理器定义:WIN32;_DEBUG;_WINDOWS;_USRDLL;TRY_EXPORTS。
建一个测试工程,将生成的Try.dll,Try.lib,Try.pdb统统copy到该工程目录下,在工程属性—>链接器—>输入中将Try.lib添加进去。就可以像普通工程那样使用DLL中的定义的数据了。
#include "stdafx.h"
#include <iostream>
#include "Try.h"
using namespace std;
int main()
{
cout<<nTry<<endl;
cout<<fnTry()<<endl;
CTry _a;
_a.print() ;
system("pause") ;
return 0 ;
}