第九章 动态链接库

126
第第第 第第第第第

Upload: sybill-velasquez

Post on 30-Dec-2015

131 views

Category:

Documents


4 download

DESCRIPTION

第九章 动态链接库. 库 是 C/C++ 的一种重要软件形式,它的作用表现在: · 软件开发中 系统功能 、 通用功能 、 特殊功能 的提供者; · 提供 商品化 的 特定功能 或 通用功能集 的软件形式; · 组织开发 大型软件工程 中不可缺少的重要 软件构件 。 库 以 库文件 的形式提供, 库文件 包括 静态链接库 和 动态链接 库 两种, 库 所提供的功能是 不能独立运行 的,而必须通过将 库 静态 或 动态链接 到 可执行程序 中进行 加载 才能执行。 在 Visual C++ 开发环境中,不仅提供了开发软件所需要的大 - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 第九章  动态链接库

第九章 动态链接库

Page 2: 第九章  动态链接库

库是 C/C++ 的一种重要软件形式,它的作用表现在:· 软件开发中系统功能、通用功能、特殊功能的提供者;· 提供商品化的特定功能或通用功能集的软件形式;· 组织开发大型软件工程中不可缺少的重要软件构件。 库以库文件的形式提供,库文件包括静态链接库和动态链接库两种,库所提供的功能是不能独立运行的,而必须通过将库静态或动态链接到可执行程序中进行加载才能执行。 在 Visual C++ 开发环境中,不仅提供了开发软件所需要的大部分功能库,同时为制作用户自定义功能库提供了方便,有效的方法。对于 Windows 程序设计来说,动态链接库的制作和使用,是至关重要的,它是一种常用的软件打包技术。本章将对这一主题进行较为系统的讨论。

Page 3: 第九章  动态链接库

9.1 动态链接库介绍 动态链接库是具有某些特定功能的函数和类的目标代码集

合,它可以由用户自己开发,并被最终用户使用。使用动态链

接库与能提供相同功能的源代码相比有以下优点:

· 省去了用户管理源代码的烦恼(许多情况下用户并不关心模

块的内部实现细节);

· 使功能提供者达到只提供功能的使用而保护源代码的目的;

· 可以减少模块的函数名、变量名与最终用户的程序相冲突。

Page 4: 第九章  动态链接库

9.1.1 动态链接库和静态库· 静态链接库:提供功能函数的目标代码,如果应用程序调用

库中的函数,则在程序的编译链接阶段,就将库中被调函数的

目标代码复制链接到运行文件中。

· 动态链接库:提供功能函数的目标代码,如果应用程序调用

库中的函数,则在程序的编译链接阶段,只是记录被调函数的

位置信息(即位于哪个动态链接库的什么位置),而不将库中

被调函数的目标代码复制链接到运行文件。直到程序执行时,

才依据被调函数的位置信息找到函数的目标代码,完成真正的

链接,因此称为动态链接。被调函数的位置信息被放在动态链

接库的导入库文件中(与动态链接库文件同时创建)。

Page 5: 第九章  动态链接库

与静态链接库相比,动态链接库具有以下不同之处和优点: · 当两个以上程序调用同一库中的函数时,使用静态链接库,则将随着 所运行的程序在内存中出现该函数的多份拷贝;使用动态链接库,则 无论有多少运行的程序,被调用函数在内存中只有一份拷贝,更适合 多任务环境。· 将共用的资源制作成动态链接库可以实现资源共享, Windows 下的串 行口、并行口等外设的驱动程序,字体库等都是通过动态链接库实现 资源共享的。· 由于静态链接库是将函数的目标代码复制到程序的运行文件中,所以 应用程序可以脱离库独立运行;而动态链接库是在程序运行时才发生 真正的链接,所以提供可执行程序的运行文件的同时,还必须提供所 调用的动态链接库文件。

Page 6: 第九章  动态链接库

动态链接库与应用程序都是目标代码,它们协同工作完成进

程的操作。在这种协同工作中它们的区别在于:应用程序总是

主动地调用动态链接库,而动态链接库总是被动地提供服务,

有些情况下(如中断驱动)也主动完成一些功能。

Page 7: 第九章  动态链接库

9.1.2 动态链接库与进程的关系 进程与被加载的动态链接库之间的关系:

Windows NT 内核设备驱动程序等

Windows DLL应用程序 DLL内存映射文件EXE

0xFFFFFFFF

2G 保护区

0x80000000

0x7FFFFFFF

2G 私有区

0x00000000

64K 隔 离区

64K 隔 离区

Windows NT 进程内存映象

Page 8: 第九章  动态链接库

进程: 进程中的线程可以调用动态链接库中任意一个函

数和使用动态链接库提供的类、变量,通过堆栈

为被调用的动态链接库中的函数提供参数。

动态链接库:库中的所有资源(函数、类、变量)都可被进程

中的所有线程使用,被调用的库函数通过进程中

线程的堆栈获取被调用的参数、分配任何它所需

要的局部变量和返回调用结果,函数所创建的任

何对象都归调用它的线程和进程所有,动态链接

库在进程中一无所有。

Page 9: 第九章  动态链接库

9.1.3 动态链接库与静态链接库的加载原理1 静态链接库

⑴ 提供形式:

· 库文件 *.LIB 库函数和其他资源的目标代码,用于静态

链接。

·头文件 *.h 库函数和其他资源的声明,用于编译。

⑵ 加载方法:

静态联编,即在编译链接阶段,就把应用程序所调用的库

函数目标代码复制连接进应用程序的目标程序中,所以随

后应用程序的运行将可以独立于静态库的存在。

Page 10: 第九章  动态链接库

2 动态链接库

⑴ 提供形式:

① 库文件 *.dll 库函数和其他资源的目标代码,用于动态

链接。

② 导入文件 *.lib 库函数和其他资源的位置索引信息,用于

静态链接。位置索引信息有两种形式:

·符号索引—— 按照函数名、类名和资源

名寻找目标代码;

· 序号索引—— 按照函数、类、资源在库

中的序号寻找目标代码。

③ 头文件 *.h 库函数和其他资源的声明,用于编译。

Page 11: 第九章  动态链接库

⑵ 加载方法:

① 静态联编阶段,通过 *.lib 和 *.h 文件在应用程序的目标代

码中确定被调用库函数的位置。

② 动态链接阶段,即在应用程序加载后,首先根据程序中

由 *.lib 提供的 dll 文件名,搜寻 dll 文件。系统搜寻 dll 文

件按照如下顺序:

· 包含 EXE 文件的目录;

· 进程执行的当前目录;

· Windows 目录;

· Windows\system 目录;

· PATH 环境变量列出的目录。

Page 12: 第九章  动态链接库

一般情况下, dll 文件放在应用程序的执行文件所在的目

录下,而系统所用的 dll 文件则放在 Windows\system 目录

下。系统找到对应的 dll 文件后,把它加载到进程中的一

个相对固定的地址。在 Win32 ,该地址为 0x10000000 ,

而进程的加载地址为 0x400000 。如果加载多个 dll ,则顺

序下移。这个地址由实例句柄来描述,它唯一地标志了

被加载动态链接库在进程中的位置。根据该句柄和由 lib

文件提供的位置索引信息很容易就找到了对应的目标代

码,从而实现了对库函数等的动态链接调用。

Page 13: 第九章  动态链接库

9.1.4 实例 1 :静态库的制作和使用 动态链接库可以视为是静态链接库的延伸,在大多数情况

下,使用动态链接库具有更多的优点,但有些情况使用静态链接库更合适,二者并非可以相互替代。学习编写和使用动态链

接库之前,学习如何编写和使用静态链接库也是十分必要的。

1 生成静态链接库框架

在 VC 环境中,选择 File -> New 菜单项,弹出 New 对话框。

选择 Projects 标签,在项目类别中选择 Win32 Static Library ,在

Name 文本框中输入项目名,即建立一个与项目名同名的静态

链接库。本例中建立一个 “ mymath.libmymath.lib” 。

Page 14: 第九章  动态链接库

在生成项目过程中的 Win32 Static Library – Step 1 of 1 对话框中使用默认选择,即不做任何新的选择。

Page 15: 第九章  动态链接库

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 等)正确地编译。

Page 16: 第九章  动态链接库

⑵ 添加库源代码文件 mymath.cpp

#include "mymath.h"

int Summary( int n )

{

int sum = 0;

int i;

for( i = 1; i < n; i++ )

{

sum += i;

}

return sum;

}

Page 17: 第九章  动态链接库

int Factorial( int n )

{

int Fact = 1;

int i;

for( i = 1; i <= n; i++ )

{

Fact *= i;

}

return Fact;

}

Page 18: 第九章  动态链接库

3 编译库文件

成功编译后,会在项目的 mymath\debug\ 子目录中建立一个

名为 mymath.lib 的静态链接库文件。

Page 19: 第九章  动态链接库

4 测试静态链接库

由于库文件中的函数是不能独立运行的,因此必须建立一个

可执行程序,并在此程序中测试所建静态链接库中的函数。

⑴ 使用 MFC AppWizard(exe) 在当前工作区中生成一个名为 “ test”

的 Dialog Based 类型的测试项目。

⑵ 修改对话框资源

在 IDD_TEST_DIALOG 对话框中添加两个按钮控件:控件标识 控件标题

IDC_SUM

IDC_FACTORIAL

1 到 10 求和 (&S)

1 到 10 求积 (&F)

Page 20: 第九章  动态链接库

⑶ 使用 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 )

Page 21: 第九章  动态链接库

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);

}

Page 22: 第九章  动态链接库

⑷ 添加编译链接静态库的有关代码·首先将 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”

Page 23: 第九章  动态链接库

9.1.5 实例 2 : Win32 动态链接库的创建和使用1 生成 dll 项目

⑴ 在 VC 环境中,选择 File -> New 菜单项,弹出 New 对话框。

在 Project 标签页下,选择 Win32 Dynamic - Link Library 项目生

成向导。

⑵ 输入项目名 “ mymathsmymaths” ,进入 Win32 Dynamic-Link Library –

Step 1 of 1 对话框。选择 A DLL that’exports some symbols 。

⑶ 单击 Finish 按钮,创建动态链接库所需要的项目文件框架。

Page 24: 第九章  动态链接库

2 修改框架文件

项目目录中包括 4 个文件(以本例中的文件为例):

· 源文件 mymaths.cpp :该文件定义了 DllMain 函数和要导出的

变量、函数和类的定义;

·头文件 mymaths.h :该文件定义了要导出的变量、函数和类的

原型及导出标识符;

·预编译头文件 StdAfx.h 和源文件 StdAfx.cpp 。

Page 25: 第九章  动态链接库

⑴ 在头文件 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.

};

Page 26: 第九章  动态链接库

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 即可以

作为导出头文件,又可以作为导入头文件。

Page 27: 第九章  动态链接库

⑵ 在源文件 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;

}

Page 28: 第九章  动态链接库

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;

}

Page 29: 第九章  动态链接库

其中: ·#include “mymaths.h” 声明了动态链接库所包含的两个导 出函数。 ·DllMain 动态链接库入口函数,应用程序通过该入口函数 访问动态链接库所提供的服务。该函数的主体是一个 switch/case结构。在各个 case 分支中允许加入你所需要的 特定代码,例如 在 DLL_PROCESS_ATTACH 分支中加入动态链接库执行

时的初始化代码;在 DLL_PROCESS_DETACH 分支中加入动态链接库卸载时的清理代码。

详细信息参见 MSDN 中的相关部分。 · 库功能函数 Summary 和 Factorial 的定义代码。

Page 30: 第九章  动态链接库

3 编译动态链接库项目

成功编译后,会在项目的 mymaths\debug\ 子目录中建立名为

mymaths.dll 的动态链接库文件和一个名为 mymaths.lib 的导入库

文件。

Page 31: 第九章  动态链接库

4 测试 mymaths.dll

使用动态链接库有两种方式:隐式连接和显式连接。

⑴ 通过引入库的隐式连接

① 把实例 1 中的 “ test” 项目加入 mymaths 工作区:

可以重新建立一个与实例 1 中的测试项目相同的项目 test 。

也可以按照以下方法将实例 1 中的测试项目复制加入到本

例中:

· 将实例 1 的 test 目录及其目录下的文件复制到 mymath

s

目录下;

Page 32: 第九章  动态链接库

· 在 mymaths 工作区的文件视图区 File View 中,右击

Workspace ‘mymaths’ 选择 “ Insert project into workspace…

命令,

在随后的对话框中选择 test.dsp 把项目 test 加入 mymaths 工作区。

Page 33: 第九章  动态链接库

· 在文件视图区 FileView 中删除 test 项目下的 mymath.h 和

mymath.lib 条目以及 test 目录中的 mymath.h 和 mymath.lib

文件。

Page 34: 第九章  动态链接库

② 把动态链接库相关的文件复制到指定的目录下:

· 将动态链接库头文件 mymaths.h 从 ..\mymaths\ 目录中复

制到 ..\mymaths\test\ 目录中,以便 test 应用程序编译时使

用;

· 将导入库 mymaths.lib 从 ..\mymaths\debug\ 目录中复制到

..\mymaths\test\ 中,以便 test 应用程序静态链接时使用;

· 将动态链接库 mymaths.dll 从 ..\mymaths\debug\ 目录中复

制到 ..\mymaths\test\debug\ 目录中或复制到应用程序运行

时可以找到的磁盘目录中,便于 test 应用程序运行时与

mymaths.dll 动态链接。

Page 35: 第九章  动态链接库

③ 使用 Project->Add to project->Files 菜单命令,将 mymath

s.lib

加入到 test 项目中,或通过 Project->Settings…->Link

在 Project Settings 对话框中将 mymaths.lib 加入 Link

属性的

Object / library modules 中。

Page 36: 第九章  动态链接库
Page 37: 第九章  动态链接库

④ 在 testdlg.cpp 中加入包含动态链接库头文件 mymaths.

h 的

代码: #include "mymaths.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

⑤ 编译运行 “ test”

Page 38: 第九章  动态链接库

⑵ 直接指定库和函数地址的显式连接

① 使用 MFC AppWizard(exe) 在 mymaths 工作区中再添加一个

名为 “ test1” 的 Dialog Based 类型的应用程序项目。

② 在对话框 IDD_TEST1_DIALOG 中加入按钮倥件 IDC_S

UM 和

IDC_FACTORIAL ,

③ 使用 ClassWizard 为按钮 IDC_SUM 和 IDC_FACTORIA

L 的通

知消息 BN_CLICKED 添加消息映射和响应函数 OnSu

m 和

OnFactorial 。函数的定义代码可以从 test 项目的 testdlg.cp

p

中复制相应的代码。

Page 39: 第九章  动态链接库

④ 在 CTest1Dlg 类中增加用于显式连接动态链接库的函数

LoadDLL ,并编写操作代码。

在 test1dlg.h 中增加了: class CTest1Dlg : public CDialog

{

protected:

BOOL LoadDLL();

};

Page 40: 第九章  动态链接库

在 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;

}

Page 41: 第九章  动态链接库

// 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(…) 的调用地址。

Page 42: 第九章  动态链接库

上述三个变量是全程的,它们被定义在 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;

Page 43: 第九章  动态链接库

⑤ 在 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);

}

Page 44: 第九章  动态链接库

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\ 目录中。

Page 45: 第九章  动态链接库

⑦ 编译运行 “ test1”

显式连接动态链接库的方式经常用在随时加载或者有选择地

加载动态链接库的场合。例如,一个程序带有多个动态链接

库,分别用于访问 JPEG 、 BMP 、 GIF 等多种图象文件格式,

这些动态链接库提供了相同的库函数接口。此时,无法使用

引入库隐式连接动态链接库,可以用下面的显式连接的方法

解决: HANDLE nLibrary;

FARPROC lpFunc;

int nFormat;

Page 46: 第九章  动态链接库

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) // 释放动态链接库 }

Page 47: 第九章  动态链接库

其中:

·LoadLibrary 函数装入所需要的动态链接库,并返回句柄。

·GetProcAddress 函数使用函数名取得函数的地址,利用函

数地址,就可以访问动态链接库的函数了。注意,用于获

取函数地址的“函数名字串”有可能与函数名有所不同(可

以在导入库 *.lib 中查找)。

·FreeLibrary 函数通过检查动态链接库的引用计数器,判断

是否还有别的程序正在使用这个动态链接库。如果没有,

从内存中移去该动态链接库;如果有,将动态链接库的使

用计数器减 1 , LoadLibrary 则将引用计数器加 1 。

Page 48: 第九章  动态链接库

9.2 MFC 动态链接库 虽然从实现功能的角度看,静态链接库和动态链接库都一样

可以将用户所需要的库函数打包在一个用户模块中,使得重用

这些函数功能的用户都能告别源代码,但两者的用途各有其

所。但在一些情况下,必须使用动态链接库,这些场合包括:

⑴ 多个应用程序共享代码和资源。

⑵ 在钩子程序 <HOOK> 过滤系统消息时必须使用动态链接库。

⑶ 设备驱动程序必须是动态链接库。

⑷ 如果要在对话框编辑器中使用自定义控件,也必须使用动态

链接库或 ActiveX 控件。

Page 49: 第九章  动态链接库

⑸ 使用动态链接库可以以一种自然的方式将一个大规模应用程

序划分为若干小模块,有利于软件开发人员的分工和合作。

⑹ 为了实现应用程序的国际化,可以将针对不同国家的语言等

信息作为资源存放在不同版本的动态链接库中,对于针对不

同国家的应用程序只要连接不同的动态链接库即可。

在前一节所讲述的动态链接库的编制中只能使用使用 Win

32

API 函数。在 VC 集成开发环境中还提供了可以使用 MFC 类库编

制动态链接库的方法,使我们能更方便地编制功能强大的 MFC

动态链接库。 AppWizard 支持创建的 MFC 动态链接库有两类:

·MFC 正规动态链接库

·MFC 扩展动态链接库

Page 50: 第九章  动态链接库

9.2.1 MFC 正规动态链接库与 MFC 扩展动态链接库比较内容 MFC 正规动态链接库 MFC 扩展动态链接库

能够导出的内容种类

1 能导出 C 风格函数、全 局变量;2 能导出资源;3 不能导出 C++ 类、成员 函数、重载函数。

1 能导出 C 风格函数、全 局变量;2 能导出 C++ 类、成员函 数、重载函数;3 能导出资源。

允许连接 MFC 类库方式 允许连接静态或动态链接库 只允许连接动态链接库

对客户应用程序的要求 客户应用程序可静态连接 MFC 类库,也可动态连接 MFC 类库。

客户应用程序只能动态连接 MFC 类库,而且要求客户应用程序和 DLL 项目连接到同一版本的 MFC类库上。

编译器 #define 常量设置

1 静态连接 MFC 类库: _USRDLL2 动态连接 MFC 类库: _USRDLL _AFXDLL

只能动态连接 MFC 类库:_AFXEXT_AFXDLL

Page 51: 第九章  动态链接库

显然,从表中的比较不难看出:

· 如果希望充分利用 MFC 类库的资源时,则要创建 MFC 扩展

动态链接库,但会给应用带来某些限制。

· 如果希望动态链接库的导出函数能适用于多种 Win32 编译环

境,则要创建 MFC 正规动态链接库。

Page 52: 第九章  动态链接库

9.2.2 实例 3 :正规 DLL 导出函数 MFC 正规动态链接库的特点是编制库时,内部可以使用 MF

C

类库,但应用库时,不能导出 C++ 接口,而只能导出 C 风格的

函数。

1 生成正规动态链接库的框架

⑴ 选择 File -> New 菜单项,弹出 New 对话框。在 Project

标签页

中,选择 “ MFC AppWizard (dll)” 模板,创建名为 “ rgdll”

的项目。

⑵ 单击 OK 按钮,进入 MFC AppWizard (dll) – Step 1 of 1 对话框,

选择 Regular DLL using shared MFC DLL 选项。

⑶ 单击 Finish 按钮,完成创建 MFC 正规动态链接库所需的项目

框架。

Page 53: 第九章  动态链接库

2 查看 rgdll.cpp 文件和 rgdll.h 文件

框架自动生成了一个 CRgdllApp 类和该类全局对象 theAp

p ,

但没有显式的链接入口函数 DllMain (但该函数的功能被封装在

框架中了 ) 。由于 CRgdllApp 是 CWinApp 类的派生类,所以可以

通过重载 CWinApp::InitInstance 和 CWinApp::ExitInstance 提供初

始化和最后清理服务。 Rgdll.cpp 代码如下:

// rgdll.cpp : Defines the initialization routines for the DLL.

#include "stdafx.h"

#include "rgdll.h"

Page 54: 第九章  动态链接库

// CRgdllApp

BEGIN_MESSAGE_MAP(CRgdllApp, CWinApp)

//{{AFX_MSG_MAP(CRgdllApp)

// NOTE - the ClassWizard will add and remove mapping macros here.

// DO NOT EDIT what you see in these blocks of generated code!

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

// CRgdllApp construction

CRgdllApp::CRgdllApp()

{

// TODO: add construction code here,

// Place all significant initialization in InitInstance

}

// The one and only CRgdllApp object

CRgdllApp theApp;

Page 55: 第九章  动态链接库

3 添加导出函数定义

在 rgdll.cpp 文件中加入下列函数代码:extern "C" __declspec(dllexport) int Factorial(int n)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

int Fact = 1;

for(int i = 1; i <= n; i++)

Fact *= i;

CString str;

str.Format("%d! = %d", n, Fact);

AfxMessageBox(str);

return Fact;

}

Page 56: 第九章  动态链接库

其中:·extern “C” 指示编译器生成简明的 C 风格函数名, __declspec(dllexport) 为库函数导出修饰,指示编译器生成该 函数的映像信息。·AFX_MANAGE_STATE(AfxGetStaticModuleState()) 是正规 DLL 在 动态连接 MFC DLL 时必须添加的一个调整语句,目的是消除

DLL 的全程变量的不同步问题。注意,该语句的位置必须是 函数定义体的第一行。由于该语句对静态连接 MFC DLL

的常 规动态链接库没有作用,但也没有妨碍,所以建议:无论

哪 一种方式连接 MFC DLL 的正规动态链接库的导出函数都添加

该语句。4 编译项目 “ rgdll” ,创建库文件 rgdll.dll 和导出库文件 rgdll.

lib 。

Page 57: 第九章  动态链接库

5 生成测试项目

⑴ 在当前工作区的属性页 FileView 中,选择环境菜单项 “ A

dd

New Project to Workspace” 生成一个名为 “ testdll” 的 SDI 应用程

序项目。

⑵ 将 rgdll.dll 和 rgdll.lib 从 ..\rgdll\debug\ 目录中分别复制到

..\rgdll\testdll\debug\ 和 ..\rgdll\testdll\ 目录中,并将 rgdll.lib 添加

到 testdll 项目的链接选项中。

⑶ 使用 ClassWizard 在 CTestdllView 类中为 WM_LBUTTONDO

WN

消息添加消息映射和响应函数 OnLButtonDown 。

Page 58: 第九章  动态链接库

void CTestdllView::OnLButtonDown(UINT nFlags, CPoint point)

{

int a = Factorial(10);

CView::OnLButtonDown(nFlags, point);

}

⑷ 在使用 Factorial 函数之前,需要在 rgdll.cpp 文件中加入函数

的导入声明:

extern "C" __declspec(dllimport) int Factorial(int n);

6 编译运行测试项目 “ testdll”

Page 59: 第九章  动态链接库

9.2.3 实例 4 :扩展 DLL 导出 C++ 类、资源 从 MFC 扩展动态链接库中不但能导出 C++ 风格的函数,

量和资源,还能导出 MFC 的派生类,这些类是 MFC 类的自然

延伸。

1 生成扩展动态链接库框架

使用 MFC AppWizard (dll) 创建一个名为 “ extdllextdll” 的扩展动态链

接库(在 MFC AppWizard (dll) – Step 1 of 1 对话框中选择 MFC

Extension DLL(using shared MFC DLL) )。查看 extdll.cpp 文件:#include "stdafx.h"

#include <afxdllx.h>

Page 60: 第九章  动态链接库

static AFX_EXTENSION_MODULE ExtdllDLL = { NULL, NULL };

extern "C" int APIENTRY

DllMain( HINSTANCE hInstance, DWORD dwReason,

LPVOID lpReserved )

{ // Remove this if you use lpReserved

UNREFERENCED_PARAMETER(lpReserved);

if (dwReason == DLL_PROCESS_ATTACH)

{

TRACE0("EXTDLL.DLL Initializing!\n");

// Extension DLL one-time initialization

if (!AfxInitExtensionModule(ExtdllDLL, hInstance))

return 0;

new CDynLinkLibrary(ExtdllDLL);

}

Page 61: 第九章  动态链接库

else if (dwReason == DLL_PROCESS_DETACH)

{

TRACE0("EXTDLL.DLL Terminating!\n");

// Terminate the library before destructors are called

AfxTermExtensionModule(ExtdllDLL);

}

return 1; // ok

}

Page 62: 第九章  动态链接库

其中 AFX_EXTENSION_MODULE 是一个数据结构,用于在扩展

动态链接库初始化时保持动态链接模块状态。struct AFX_EXTENSION_MODULE

{  

BOOL bInitialized;  

HMODULE hModule;  

HMODULE hResource;

CRuntimeClass* pFirstSharedClass;  

COleObjectFactory* pFirstSharedFactory;

};

祥见 MSDN 的相关章节。

Page 63: 第九章  动态链接库

2 在项目中生成一个对话框类 CCalDlg

⑴ 添加一个对话框资源 IDD_DIALOG1 ,修改默认的控件资源布

局,并添加两个静态文本框、两个编辑文本框和一个按钮,

如图所示:

Page 64: 第九章  动态链接库

其中文本编辑框控件件和按钮控和标题如的标识下:

⑵ 使用 ClassWizard 为 IDD_DIALOG1 定义基类为 CDialog 的对话

框类 CCalDlg ,并为上述文本编辑控件定义数据成员和为按

钮控件消息生成消息映射及消息响应函数。

控件类型 控件标题 控件标识文本编辑框

文本编辑框

按钮 Product

IDC_INPUT

IDC_RESULT

IDC_PRODUCT

控件标题 关联数据成员 /响应函数

IDC_INPUT

IDC_RESULT

IDC_PRODUCT

int m_nInput

int m_nResult

void OnProduct()

Page 65: 第九章  动态链接库

⑶ 编写 OnProduct 函数:

void CCalDlg::OnProduct()

{

UpdateData(); // 更新控件关联的数据成员

m_nResult = m_nInput * m_nInput; // 计算平方值

UpdateData(FALSE); // 更新控件显示计算结果

}

Page 66: 第九章  动态链接库

⑷ 给 CCalDlg 类定义加上导出修饰 AFX_EXT_CLASS ,修改后的

类声明如下: class AFX_EXT_CLASS CCalDlg : public CDialog

{

public:

//{{AFX_DATA(CCalDlg)

enum { IDD = IDD_DIALOG1 };

int m_nInput;

int m_nResult;

//}}AFX_DATA

Page 67: 第九章  动态链接库

protected:

// Generated message map functions

//{{AFX_MSG(CCalDlg)

afx_msg void OnProduct();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

其中 AFX_EXT_CLASS 在动态库创建时,被自动替换为导入

修饰 _declspec(dllexport) ;在动态库应用时,被自动替换为导

出修饰 _declspec(dllimport) 。因此,应该为动态链接库添加一

个头文件,声明所有需要导出的函数声明, C++ 类和变量等

的定义,并为这些声明和定义添加 AFX_EXT_CLASS 前缀,

以满足动态链接库在创建和使用时的编译的需要。

Page 68: 第九章  动态链接库

3 为动态链接库添加一个需要导出的字符串资源

在 CalDlg.cpp 中加入 #include “resource.h” ,将该字符串和对

话框模板资源的 ID 定义从 resource.h 头文件复制到 CalDlg.h

中,

以便在使用动态链接库时引用这些资源。

字符串标识 文本内容IDS_HELLOSTR Hi, I am from extdll.

Page 69: 第九章  动态链接库

4 编译 “ extdll” 项目

生成动态链接库文件 extdll.dll 和导入库文件 extdll.lib 。

5 生成测试项目 testdll

⑴ 在当前工作区的属性页 FileView 中,通过环境菜单选择菜单

项“ Add New Project to Workspace” 添加一个名为 “ testdll”

的 SDI

应用程序项目。

⑵ 复制相关的文件:将 extdll.dll 从 ..\extdll\debug\ 复制到

..\extdll\testdll\debug\ 目录中,将 extdll.lib 从 ..\extdll\debug\ 复

制到 ..\extdll\testdll\ 目录中,再将 CalDlg.h 从 ..\extdll\ 目录中复

制到 ..\extdll\testdll\ 目录中。

⑶ 将 extdll.lib 加入到 testdll 项目的链接环境中。

Page 70: 第九章  动态链接库

⑷ 在 CTestdllView 类的实现文件中加入包含 CalDlg.h 的语句,以

便在使用 CCalDlg 类和字符串资源 IDS_HELLOSTR 之前,加

以声明。

#include "stdafx.h"

#include "testdll.h"

#include "testdllDoc.h"

#include "testdllView.h"

#include "CalDlg.h"

Page 71: 第九章  动态链接库

⑸ 在 CTestdllView 类中为 WM_LBUTTONDOWN 消息添加消息映

射和消息响应函数。

afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

ON_WM_LBUTTONDOWN()

void CTestdllView::OnLButtonDown(UINT nFlags, CPoint point)

{

CCalDlg dlg;

dlg.DoModal();

CString str;

str.LoadString(IDS_HELLOSTR);

AfxMessageBox(str);

CView::OnLButtonDown(nFlags, point);

}

Page 72: 第九章  动态链接库

6 编译运行测试项目 “ testdll”

⑴ 按上述代码运行顺序:先通过调用 CCalDlg 链接动态链接库

后,再装载库中的字符串资源,得到了预期运行的结果。

Page 73: 第九章  动态链接库

⑵ 将 OnLButtonDown 的代码修改为如下形式后编译运行。 void CTestdllView::OnLButtonDown(UINT nFlags, CPoint point)

{

CString str;

str.LoadString(IDS_HELLOSTR);

AfxMessageBox(str);

CView::OnLButtonDown(nFlags, point);

}

显然,字符串资源 IDS_HELLOSTR 并没有加载。这是因为动

态链接库的隐式链接加载,至少需要调用了一个动态链接库

的函数或者使用动态链接库的一个类。

Page 74: 第九章  动态链接库

⑶ 应用程序在使用资源时,它将按如下顺序查找资源:

·首先查找应用程序本身的资源,如找到,则查找结束,否

则继续;

·查找 MFC 类动态库的资源,如找到,则查找结束,否则继

续;

·查找应用程序所带的动态链接库中的资源,如找到,则查

找结束,否则报错;

如果想强制使用动态链接库的资源,而不经过上述搜索顺

序,可以通过调用 AfxGetResourceHandle 强制获取资源句

柄,和通过调用 AfxSetResourceHandle 来设置新的资源句柄。

因此,程序中的 OnLButtonDown 函数可以改写为:

Page 75: 第九章  动态链接库

void CTestdllView::OnLButtonDown(UINT nFlags, CPoint point)

{

HINSTANCE hInstOld = AfxGetResourceHandle();

::LoadLibrary("extdll.dll");

AfxSetResourceHandle(::GetModuleHandle(“extdll.dll”));

CString str;

str.LoadString(IDS_HELLOSTR)

AfxSetResourceHandle(hInstOld);

AfxMessageBox(str);

CView::OnLButtonDown(nFlags, point);

}

Page 76: 第九章  动态链接库

也可以使用 FindResource 函数来搜寻资源表,寻找给定的资

源。该函数的原型: HRSRC FindResource(HMODULE hModule, // module handle

LPCTSTR lpName, // pointer to resource name

LPCTSTR lpType // pointer to resource

type );

参数: hModule Handle to the module whose executable file contains the

resource. A value of NULL specifies the module handle

associated with the image file that the operating system used

to create the current process.

lpName Specifies the name of the resource. For more information,

see the Remarks section.

Page 77: 第九章  动态链接库

lpType Specifies the resource type. For more information, see the

Remarks section. For standard resource types, this parameter

can be one of the following values:

Value Meaning

RT_ACCELERATOR

RT_ANICURSOR

RT_ANIICON

RT_BITMAP

RT_CURSOR

RT_DIALOG

RT_FONT

RT_FONTDIR

RT_GROUP_CURSOR

RT_GROUP_ICON

RT_HTML

RT_ICON

RT_MENU

RT_MESSAGETABLE

RT_RCDATA

RT_STRING

RT_VERSION

Accelerator table

Animated cursor

Animated icon

Bitmap resource

Hardware-dependent cursor resource

Dialog box

Font resource

Font directory resource

Hardware-independent cursor resource

Hardware-independent icon resource

HTML document

Hardware-dependent icon resource

Menu resource

Message-table entry

Application-defined resource (raw data)

String-table entry

Version resource

Page 78: 第九章  动态链接库

9.2.4 补充实例 1 : MFC 扩展 DLL 导出持续性框架窗口类 本例的目的是描述如何创建一个用于产生能导出具有持续

特性的通用框架类 CPersistentFrame 、 CPersistentMainFrame 和CPersistentChildFrame 的动态连接库文件。1 创建 MFC 扩展 DLL 项目 使用 AppWizard 创建一个名为 “ PstFrm” 的 MFC DLL 项

目。在Step 1 of 1 中选择 “MFC Extension DLL(using shared MFC DLL)” ,其余

保持默认选择。2 检查 PstFrm.cpp 文件// PstFrm.cpp : Defines the initialization routines for the DLL.

#include "stdafx.h"

#include <afxdllx.h>

Page 79: 第九章  动态链接库

static AFX_EXTENSION_MODULE PstFrmDLL = { NULL, NULL };

extern "C" int APIENTRY

DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserve

d)

{ // Remove this if you use lpReserved

UNREFERENCED_PARAMETER(lpReserved);

if (dwReason == DLL_PROCESS_ATTACH)

{

TRACE0("PSTFRM.DLL Initializing!\n");

// Extension DLL one-time initialization

if (!AfxInitExtensionModule(PstFrmDLL, hInstance)) return 0;

new CDynLinkLibrary(PstFrmDLL);

}

Page 80: 第九章  动态链接库

else if (dwReason == DLL_PROCESS_DETACH)

{

TRACE0("PSTFRM.DLL Terminating!\n");

// Terminate the library before destructors are called

AfxTermExtensionModule(PstFrmDLL);

}

return 1; // ok

}

Page 81: 第九章  动态链接库

3 将导出类插入到工程中

从 Project 菜单中选择 Add To Project ,然后从子菜单中选择

Components And Controls ,定位到文件 Persistent Frame.ogx (该文

件在第五章中已建立)单击 Insert 按钮,把 CPersistentFrame

添加到工程中。

按同样方法将 CPersistentMainFrame 和 CPersistentChildFra

me 类

插入到工程中。当然你也可以采用其它方法,例如重新创建上

述各类。

Page 82: 第九章  动态链接库

4 编辑 persist.h 和 persist.cpp 文件

在 persist.h 中将代码行:

class CPersistentFrame : public CFrameWnd 修改为:

class AFX_EXT_CLASS CPersistentFrame : public CFrameWnd

在 persist1.h 中将代码行:

class CPersistentMainFrame : public CMDIFrameWnd

class CPersistentChildFrame : public CMDIChildWnd 修改为: class AFX_EXT_CLASS CPersistentMainFrame : public CMDIFrameWnd

class AFX_EXT_CLASS CPersistentChildFrame : public CMDIChildWnd

将修改后的 persist1.h 和 persist1.cpp 中的代码合并到修改后的

persist.h 和 persist.cpp 中,并从 PstFrm 项目和 PstFrm 目录中

删除 persist1.h 和 persist1.cpp 。

Page 83: 第九章  动态链接库

5 编译项目 “ PstFrm” 创建动态连接库文件 PstFrm.dll 和导入库文

件 Pstfrm.lib 。6 测试动态连接库⑴ 创建一个客户程序 使用 AppWizard 创建一个项目名为 “ PFrmSDI” 的 SDI

项目。⑵ 复制动态连接库的相关文件 ① 将包含动态连接库的头文件 persist.h 从 PstFrm 目录

中复制 到 PFrmSDI 项目的目录中。 ② 将动态连接库文件 PstFrm.dll 从 ..\Pstfrm\debug 目录中

复制 到系统目录( \Windows\System 或 \Winnt\Sysyem32 )中,

以 便为所有使用该动态链接库的应用程序提供方便。如果只 为该项目使用,也可以将 PstFrm.dll 复制到 ..\PFrmSDI\deb

ug

目录中。

Page 84: 第九章  动态链接库

③ 可以选择将动态连接库的导入库文件 PstFrm.lib 复制到

..\PFrmSDI 目录中。

⑶ 将 CMainFrame 的基类改为 CPersistentFrame

在 MainFrm.h 和 MainFrm.cpp 中,把所有的 CMainFrame

替换为

CPersistentFrame 。同时在 MainFrm.h 中插入包含动态链接库

头文件的代码行:

#include "persist.h"

Page 85: 第九章  动态链接库

⑷ 将动态连接库的导入库加入项目的链接环境中

在 Project 菜单中选择 Settings… ,在 Settings For 下拉列表中选

择 All Configurations ,然后在 Link 选项下,将 PstFrm.lib

填写

到 Object/Library Modules 文本编辑框中。注意,这里填写的

必须是导入库文件所在位置的全路径名,只有将导入库文件

复制到项目目录中,路径名才是可以省略的。

Page 86: 第九章  动态链接库
Page 87: 第九章  动态链接库

当然,你也可以使用另一种方法将动态连接库的导入库添加

到项目的链接环境中:通过 Project -> Add To Project -> Files

菜单项,或者在 Workspace 的 Fileview 中使用环境菜单的菜单

项 Add Files to Project ,将导入库文件加入项目中(在下一个

示例程序中就是使用这种方法)。

⑸ 编译并测试项目 “ PFrmSDI”

测试结果与第五章中的示例程序 PFrmSDI 相同。

Page 88: 第九章  动态链接库

9.2.5 自定义控件 DLL

由于用户自定义控件非常独立,因而在 Windows 的早期,编

程人员就使用 DLL 来实现自定义控件。那时的自定义控件用 C

语言编写,往往配置成独立的 DLL 。如今,你可以在自定义控

件的编写中使用 MFC ,甚至使用 AppWizard 和 ClassWizard ,这

样使编码更为容易。对于自定义控件,使用正规 DLL 更为合

适,因为控件并不需要 C++ 接口,还因为它可被用于任何可接

受自定义控件的开发系统(如 Borland C++ 编译器)。你可以使

用 MFC 动态链接选项,这样得到的 DLL 会很小,装入会很快。

Page 89: 第九章  动态链接库

9.2.5.1 什麽是自定义控件自定义控件 我们已经看到了普通控件和 Microsoft Windows 通用控件的各种应用。自定义控件的行为与普通控件(如编辑框控件)类似,在控件里它给父窗口发出 WM_COMMAND 通知消息并能接收用户定义的消息。资源编辑器允许你在对话框中指定自定义控件的位置。

在设计自定义控件时,你有很大的自由度。你可以在自定义控件的窗口里绘制任何图形和文字(窗口由客户应用程序管理),而且,你可以定义任何通知消息。你可以用 ClassWizard

映射控件的常用 Windows 消息,但你必须手工映射用户定义消息,并在父窗口类中手工映射通知消息。

自定义控件

Page 90: 第九章  动态链接库

9.2.5.2 自定义控件的窗口类 对话框资源模板利用符号化的窗口类名来指定自定义控件。注意,不要把 Win32 窗口类与 MFC C++ 类混淆起来,它们唯

一的可以相同的是名字,一个窗口类通过数据结构进行定义,该结构包含下面的信息:· 类名字·WndProc 函数指针,该函数接收和处理发送给该窗口的消息·其它的属性,如背景刷子等 Win32 API 函数 RegisterClass 把该结构变量拷贝到进程内存,因此进程中的任何函数都可以访问和使用该窗口。当对话框窗口被初始化时, Windows 根据保存在模板里的自定义控件窗口类名,创建自定义控件子窗口。

Page 91: 第九章  动态链接库

假定现在控件窗口的 WndProc 函数在 DLL 中,则当 DL

L 被初

始化 时,通过自定义控件类的 InitInstance 为该控件窗口调用

RegisterClass 在内存中注册自定义控件子窗口,从而创建自定

义控件子窗口。简言之:

·客户程序只知道自定义控件窗口类的名字,它可以用此名构

造自定义控件子窗口;

· 所有自定义控件的代码,包括 WndProc 函数,都在 DLL 中;

·客户程序只需要在创建自定义控件子窗口之前先装入 DLL 。

Page 92: 第九章  动态链接库

9.2.5.3 MFC 库和 WndProc 函数 我们知道 Windows 系统为发送给控件窗口的每一个消息

调用

控件窗口的窗口函数 WndProc 。但我们并不想编写一个老式的

switch – class 结构的 WndProc ,我们希望把这些消息映射到 C+

+

类的成员函数。因此,在控件的 DLL 中,你必须建立一个与自

定义控件窗口对应的 C++ 类。一旦有了这个 C++ 类,你就可以

用 ClassWizard 来映射消息了。

显然,编写自定义控件的 C++ 类,你可以使用 ClassWiza

rd 创

建一个新的 CWnd 派生类。需要使用的技巧是,要把该 C++

与 WndProc 函数及应用程序框架的消息映射相对应。在后面的

示例程序中,我们将会给出一个有实际意义的 WndProc 。

Page 93: 第九章  动态链接库

下面先给出一个典型的控件 WndProc 函数的伪代码:LRESULT MyControlWndProc(HWND hWnd, UINT message,

WPARAM wParam, LPARAM lParam)

{

if(/*this is the first message for this window*/)

{

CWnd* pWnd = new CMyControlWindowClass();

//attach pWnd to hWnd

}

return AfxCallWndProc(pWnd, hWnd, message, wParam, lParam);

}

MFC 的 AfxCallWndProc 函数把消息传给框架,框架把消息分

发给自定义控件 CMyControlWindowClass 中被映射的成员函数。

Page 94: 第九章  动态链接库

9.2.5.4 自定义控件通知消息 自定义控件通过给父窗口发送特定的 WM_COMMAND 通知消

息,与父窗口进行通讯。这些消息可以带参数,如下表所示:

通知码的含义可以任意定义,具体取决于控件的操作。父窗口

根据它对通知码的翻译,理解控件的请求。

消息参数 调用时传递的值(HIWORD)wParam 消息通知码

(LOWORD)lParam 子窗口 ID

LParam 子窗口句柄

Page 95: 第九章  动态链接库

控件可以用下面的方式发送通知消息:GetParent()->SendMessage( WM_COMMAND,

GetDlgCtrlID() | ID_NOTIFYCODE << 16,

( LONG )GetSafeHWnd());

而在客户程序方,你可以用 MFC 的 ON_CONTROL 宏映射消息

将消息映射到响应函数。如下所示:ON_CONTROL( ID_NOTIFYCODE, IDC_MYCONTROL,

OnClickedMyContro

l)

并在自定义控件类中声明该消息响应函数:afx_msg void OnClickedMyControl();

Page 96: 第九章  动态链接库

9.2.5.5 发送给控件的用户定义消息 客户程序可以通过用户定义消息与自定义控件进行通讯。

果一个标准消息是被直接发送( SendMessage )的,而不是被间

接寄送( PostMessage )的,则函数返回一个 32 位值,所以客

户程序可以通过 SendMessge 的返回值从自定义控件获得消息处

理结果。

小结:使用 MFC支持的自定义控件正规 DLL 中应该包括:

Page 97: 第九章  动态链接库

1. 使用 MFC 为自定义控件定义 C++ 窗口类。

2. 在 DLL 的入口函数 CXXXApp::InitInstance 中完成自定义控件的 Windows 窗口的注册。

3. 定义自定义控件的窗口函数,建立自定义控件定义 C++ 窗口类对象与 已注册的 Windows 窗口关联,实现系统对该窗口函数调用到通过自定 义控件定义 C++ 窗口类对象完成调用操作的转换。

4. 定义可以被应用程序调用的 DLL 入口(空)函数,用于隐式导入自定 义控件 DLL 库。

5. 定义自定义控件向应用程序发送的消息通知码和能接收应用程序发来 的用户消息码,并添加相应的消息发送代码和定义相应的消息响应函 数,实现自定义控件与应用程序之间的调用和通讯。

Page 98: 第九章  动态链接库

应用程序使用自定义控件所需要的编程信息:

1. 自定义控件的窗口类名( DLL 中用于注册窗口时使用的类名),用于 设置自定义控件模板的类名属性。

2. 自定义控件能发送的消息通知码,用于在应用程序中添加消息映射和 定义相应的处理函数。

3. 自定义控件能接收的用户消息,用于在应用程序中通过这些消息与自 定义控件通讯,修改状态。

Page 99: 第九章  动态链接库

9.2.5.6 示例程序 —— 自定义控件 本示例程序是一个 MFC 正规 DLL ,它实现一个交通灯自

定义

控件,该控件的功能:

·显示关闭、红灯、黄灯和绿灯四种状态;

· 当鼠标左键单击时,自定义控件就向父窗口发送一个鼠标单

击通知消息;

· 该自定义控件能响应两个用户定义消息 RYG_SETSTATE 和

RYG_GETSTATE 用于设置和获取自定义控件的状态。

1 创建一个正规 DLL 工程

与前面描述过的正规 DLL 的创建过程一样,使用 AppWiz

ard

创建一个项目名为 “ Traffic” 的 MFC 正规 DLL 项目

Page 100: 第九章  动态链接库

2 编辑 Traffic.h 和 Traffic.cpp

⑴ 使用 ClassWizard 为 CTrafficApp 重定义虚函数 InitInstance 用于

在 DLL 初始化时创建自定义控件。为此,必须将自定义控件

类 CRygWnd 的头文件包含到 Traffic.cpp 中。

⑵ 加入虚构的导出函数 TrafficEntry,用于使 DLL 能够被隐式链

接。在客户程序中必须包括对该函数的调用,而且调用必须

在程序的执行路径中,否则编译器会忽略掉该调用。做为另

一种选择,你也可以在 InitInstance 中调用 Win32 API 函数

LaodLibrary 进行 DLL 的显式链接。

Page 101: 第九章  动态链接库

修改后 traffic.h 中的代码: // Traffic.h : main header file for the TRAFFIC DLL

class CTrafficApp : public CWinApp

{ …

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CTrafficApp)

public:

virtual BOOL InitInstance();

//}}AFX_VIRTUAL

};

Page 102: 第九章  动态链接库

修改后 Traffic.cpp 中的代码: …

#include “stdafx.h”

#include “Traffic.h”

#include “RygWnd.h”

extern “C” __declspec(dllexport) void TrafficEntry() {}

// dummy function

CTrafficApp theApp;

BOOL CTrafficApp::InitInstance()

{

CRygWnd::RegisterWndClass(AfxGetInstanceHandle());

return CWinApp::InitInstance();

}

Page 103: 第九章  动态链接库

3 添加自定义控件类 —— CRygWnd

通过 ClassWizard 建立的 CRygWnd 类是 DLL 中实现功能的主

体部分。在所建立的 RygWnd.h 和 RygWnd.cpp 文件中应包括:

· 说明自定义控件状态的数据成员;

·窗口类注册成员函数;

·绘制自定义控件的成员函数;

·响应事件并向父窗口发送消息的成员函数;

·响应由父窗口发出的消息的成员函数;

· 构造、析构及相关成员函数;

· 系统回调函数。

Page 104: 第九章  动态链接库

⑴ 数据成员:

在 RygWnd.h 中添加:

private:

int m_nState; // 0=off, 1=red, 2=yellow, 3=green

static CRect s_rect;

static CPoint s_point;

static CRect s_rColor[3];

static CBrush s_bColor[4];

Page 105: 第九章  动态链接库

在 RygWnd.cpp 中添加:

除 m_nState 在构造函数中被设置外,其它静态数据成员在类

外设置为: CRect CRygWnd::s_rect(-500, 1000, 500, -1000); // outer rectangle

CPoint CRygWnd::s_point(300, 300); // rounded corners

CRect CRygWnd::s_rColor[] = { CRect(-250, 800, 250, 300),

CRect(-250, 250, 250, -250),

CRect(-250, -300, 250, -800) };

CBrush CRygWnd::s_bColor[] = { RGB(192, 192, 192),

RGB(0xFF, 0x00, 0x00),

RGB(0xFF, 0xFF, 0x00),

RGB(0x00, 0xFF, 0x00) };

Page 106: 第九章  动态链接库

⑵ 窗口类注册函数:

该函数用于对 RYG 窗口的注册,与 CRygWnd 对象关联,因

此它必须在 DLL 初始化时被调用 (见 CTrafficApp::InitInsta

nce) 。

在 RygWnd.h 中添加函数声明: Static BOOL RegisterWndClass(HINSTANCE hInstance);

在 RygWnd.cpp 中添加函数的实现代码:

Page 107: 第九章  动态链接库

BOOL CRygWnd::RegisterWndClass(HINSTANCE hInstance)

{

WNDCLASS wc;

wc.lpszClassName = “RYG”; // matches class name in client

wc.hInstance = hInstance;

wc.lpfnWndProc = RygWndProc;

wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);

wc.hIcon = 0;

wc.lpszMenuName = NULL;

wc.hbrBackground=(HBRUSH)::GetStockObject(LTGRAY_BRUSH);

wc.style = CS_GLOBALCLASS;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

return (::RegisterClass(&wc) != 0);

}

Page 108: 第九章  动态链接库

⑶ 绘制自定义控件(交通灯)的函数:

在 RygWnd.h 中添加函数声明:

private:

void SetMapping(CDC* pDC);

void UpdateColor(CDC* pDC, int n);

protected:

afx_msg void OnPaint();

在 RygWnd.cpp 中添加函数的实现代码:

Page 109: 第九章  动态链接库

void CRygWnd::SetMapping(CDC* pDC)

{

CRect clientRect;

GetClientRect(clientRect);

pDC->SetMapMode(MM_ISOTROPIC);

pDC->SetWindowExt(1000, 2000);

pDC->SetViewportExt(clientRect.right, -clientRect.bottom);

pDC->SetViewportOrg(clientRect.right / 2, clientRect.bottom / 2);

}

Page 110: 第九章  动态链接库

void CRygWnd::UpdateColor(CDC* pDC, int n)

{

if (m_nState == n + 1)

pDC->SelectObject(&s_bColor[n+1]);

else

pDC->SelectObject(&s_bColor[0]);

pDC->Ellipse(s_rColor[n]);

}

Page 111: 第九章  动态链接库

void CRygWnd::OnPaint()

{

int i;

CPaintDC dc(this); // device context for painting

SetMapping(&dc);

dc.SelectStockObject(DKGRAY_BRUSH);

dc.RoundRect(s_rect, s_point);

for (i = 0; i < 3; i++)

UpdateColor(&dc, i);

}

Page 112: 第九章  动态链接库

⑷ 响应事件并向父窗口发送消息的函数:

该函数是响应鼠标左键单击自定义控件事件的处理函数。它

将向它的父窗口发送一个窗口命令消息。

在 RygWnd.h 中添加函数声明: afx_msg void OnLButtonDown(UINT nFlags, Cpoint point);

在 RygWnd.cpp 中添加函数实现代码: void CRygWnd::OnLButtonDown(UINT nFlags, Cpoint point)

{

// Notification code is HIWORD of wParam, 0 in this case

GetParent()->SendMessage(WM_COMMAND, GetDlgCtrlID(),

(LONG) GetSafeHwnd());

}

Page 113: 第九章  动态链接库

⑸ 响应由父窗口发出的消息的成员函数:

该类函数用于响应父窗口发送到自定义控件窗口的用户定义

消息,以便获取和设置自定义控件的状态导致重画操作。

在 RygWnd.h 中添加函数声明和用户定义消息:

#define RYG_SETSTATE WM_USER + 0

#define RYG_GETSTATE WM_USER + 1

afx_msg LRESULT OnSetState(WPARAM wParam, LPARAM lParam);

afx_msg LRESULT OnGetState(WPARAM wParam, LPARAM lParam);

Page 114: 第九章  动态链接库

在 RygWnd.cpp 中添加函数实现代码: LRESULT CRygWnd::OnSetState(WPARAM wParam, LPARAM lParam)

{

TRACE(“CRygWnd::SetState, wParam = %d\n”, wParam);

m_nState = (int) wParam;

Invalidate(FALSE);

return 0L;

}

LRESULT CRygWnd::OnGetState(WPARAM wParam, LPARAM lParam)

{

TRACE(“CRygWnd::GetState\n”);

return m_nState;

}

Page 115: 第九章  动态链接库

⑹ 构造、析构及相关成员函数:

其中 PostNcDestroy 很重要,因为当客户程序删除控件窗口

时,该函数将删除与自定义控件窗口关联的 CRygWnd 对象。

在 RygWnd.h 中添加函数声明: CRygWnd();

virtual ~CRygWnd();

virtual void PostNcDestroy(); // 重载

在 RygWnd.cpp 中添加函数实现代码: CRygWnd::CRygWnd()

{

m_nState = 0;

TRACE("CRygWnd constructor\n");

}

Page 116: 第九章  动态链接库

CRygWnd::~CRygWnd()

{

TRACE("CRygWnd destructor\n");

}

void CRygWnd::PostNcDestroy()

{

TRACE("CRygWnd::PostNcDestroy\n");

delete this; // CWnd::PostNcDestroy does nothing

}

Page 117: 第九章  动态链接库

⑺ 系统回调函数

在 RygWnd.h 中添加函数声明: LRESULT CALLBACK AFX_EXPORT

RygWndProc(HWND hWnd, UINT message,

WPARAM wParam, LPARAM lParam);

在 RygWnd.cpp 中添加函数实现代码: RygWndProc(HWND hWnd, UINT message,

WPARAM wParam, LPARAM lParam)

{

AFX_MANAGE_STATE(AfxGetStaticModuleState());

CWnd* pWnd;

pWnd = CWnd::FromHandlePermanent(hWnd);

Page 118: 第九章  动态链接库

if (pWnd == NULL)

{

// Assume that client created a CRygWnd window

pWnd = new CRygWnd();

pWnd->Attach(hWnd);

}

ASSERT(pWnd->m_hWnd == hWnd);

ASSERT(pWnd == CWnd::FromHandlePermanent(hWnd));

LRESULT lResult = AfxCallWndProc(pWnd, hWnd, message,

wParam, lParam);

return lResult;

}

Page 119: 第九章  动态链接库

4 编译连接生成 DLL

编译项目 “ Traffic” 创建名为 Traffic.dll 的自定义控件动态链接

库文件和名为 Traffic.lib 自定义控件导入库文件。然后将 Traffic.

dll

复制到 ..\PFrmSDI\Debug 目录中,并将导入库文件 Traffic.lib 复制

到 ..\PFrmSDI 目录中。

Page 120: 第九章  动态链接库

5 修改 PFrmSDI 示例程序

⑴ 添加对话框和相应的类:

用资源编辑器创建对话框模板 IDD_TRAFFIC ,如下图所示:

然后用 ClassWizard 定义 CDialog 的派生类 CTrafficDlg 。

Page 121: 第九章  动态链接库

⑵ 编辑 TrafficDlg.h 文件:

加入私有数据成员:

private:

enum { OFF, RED, YELLOW, GREEN } m_nState;

同时加入下面的导入函数和用户定义消息:

extern "C" __declspec(dllimport) void TrafficEntry(); // dummy function

#define RYG_SETSTATE WM_USER + 0

#define RYG_GETSTATE WM_USER + 1

Page 122: 第九章  动态链接库

⑶ 编辑 CTrafficDlg 的构造函数:

CTrafficDlg::CTrafficDlg(CWnd* pParent /*=NULL*/)

: CDialog(CTrafficDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CTrafficDlg)

// NOTE: the ClassWizard will add member initialization here

//}}AFX_DATA_INIT

m_nState = OFF;

TrafficEntry(); // Make sure DLL gets loaded

}

Page 123: 第九章  动态链接库

⑷ 映射自定义控件的单击通知消息:

由于不能使用 ClassWizard ,所以必须在 TrafficDlg.cpp 文件中

手工加入消息映射和响应函数的声明及定义如下:

在 TrafficDlg.h 中添加响应函数的声明: afx_msg void OnClickedRyg();

在 TrafficDlg.cpp 中添加消息映射和响应函数定义:

ON_CONTROL(0, IDC_RYG, OnClickedRyg)

// Notification code is 0

Page 124: 第九章  动态链接库

void CTrafficDlg::OnClickedRyg()

{

switch(m_nState)

{

case OFF:

m_nState = RED; break;

case RED:

m_nState = YELLOW; break;

case YELLOW:

m_nState = GREEN; break;

case GREEN:

m_nState = OFF;

}

GetDlgItem(IDC_RYG)->SendMessage(RYG_SETSTATE, m_nState);

return;

}

Page 125: 第九章  动态链接库

⑸ 添加菜单项调用交通灯测试对话框: 在菜单资源中添加子菜单 Test ,该子菜单中只包含标识值为

ID_TEST_TRAFFICDLL 的菜单项 Traffic DLL 如下所示:

在 PFrmSDIView.h 中添加菜单消息响应函数的声明: afx_msg void OnTestTrafficdll();

在 PFrmSDIView.cpp 中添加菜单消息映射和响应函数的定义:

ON_COMMAND(ID_TEST_TRAFFICDLL, OnTestTrafficdll)

void CPFrmSDIView::OnTestTrafficdll()

{

CTrafficDlg dlg;

dlg.DoModal();

}

Page 126: 第九章  动态链接库

⑹ 在项目的链接环境中添加 Traffic 的导入库:

通过 Project -> Settings…弹出 Project Settings 属性表。在属性

表的 Link 属性页的 Object/Library Modules 中添加 Traffic.li

b 。

6 编译并测试修改的 “ PFrmSDI” 应用程序:

从 Test 菜单中选择 Traffic DLL ,在“ Traffic Test” 对话框中单击

自定义控件观察它的变化情况。