1.26k likes | 1.47k Vues
第九章 动态链接库. 库 是 C/C++ 的一种重要软件形式,它的作用表现在: · 软件开发中 系统功能 、 通用功能 、 特殊功能 的提供者; · 提供 商品化 的 特定功能 或 通用功能集 的软件形式; · 组织开发 大型软件工程 中不可缺少的重要 软件构件 。 库 以 库文件 的形式提供, 库文件 包括 静态链接库 和 动态链接 库 两种, 库 所提供的功能是 不能独立运行 的,而必须通过将 库 静态 或 动态链接 到 可执行程序 中进行 加载 才能执行。 在 Visual C++ 开发环境中,不仅提供了开发软件所需要的大
E N D
库是C/C++ 的一种重要软件形式,它的作用表现在: ·软件开发中系统功能、通用功能、特殊功能的提供者; ·提供商品化的特定功能或通用功能集的软件形式; ·组织开发大型软件工程中不可缺少的重要软件构件。 库以库文件的形式提供,库文件包括静态链接库和动态链接 库两种,库所提供的功能是不能独立运行的,而必须通过将库 静态或动态链接到可执行程序中进行加载才能执行。 在 Visual C++ 开发环境中,不仅提供了开发软件所需要的大 部分功能库,同时为制作用户自定义功能库提供了方便,有效 的方法。对于 Windows 程序设计来说,动态链接库的制作和使 用,是至关重要的,它是一种常用的软件打包技术。本章将对 这一主题进行较为系统的讨论。
9.1 动态链接库介绍 动态链接库是具有某些特定功能的函数和类的目标代码集 合,它可以由用户自己开发,并被最终用户使用。使用动态链 接库与能提供相同功能的源代码相比有以下优点: ·省去了用户管理源代码的烦恼(许多情况下用户并不关心模 块的内部实现细节); ·使功能提供者达到只提供功能的使用而保护源代码的目的; ·可以减少模块的函数名、变量名与最终用户的程序相冲突。
9.1.1 动态链接库和静态库 ·静态链接库:提供功能函数的目标代码,如果应用程序调用 库中的函数,则在程序的编译链接阶段,就将库中被调函数的 目标代码复制链接到运行文件中。 ·动态链接库:提供功能函数的目标代码,如果应用程序调用 库中的函数,则在程序的编译链接阶段,只是记录被调函数的 位置信息(即位于哪个动态链接库的什么位置),而不将库中 被调函数的目标代码复制链接到运行文件。直到程序执行时, 才依据被调函数的位置信息找到函数的目标代码,完成真正的 链接,因此称为动态链接。被调函数的位置信息被放在动态链 接库的导入库文件中(与动态链接库文件同时创建)。
与静态链接库相比,动态链接库具有以下不同之处和优点: ·当两个以上程序调用同一库中的函数时,使用静态链接库,则将随着 所运行的程序在内存中出现该函数的多份拷贝;使用动态链接库,则 无论有多少运行的程序,被调用函数在内存中只有一份拷贝,更适合 多任务环境。 ·将共用的资源制作成动态链接库可以实现资源共享,Windows下的串 行口、并行口等外设的驱动程序,字体库等都是通过动态链接库实现 资源共享的。 ·由于静态链接库是将函数的目标代码复制到程序的运行文件中,所以 应用程序可以脱离库独立运行;而动态链接库是在程序运行时才发生 真正的链接,所以提供可执行程序的运行文件的同时,还必须提供所 调用的动态链接库文件。
动态链接库与应用程序都是目标代码,它们协同工作完成进动态链接库与应用程序都是目标代码,它们协同工作完成进 程的操作。在这种协同工作中它们的区别在于:应用程序总是 主动地调用动态链接库,而动态链接库总是被动地提供服务, 有些情况下(如中断驱动)也主动完成一些功能。
9.1.2 动态链接库与进程的关系 进程与被加载的动态链接库之间的关系: Windows NT进程内存映象 0xFFFFFFFF 2G保护区 0x80000000 Windows NT内核 设备驱动程序等 64K隔离区 0x7FFFFFFF 2G私有区 0x00000000 Windows DLL 应用程序DLL 内存映射文件 EXE 64K隔离区
进程: 进程中的线程可以调用动态链接库中任意一个函进程: 进程中的线程可以调用动态链接库中任意一个函 数和使用动态链接库提供的类、变量,通过堆栈 为被调用的动态链接库中的函数提供参数。 动态链接库:库中的所有资源(函数、类、变量)都可被进程 中的所有线程使用,被调用的库函数通过进程中 线程的堆栈获取被调用的参数、分配任何它所需 要的局部变量和返回调用结果,函数所创建的任 何对象都归调用它的线程和进程所有,动态链接 库在进程中一无所有。
9.1.3 动态链接库与静态链接库的加载原理 1静态链接库 ⑴提供形式: ·库文件*.LIB 库函数和其他资源的目标代码,用于静态 链接。 ·头文件*.h库函数和其他资源的声明,用于编译。 ⑵加载方法: 静态联编,即在编译链接阶段,就把应用程序所调用的库 函数目标代码复制连接进应用程序的目标程序中,所以随 后应用程序的运行将可以独立于静态库的存在。
2 动态链接库 ⑴提供形式: ①库文件*.dll库函数和其他资源的目标代码,用于动态 链接。 ②导入文件*.lib库函数和其他资源的位置索引信息,用于 静态链接。位置索引信息有两种形式: ·符号索引—— 按照函数名、类名和资源 名寻找目标代码; ·序号索引—— 按照函数、类、资源在库 中的序号寻找目标代码。 ③头文件*.h库函数和其他资源的声明,用于编译。
⑵加载方法: ①静态联编阶段,通过*.lib 和*.h 文件在应用程序的目标代 码中确定被调用库函数的位置。 ②动态链接阶段,即在应用程序加载后,首先根据程序中 由 *.lib 提供的 dll 文件名,搜寻 dll 文件。系统搜寻 dll文 件按照如下顺序: ·包含 EXE 文件的目录; ·进程执行的当前目录; ·Windows目录; ·Windows\system 目录; ·PATH 环境变量列出的目录。
一般情况下,dll 文件放在应用程序的执行文件所在的目 录下,而系统所用的 dll 文件则放在 Windows\system 目录 下。系统找到对应的 dll 文件后,把它加载到进程中的一 个相对固定的地址。在 Win32,该地址为 0x10000000, 而进程的加载地址为 0x400000。如果加载多个 dll,则顺 序下移。这个地址由实例句柄来描述,它唯一地标志了 被加载动态链接库在进程中的位置。根据该句柄和由 lib 文件提供的位置索引信息很容易就找到了对应的目标代 码,从而实现了对库函数等的动态链接调用。
9.1.4 实例1:静态库的制作和使用 动态链接库可以视为是静态链接库的延伸,在大多数情况 下,使用动态链接库具有更多的优点,但有些情况使用静态链接库更合适,二者并非可以相互替代。学习编写和使用动态链 接库之前,学习如何编写和使用静态链接库也是十分必要的。 1 生成静态链接库框架 在 VC 环境中,选择 File -> New 菜单项,弹出 New 对话框。 选择 Projects标签,在项目类别中选择 Win32 Static Library,在 Name文本框中输入项目名,即建立一个与项目名同名的静态 链接库。本例中建立一个 “mymath.lib”。
在生成项目过程中的 Win32 Static Library – Step 1 of 1 对话框中使用默认选择,即不做任何新的选择。
2 添加库源文件 ⑴ 添加库头文件 mymath.h #ifndef _MYMATH_H // 防止重定义 #define _MYMATH_H extern "C"// 标准c风格代码 { int Summary( int n ); int Factorial( int n ); } #endif 其中:extern “C” 关键字表明被修饰的函数是 C 风格的外部函 数。这样做的目的是避免 C++ 编译器把函数名改为带修饰名 的名称,因为带有修饰名的函数名不能被非 C++ 编译器(如 VB、Delphi 等)正确地编译。
⑵ 添加库源代码文件 mymath.cpp #include "mymath.h" int Summary( int n ) { int sum = 0; int i; for( i = 1; i < n; i++ ) { sum += i; } return sum; }
int Factorial( int n ) { int Fact = 1; int i; for( i = 1; i <= n; i++ ) { Fact *= i; } return Fact; }
3 编译库文件 成功编译后,会在项目的 mymath\debug\ 子目录中建立一个 名为 mymath.lib 的静态链接库文件。
4 测试静态链接库 由于库文件中的函数是不能独立运行的,因此必须建立一个 可执行程序,并在此程序中测试所建静态链接库中的函数。 ⑴ 使用 MFC AppWizard(exe) 在当前工作区中生成一个名为 “test” 的 Dialog Based类型的测试项目。 ⑵ 修改对话框资源 在 IDD_TEST_DIALOG对话框中添加两个按钮控件:
⑶ 使用 ClassWizard 为控件 IDC_SUM 和 IDC_FACTORIAL 的消息 BN_CLICKED 添加映射和响应函数 OnSum 和 OnFactorial。 在 CTestDlg定义文件中添加的响应函数声明: afx_msg void OnSum(); afx_msg void OnFactorial(); 在 CTestDlg实现文件中添加的消息映射项和响应函数定义: … ON_BN_CLICKED( IDC_SUM, OnSum ) ON_BN_CLICKED( IDC_FACTORIAL, OnFactorial ) …
void CTestDlg::OnSum() { int nSum = Summary(10) ; CString sResult; sResult.Format("Sum(10) = %d", nSum); AfxMessageBox(sResult); } void CTestDlg::OnFactorial() { int nFact = Factorial(10) ; CString sResult; sResult.Format("10! = %d", nFact); AfxMessageBox(sResult); }
⑷ 添加编译链接静态库的有关代码 ·首先将 mymath.lib 和 mymath.h 复制到 test 目录下; ·使用 Project -> Add to Project -> Files 菜单命令,或在 Workspace 的属性页 FileView 中使用环境菜单项Add Files to Project 命令, 将 mymath.lib 加入到 test 项目中; ·在 testdlg.cpp 文件的头部加入包含 mymath.h 的代码: … #include "mymath.h“ … static char THIS_FILE[] = __FILE__; #endif … 5 编译运行测试项目“test”
9.1.5 实例2:Win32 动态链接库的创建和使用 1 生成 dll 项目 ⑴ 在 VC 环境中,选择 File -> New 菜单项,弹出 New 对话框。 在 Project 标签页下,选择 Win32 Dynamic-Link Library 项目生 成向导。 ⑵ 输入项目名 “mymaths”,进入 Win32 Dynamic-Link Library – Step 1 of 1 对话框。选择 A DLL that’exports some symbols 。 ⑶ 单击 Finish 按钮,创建动态链接库所需要的项目文件框架。
2 修改框架文件 项目目录中包括 4个文件(以本例中的文件为例): ·源文件 mymaths.cpp:该文件定义了 DllMain 函数和要导出的 变量、函数和类的定义; ·头文件 mymaths.h:该文件定义了要导出的变量、函数和类的 原型及导出标识符; ·预编译头文件 StdAfx.h 和源文件 StdAfx.cpp。
⑴ 在头文件 mymaths.h 中添加导出函数的原型 #ifdef MYMATHS_EXPORTS #define MYMATHS_API __declspec(dllexport) #else #define MYMATHS_API __declspec(dllimport) #endif // This class is exported from the mymaths.dll class MYMATHS_API CMymaths { public: CMymaths(void); // TODO: add your methods here. };
extern MYMATHS_API int nMymaths; MYMATHS_API int fnMymaths(void); MYMATHS_API int Summary(int); MYMATHS_API int Factorial(int); 其中 ·MYMATHS_API创建动态链接库时为 _declspec(dllexport) 导 出标志;在使用动态库时为 _declspec(dllimport)导入标志。 ·MYMATHS_EXPORTS创建动态链接库时,框架自动定义 了该宏常量;使用动态库时,则不定义它。 显然,通过上述宏的作用,使得该头文件 mymaths.h 即可以 作为导出头文件,又可以作为导入头文件。
⑵ 在源文件 mymaths.cpp 中加入导出函数定义 #include "stdafx.h" #include "mymaths.h“ BOOL APIENTRY DllMain( HANDLE 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; }
… MYMATHS_API int Summary(int n) { int sum = 0; int i; for(i = 1; i < n; i++) sum += i; return sum; } MYMATHS_API int Factorial(int n) { int Fact = 1; int i; for(i = 1; i <= n; i++) Fact *= i; return Fact; }
其中: ·#include “mymaths.h”声明了动态链接库所包含的两个导 出函数。 ·DllMain动态链接库入口函数,应用程序通过该入口函数 访问动态链接库所提供的服务。该函数的主体是一个 switch/case结构。在各个 case 分支中允许加入你所需要的 特定代码,例如 在 DLL_PROCESS_ATTACH分支中加入动态链接库执行时的 初始化代码; 在 DLL_PROCESS_DETACH分支中加入动态链接库卸载时 的清理代码。 详细信息参见 MSDN中的相关部分。 ·库功能函数 Summary 和 Factorial 的定义代码。
3 编译动态链接库项目 成功编译后,会在项目的 mymaths\debug\ 子目录中建立名为 mymaths.dll 的动态链接库文件和一个名为 mymaths.lib 的导入库 文件。
4 测试 mymaths.dll 使用动态链接库有两种方式:隐式连接和显式连接。 ⑴ 通过引入库的隐式连接 ① 把实例1中的 “test”项目加入 mymaths 工作区: 可以重新建立一个与实例1中的测试项目相同的项目 test。 也可以按照以下方法将实例1中的测试项目复制加入到本 例中: ·将实例1 的 test 目录及其目录下的文件复制到 mymaths 目录下;
·在 mymaths 工作区的文件视图区 File View 中,右击 Workspace ‘mymaths’ 选择 “Insert project into workspace… ” 命令, 在随后的对话框中选择 test.dsp把项目 test 加入 mymaths工作区。
·在文件视图区 FileView 中删除 test 项目下的 mymath.h 和 mymath.lib 条目以及 test 目录中的 mymath.h 和 mymath.lib 文件。
② 把动态链接库相关的文件复制到指定的目录下: ·将动态链接库头文件 mymaths.h 从 ..\mymaths\ 目录中复 制到 ..\mymaths\test\ 目录中,以便 test 应用程序编译时使 用; ·将导入库 mymaths.lib 从 ..\mymaths\debug\ 目录中复制到 ..\mymaths\test\中,以便 test 应用程序静态链接时使用; ·将动态链接库 mymaths.dll 从 ..\mymaths\debug\ 目录中复 制到 ..\mymaths\test\debug\ 目录中或复制到应用程序运行 时可以找到的磁盘目录中,便于 test 应用程序运行时与 mymaths.dll动态链接。
③使用 Project->Add to project->Files 菜单命令,将 mymaths.lib 加入到 test 项目中,或通过 Project->Settings…->Link 在 Project Settings 对话框中将 mymaths.lib 加入 Link 属性的 Object / library modules 中。
④在 testdlg.cpp 中加入包含动态链接库头文件 mymaths.h 的 代码: #include "mymaths.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ⑤ 编译运行 “test”
⑵ 直接指定库和函数地址的显式连接 ① 使用 MFC AppWizard(exe) 在 mymaths 工作区中再添加一个 名为 “test1” 的 Dialog Based 类型的应用程序项目。 ② 在对话框 IDD_TEST1_DIALOG 中加入按钮倥件 IDC_SUM 和 IDC_FACTORIAL, ③ 使用 ClassWizard 为按钮 IDC_SUM 和 IDC_FACTORIAL 的通 知消息 BN_CLICKED 添加消息映射和响应函数 OnSum 和 OnFactorial。函数的定义代码可以从 test项目的 testdlg.cpp 中复制相应的代码。
④ 在 CTest1Dlg 类中增加用于显式连接动态链接库的函数 LoadDLL,并编写操作代码。 在test1dlg.h中增加了: class CTest1Dlg : public CDialog { … protected: BOOL LoadDLL(); … };
在 test1dlg.cpp 中增加了: BOOL CTest1Dlg::LoadDLL() { // It return if DLL library has been loaded if(ghMathsDLL != NULL) return TRUE; // To load Mymaths.DLL ghMathsDLL = ::LoadLibrary("mymaths.DLL"); // To display indicating information if loading fails if(ghMathsDLL == NULL) { AfxMessageBox("Cannot load DLL file!"); return FALSE; }
// To get the address of function Summary in DLL. Summary = (MYPROC)::GetProcAddress( ghMathsDLL, LPCSTR 2 ); // ordinal value of Summary is 2 // To get the address of function Factorial in DLL. Factorial = (MYPROC)::GetProcAddress( ghMathsDLL, LPCSTR 1 ); // ordinal value of Factorial is 1 return TRUE; } 其中: ·ghMathsDLL是加载后的动态链接库的句柄。 ·Summary和Factorial是获取的动态链接库中的导出函数 Summary(…) 和 Factorial(…) 的调用地址。
上述三个变量是全程的,它们被定义在 test1dlg.cpp 中: … #endif // The instance of the Mymaths.DLL library HINSTANCE ghMathsDLL = NULL; typedef int (*MYPROC) (int); // declare the Summary() function from the Mymaths.DLL library MYPROC Summary; // declare the Factorial() function from the Mymaths.DLL library MYPROC Factorial;
⑤ 在 OnSum 和 OnFactorial 的中加入调用 LoadDLL 的代码: void CTest1Dlg::OnSum() { // TODO: Add your control notification handler code here if(!LoadDLL()) return; int nSum = Summary(10); CString sResult; sResult.Format("Sum(10) = %d", nSum); AfxMessageBox(sResult); }
void CTest1Dlg::OnFactorial() { // TODO: Add your control notification handler code here if(!LoadDLL()) return; int nFact = Factorial(10); CString sResult; sResult.Format("10! = %d", nFact); AfxMessageBox(sResult); } ⑥ 为了能显式连接动态链接库,将 mymaths.dll 复制到 ..\mymaths\test1\debug\ 目录中。
⑦ 编译运行 “test1” 显式连接动态链接库的方式经常用在随时加载或者有选择地 加载动态链接库的场合。例如,一个程序带有多个动态链接 库,分别用于访问 JPEG、BMP、GIF 等多种图象文件格式, 这些动态链接库提供了相同的库函数接口。此时,无法使用 引入库隐式连接动态链接库,可以用下面的显式连接的方法 解决: HANDLE nLibrary; FARPROC lpFunc; int nFormat;
… hLibrary = NULL; if(nFormat == JPEG) // 如果是JPEG格式,装入JPEG动态链接库 hLibrary = LoadLibrary(“JPEG.DLL”); else if(nFormat == BMP) // 如果是BMP格式,装入BMP动态链接库 hLibrary = LoadLibrary(“BMP.DLL”); else // 如果是GIF格式,装入GIF动态链接库 hLibrary = LoadLibrary(“GIF.DLL”); if(hLibrary) { lpFunc = GetProcAddress(hLibrary, “ReadImage”); if(lpFunc != (FARPROC)NULL) (*lpFunc)((LPCTSTR)strFileName); // 装载图象 FreeLibrary(hLibrary) // 释放动态链接库 }
其中: ·LoadLibrary 函数装入所需要的动态链接库,并返回句柄。 ·GetProcAddress 函数使用函数名取得函数的地址,利用函 数地址,就可以访问动态链接库的函数了。注意,用于获 取函数地址的“函数名字串”有可能与函数名有所不同(可 以在导入库 *.lib 中查找)。 ·FreeLibrary 函数通过检查动态链接库的引用计数器,判断 是否还有别的程序正在使用这个动态链接库。如果没有, 从内存中移去该动态链接库;如果有,将动态链接库的使 用计数器减1,LoadLibrary 则将引用计数器加1。
9.2 MFC动态链接库 虽然从实现功能的角度看,静态链接库和动态链接库都一样 可以将用户所需要的库函数打包在一个用户模块中,使得重用 这些函数功能的用户都能告别源代码,但两者的用途各有其 所。但在一些情况下,必须使用动态链接库,这些场合包括: ⑴ 多个应用程序共享代码和资源。 ⑵ 在钩子程序<HOOK> 过滤系统消息时必须使用动态链接库。 ⑶ 设备驱动程序必须是动态链接库。 ⑷ 如果要在对话框编辑器中使用自定义控件,也必须使用动态 链接库或 ActiveX 控件。
⑸ 使用动态链接库可以以一种自然的方式将一个大规模应用程 序划分为若干小模块,有利于软件开发人员的分工和合作。 ⑹ 为了实现应用程序的国际化,可以将针对不同国家的语言等 信息作为资源存放在不同版本的动态链接库中,对于针对不 同国家的应用程序只要连接不同的动态链接库即可。 在前一节所讲述的动态链接库的编制中只能使用使用 Win32 API 函数。在VC 集成开发环境中还提供了可以使用 MFC 类库编 制动态链接库的方法,使我们能更方便地编制功能强大的 MFC 动态链接库。AppWizard 支持创建的 MFC 动态链接库有两类: ·MFC 正规动态链接库 ·MFC 扩展动态链接库