这里要提到的一点是,在用COM和ATL前,程序员一般用.DLLs来代替它们. 你可以用.DLL做很多事.如果你有几个程序要用到相同的函数或资源,你可以将代码放到一个.DLL中.将多个程序要共同用到的代码放到一个简单的.DLL中可以节省维护时间,因为代码就在一个地方.:)修理和其他的改动最多做一次就够了.如果你有一个在不同时间用不同程序的程序,你应该把这些程序做成.DLLs,根据需要的导入相应的.DLL.有很多理由要用到.DLLS.
虽然用.DLL能做的COM全能做,但是仍有很多好原因使得我们要用.DLLs,所以它们没有消失.但是.DLLs还是有很多严重缺点的,这些严重的缺点就是我们为什么会首先想到用COM.但.DLLs仍然是很实用的工具.对照COM和ATL,.DLLs是非常简单的来实现的.学习COM和ATL需要投入大量的时间和努力.实现.DLL却相对简单,修改起来也不难.如果你会一些C++和MFC,你现在就可以实现.DLLs.
这篇文章回顾一下用MFC实现.DLLs的几种形式,包括何时用和怎么用各种形式.在下一篇将讨论.DLLs的局限性(这就是为何会出现COM和ATL)
Different types of .DLLs
可以用MFC来实现两种.DLLs:一个是MFC扩展.DLL,一个是正规的.DLLs.正规的.DLLs有两种实现方式:dynamically linked 或者 statically linked.Visual C++也允许你用generic Win32 .DLL,但本文我们只讨论以MFC为基础的.DLL形式.
MFC extension .DLLs
每一个.DLL都有一些接口.接口是一套变量,指针,函数或者是类,可以通过客户程序来访问.MFC的扩展.DLL有一个C++形式的接口,也就是说它提供给客户程序("export")C++函数或者整个类.导出函数可以用C++或者MFC的数据形式作为参数或返回值.当导出整个类,客户程序可以创建此类的对象或者派生于此类.在.DLL中,你也可以用MFC和C++.
Visual C++用的MFC的类代码库也存在于.DLL中.一个扩展.DLL是动态连接到MFC的代码库的.DLL的.客户程序也必须动态的连接到MFC的代码库.随着时间的推移,MFC的库也在增长.结果,就有了几个不同的MFC的代码库的版本.客户端和扩展的.DLL必须建立在相同版本的MFC上.因此,一个MFC扩展的.DLL要运行,客户端和此扩展的.DLL必须动态的连到相同的MFC代码库的.DLL上,并且,此库还得在该程序运行的机器上好使.
注意:如果你的程序静态的连接到MFC,但是你希望改变它便于从一个扩展的.DLL中访问函数,这时,你要改此应用程序为动态连接到MFC.在Visual C++,在菜单中选"Project | Settings",在"General"设置标签中可以把你的程序改成动态连接到MFC.
MFC扩展.DLLs非常小.你可以建立一个导出一些函数或者类的大约10-15KB的.DLL.显然的,你的.DLL的大小要以你要存多少代码到你的.DLL中为准.但是通常MFC扩展的.DLLs是相对小和快捷的.
Regular .DLLs
MFC的扩展.DLL只能工作在用MFC编的客户程序上.如果你的.DLL可以被大多数的Win32程序导入和运行,你应改选择正规的.DLL.但你只能导出C-style函数.你不能导出类.你不能导出C++函数或overloaded函数.不能用MFC的数据类型作为参数和返回值.但可以在你的.DLL中用C++和MFC,但是你的接口必须全是C-style.
当然正规的.DLL也要访问MFC的代码库的.DLL.可以动态,可以静态连接.如果动态连接,意味着你的.DLL函数所需的MFC代码不用建立在你的.DLL中,你的.DLL会从你的客户端的机器上MFC的代码库的.DLL中取得所需要的代码.如果正确的MFC代码库的.DLL版本没找到,你的.DLL将不能工作.像MFC扩展的.DLL一样,正规的.DLL也非常小,只能在客户端所在的机器有MFC代码库的.DLL情况下工作.
如果你静态连接到MFC代码库,你的.DLL包括它自己的所有的所需的MFC代码.那么,它将非常庞大,但是它不依赖于客户端的电脑配置.如果你不知道主机的机器配置情况,这是一个很好的方法.如果你的客户在你的公司范围内,你可以知道他们的MFC .DLL的配置情况,或者你的安装程序带了正确版本的MFC .DLL,那么静态连接就不是一个好方法了.
Building a .DLL
可以用App Wizard来实现以MFC为基础的.DLL.选择"File | New",在"Projects"标签上,选择"MFC AppWizard (.DLL).",为你的工程选一个名字,然后单击"OK".在下一个屏幕,选择建立一个MFC扩展的.DLL,或者正规的.DLL"using shared MFC .DLL"(就是动态连接到MFC),或者正规的.DLL(静态的连接到MFC).选择其中一个,按"Finish".
App Wizard新建立的.DLL没做任何事.编译新的.DLL,但是它不导出任何类和函数,本质上来说,没有任何用,你现在有两个工作:
1.增加函数.
2.修改客户端来调用你的.DLL.
Export a Class
上面提到,只有MFC的扩展.DLL能导出MFC/C++类.假设你建立了一个扩展的.DLL,你可以通过从另一个工程加入.cpp和.h文件来创建一个类,也可以在你的工程中创建新类.要导出这个新类,你必须在类的声明前加一个宏"AFX_EXT_CLASS",像这样:
class AFX_EXT_CLASS CMyClass
{
//class declaration goes here
};
还用一种方法来导出一个类,很简单而且很好,我会在下面讨论客户端如何做才能用上你的导出类提到.
Export objects and variables
代替导出整个类的方法,你可以导出该类在.DLL中的对象.客户端程序可以调用所有的该导出对象的公有函数和访问它的公有成员变量.
首先,创建一个.h文件,定义了你的新类.然后创建实现你的新类的.cpp文件.在.cpp文件底部,在你所有的公有的,私有的类函数后,创建一个这样的类的实例:
_declspec(dllexport) CMyClass myObject;
这行的作用是创建一个CMyClass类的实例,当客户端导入.DLL时,使得客户端可以访问该实例.通过该实例,访问它的公有函数和成员编量.注意,每个客户端导入.DLL将获得该实例的一个拷贝,也就是说,如果不同的程序访问相同的.DLL,如果一个程序改变了该实例,不会影响另一个程序.
除了导出实例,你可以用同样的方法导出变量.如果你加上此行:
_declspec(dllexport) int x;
就可以导出变量,为客户端应用.下面说的很重要:你只能导出全局的实例或者变量.局部的实例或者变量当它们跑出作用域,就会停止生存.如果用下面的方法,将不会正常工作:
MyFunction( )
{
_declspec(dllexport) CMyClass myObject;
_declspec(dllexport) int x;
}
一旦实例或者变量跑出作用域,它们将停止生存.
Export a function
导出函数和导出变量是很相似的.你可以简单的在函数前面加上"_declspec(dllexport)"
_declspec(dllexport) int MyExportedFunction(int);
这就是导出的全部.记住,只有MFC扩展的.DLL能导出C++函数或者以MFC的数据类型为参数或者返回值.正规的.DLLs只能导出C-style函数.
Using the .DLL in a client application
一个.DLL不能运行它自己.它需要客户端导入它,调用它的接口.
当你编译你的.DLL时,编译器创建两个很重要的文件: .DLL文件和.lib文件.你的客户端需要这两个文件.你必须拷贝它们到客户端的工程文件夹.
除了.DLL和.lib文件,你的客户端还需要要导出的类,函数,实例和变量所在的头文件.要导出函数时要加"_declspec(dllexport)"声明.现在要导入了,就要加入"_declspec(dllimport)"声明.如下:
_declspec(dllimport) CMyClass myObject;
_declspec(dllimport) int x;
_declspec(dllimport) int MyExportedFunction(int);
为了可读性,我们可以这样写:
#define DLLIMPORT _declspec(dllimport)
DLLIMPORT CMyClass myObject;
DLLIMPORT int x;
DLLIMPORT int MyExportedFunction(int);
现在你声明了你的实例,变量和函数,可以用了.:)
要导出整个类,你必须将整个.h头文件拷过来..DLL和客户端要有唯一的关于此导出类的头文件.记住,类的声明要加上: AFX_EXT_CLASS 宏.
一旦你建立了客户端,你已经准备给客户用了,你应该给他们你的Release可执行文件和Release的.DLL.不用给用户.lib文件..DLL可以放在客户程序的目录,或者系统目录.还有上面提到的,你要提供正确的MFC代码库的.DLL.这个.DLL是你的机器装Visual C++时候用的.
10:19 | 固定链接 | 评论 (0) | 引用通告 (0) | 记录它 | 计算机与 Internet
固定链接 关闭
http://spaces.msn.com/members/frognet/Blog/cns!1prCJXmkV2kTuhKawm-uIC-A!108.entry
MFC .DLL指南(二)
我们上节讨论的结果是.DLLs对于任何的程序员都是非常实用的工具.然而,使用他们却有很多限制,任何人在作的时候都要意识到这点.
MFC Issues
在上一节已经提到了这个,但是很有再一次提的价值.MFC扩展的.DLL只能在和客户端的程序用相同的MFC和正确的MFC的代码库的情况下才好使.正规的.DLL也是如此.
Compiler Incompatibility Issues
一个很重要的问题就是在以C++为基础的.DLLs,当它们建立在某一个编译器上,而调用它们的客户端却建立在另一个编译器上,通常情况下,再多的努力,它也不会工作.:(
ANSI协会制定了C和C++的语言的标准.也就是说,它指定了C和C++的函数和数据类型必须由一个编译器来支持.但是它并没有提供一个完整的基于二进制级的关于如何用函数和数据类型的实现.结果,编译器厂商就根据自己的方式来自由的实现其语言功能.
很多的C/C++程序员知道不同的编译器操作的数据类型是不同的.一个编译器为int型变量分配2bytes,但另一个也许会分配4bytes.一个会用4bytes的double,另一个可能用8bytes的.在函数实现和操作符重载方面就有更大的差别了.不同的编译器的差别比你想的还要多,所以,这些不同使你的.DLL不能运行于某些的程序上.
编译器的不兼容问题可以通过插入pragmas和其他的重编译说明到你的代码来解决,但是很难,而且不易读.但是用到不兼容的编译器确实是不可避避免的.解决编译器不兼容问题的最好的方法是让你的.DLL导出一个简单的接口类,让它指回你的.DLL,我们将在下面讨论.
Recompiling
让我们假设你建立了内含名为CMyClass的类的.DLL.当一个客户程序连到你的.DLL,.DLL创建这个类的实例并导出这个实例.假设你的导出实例为30字节.
现在假设你对CMyClass做了一些改动,加了一个int型变量,这样你的导出的CMyClass的实例就由原来的30字节变成了34字节,你将新的.DLL给用户,让它替代原来的.现在,错误来了,客户程序期待一个30字节的实例,但是你的新的.DLL却送来了34字节的实例,客户程序将抛出异常.
客户程序并不需要改变代码,所有要做的就是导入一个CMyClass类的实例.在代码的某处,加上这行:
_declspec(dllimport) CMyClass myObject;
客户程序的代码不需任何改变,只需要重编译客户程序来解决此问题,重编译后,客户程序将等待34字节的实例.
这是很严重的问题,不重新编译客户端而只在改变.DLL后将此.DLL重置是我们的目标.然而,你的.DLL要导出整个类或一个类的实例,那么此目标是不可能实现的.你必须重编译客户端.如果你没有客户端的源代码,你将使用不了新的.DLL.
Solutions
如果对上面的问题有一个很好的解决,那么我们不会用COM了.这里有一些建议:
尽可能的用MFC的扩展的.DLLs.虽然这样限制了你的客户程序的类型,但是解决了编译器不兼容的问题.
如果你的.DLL导出类或者类的实例,你不得不在修改了.DLL后重新编译你的客户端.为了避免这样,你必须做到分解你要导出的类,实现导出类的一个接口.最好的方法是,创建一个作为第一个类的接口的类,这样你改变了导出类的话,接口类不变,客户程序无需重新编译.
这里有一个例子.假设你要导出CMyClass类.CMyClass有两个公有函数,int FunctionA(int)和int FunctionB(int).替代导出CMyClass,我将创建一个导出接口CMyInterface.CMyInterface将含有一个指向CMyClass的实例.这里给出它的头文件:
class AFX_EXT_CLASS CMyInterface
{
class CMyClass; //forward declaration of CMyClass
CMyClass *m_pMyClass;
public:
CMyInterface( );
~CMyInterface( );
int FunctionA(int);
int FunctionB(int);
};
这份头文件将用在.DLL和客户端程序.注意,前面的声明意味着没有CMyClass的备份也可以编译.
在.DLL内部,这样实现CMyInterface:
CMyInterface::CMyInterface( )
{
m_pMyClass = new CMyClass;
}
~CMyInterface::~CMyInterface( )
{
delete m_pMyClass;
}
CMyInterface::FunctionA( )
{
return m_pMyClass->FunctionA( );
}
CMyInterface::FunctionB( )
{
return m_pMyClass->FunctionB( );
}
因此,CMyClass的每一个函数,CMyInterface将提供相应的函数.客户程序将和客户程序没有联系.如果它想调用CMyClass::FunctionA,只需调用CMyInterface::FunctionA.接口类会用指针调用CMyClass.用这种布局你可以改变CMyClass了----不用担心CMyClass的大小变了.CMyInterface的接口的大小不变.即使你给CMyClass加了一个私有变量,CMyInterface的大小也不会变.要是你加了公有成员,就在CMyInterface里边直接加上对应新变量的"getter" 和 "setter" 函数,不用担心,加入新的函数,CMyInterface接口类的大小不会改变.
建立一个单独的接口可以避免编译器不兼容,客户端重编译的问题.只要接口类不变,就不需重编译.但仍然有两个小问题,一:对于每一个CMyClass的公有的成员变量,你必须在CMyInterface里创建实际的对应的函数或变量.这个例子中只有两个函数,所以很简单.如果CMyClass有成千上万的函数和变量,这将变得很困难,而且易错.二:你将增大进程的开销.客户程序不再直接访问CMyClass,替代的通过访问CMyInterface来访问CMyClass.如果一个函数要被调用成千次,那此进程将会耗用很长时间.
10:19 | 固定链接 | 评论 (0) | 引用通告 (0) | 记录它 | 计算机与 Internet
固定链接 关闭
http://spaces.msn.com/members/frognet/Blog/cns!1prCJXmkV2kTuhKawm-uIC-A!107.entry
MFC的DLL 概述
DLL的背景知识
静态链接和动态链接
当前链接的目标代码(.obj)如果引用了一个函数却没有定义它,链接程序可能通过两种途径来解决这种从外部对该函数的引用:
静态链接
链接程序搜索一个或者多个库文件(标准库.lib),直到在某个库中找到了含有所引用函数的对象模块,然后链接程序把这个对象模块拷贝到结果可执行文件(.exe)中。链接程序维护对该函数的所有引用,使它们指向该程序中现在含有该函数拷贝的地方。
动态链接
链接程序也是搜索一个或者多个库文件(输入库.lib),当在某个库中找到了所引用函数的输入记录时,便把输入记录拷贝到结果可执行文件中,产生一次对该函数的动态链接。这里,输入记录不包含函数的代码或者数据,而是指定一个包含该函数代码以及该函数的顺序号或函数名的动态链接库。
当程序运行时,Windows装入程序,并寻找文件中出现的任意动态链接。对于每个动态链接,Windows装入指定的DLL并且把它映射到调用进程的虚拟地址空间(如果没有映射的话)。因此,调用和目标函数之间的实际链接不是在链接应用程序时一次完成的(静态),相反,是运行该程序时由Windows完成的(动态)。
这种动态链接称为加载时动态链接。还有一种动态链接方式下面会谈到。
动态链接的方法
链接动态链接库里的函数的方法如下:
加载时动态链接(Load_time dynamic linking)
如上所述。Windows搜索要装入的DLL时,按以下顺序:
应用程序所在目录→当前目录→Windows SYSTEM目录→Windows目录→PATH环境变量指定的路径。
运行时动态链接(Run_time dynamic linking)
程序员使用LoadLibrary把DLL装入内存并且映射DLL到调用进程的虚拟地址空间(如果已经作了映射,则增加DLL的引用计数)。首先,LoadLibrary搜索DLL,搜索顺序如同加载时动态链接一样。然后,使用GetProcessAddress得到DLL中输出函数的地址,并调用它。最后,使用FreeLibrary减少DLL的引用计数,当引用计数为0时,把DLL模块从当前进程的虚拟空间移走。
输入库(.lib):
输入库以.lib为扩展名,格式是COFF(Common object file format)。COFF标准库(静态链接库)的扩展名也是.lib。COFF格式的文件可以用dumpbin来查看。
输入库包含了DLL中的输出函数或者输出数据的动态链接信息。当使用MFC创建DLL程序时,会生成输入库(.lib)和动态链接库(.dll)。
输出文件(.exp)
输出文件以.exp为扩展名,包含了输出的函数和数据的信息,链接程序使用它来创建DLL动态链接库。
映像文件(.map)
映像文件以.map为扩展名,包含了如下信息:
模块名、时间戳、组列表(每一组包含了形式如section::offset的起始地址,长度、组名、类名)、公共符号列表(形式如section::offset的地址,符号名,虚拟地址flat address,定义符号的.obj文件)、入口点如section::offset、fixup列表。
lib.exe工具
它可以用来创建输入库和输出文件。通常,不用使用lib.exe,如果工程目标是创建DLL程序,链接程序会完成输入库的创建。
更详细的信息可以参见MFC使用手册和文档。
链接规范(Linkage Specification )
这是指链接采用不同编程语言写的函数(Function)或者过程(Procedure)的链接协议。MFC所支持的链接规范是“C”和“C++”,缺省的是“C++”规范,如果要声明一个“C”链接的函数或者变量,则一般采用如下语法:
#if defined(__cplusplus)
extern "C"
{
#endif
//函数声明(function declarations)
…
//变量声明(variables declarations)
#if defined(__cplusplus)
}
#endif
所有的C标准头文件都是用如上语法声明的,这样它们在C++环境下可以使用。
修饰名(Decoration name)
“C”或者“C++”函数在内部(编译和链接)通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字符串。有些情况下使用函数的修饰名是必要的,如在模块定义文件里头指定输出“C++”重载函数、构造函数、析构函数,又如在汇编代码里调用“C””或“C++”函数等。
修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。
调用约定
调用约定(Calling convention)决定以下内容:函数参数的压栈顺序,由调用者还是被调用者把参数弹出栈,以及产生函数修饰名的方法。MFC支持以下调用约定:
_cdecl
按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于“C”函数或者变量,修饰名是在函数名前加下划线。对于“C++”函数,有所不同。
如函数void test(void)的修饰名是_test;对于不属于一个类的“C++”全局函数,修饰名是?test@@ZAXXZ。
这是MFC缺省调用约定。由于是调用者负责把参数弹出栈,所以可以给函数定义个数不定的参数,如printf函数。
_stdcall
按从右至左的顺序压参数入栈,由被调用者把参数弹出栈。对于“C”函数或者变量,修饰名以下划线为前缀,然后是函数名,然后是符号“@”及参数的字节数,如函数int func(int a, double b)的修饰名是_func@12。对于“C++”函数,则有所不同。
所有的Win32 API函数都遵循该约定。
_fastcall
头两个DWORD类型或者占更少字节的参数被放入ECX和EDX寄存器,其他剩下的参数按从右到左的顺序压入栈。由被调用者把参数弹出栈,对于“C”函数或者变量,修饰名以“@”为前缀,然后是函数名,接着是符号“@”及参数的字节数,如函数int func(int a, double b)的修饰名是@func@12。对于“C++”函数,有所不同。
未来的编译器可能使用不同的寄存器来存放参数。
thiscall
仅仅应用于“C++”成员函数。this指针存放于CX寄存器,参数从右到左压栈。thiscall不是关键词,因此不能被程序员指定。
naked call
采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call不产生这样的代码。
naked call不是类型修饰符,故必须和_declspec共同使用,如下:
__declspec( naked ) int func( formal_parameters )
{
// Function body
}
过时的调用约定
原来的一些调用约定可以不再使用。它们被定义成调用约定_stdcall或者_cdecl。例如:
#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
表7-1显示了一个函数在几种调用约定下的修饰名(表中的“C++”函数指的是“C++”全局函数,不是成员函数),函数原型是void CALLTYPE test(void),CALLTYPE可以是_cdecl、_fastcall、_stdcall。
表7-1 不同调用约定下的修饰名
调用约定
extern “C”或.C文件
.cpp, .cxx或/TP编译开关
_cdecl
_test
?test@@ZAXXZ
_fastcall
@test@0
?test@@YIXXZ
_stdcall
_test@0
?test@@YGXXZ
MFC的DLL应用程序的类型
静态链接到MFC的规则DLL应用程序
该类DLL应用程序里头的输出函数可以被任意Win32程序使用,包括使用MFC的应用程序。输入函数有如下形式:
extern "C" EXPORT YourExportedFunction( );
如果没有extern “C”修饰,输出函数仅仅能从C++代码中调用。
DLL应用程序从CWinApp派生,但没有消息循环。
动态链接到MFC的规则DLL应用程序
该类DLL应用程序里头的输出函数可以被任意Win32程序使用,包括使用MFC的应用程序。但是,所有从DLL输出的函数应该以如下语句开始:
AFX_MANAGE_STATE(AfxGetStaticModuleState( ))
此语句用来正确地切换MFC模块状态。关于MFC的模块状态,后面第9章有详细的讨论。
其他方面同静态链接到MFC的规则DLL应用程序。
扩展DLL应用程序
该类DLL应用程序动态链接到MFC,它输出的函数仅可以被使用MFC且动态链接到MFC的应用程序使用。和规则DLL相比,有以下不同:
它没有一个从CWinApp派生的对象;
它必须有一个DllMain函数;
DllMain调用AfxInitExtensionModule函数,必须检查该函数的返回值,如果返回0,DllMmain也返回0;
如果它希望输出CRuntimeClass类型的对象或者资源(Resources),则需要提供一个初始化函数来创建一个CDynLinkLibrary对象。并且,有必要把初始化函数输出。
使用扩展DLL的MFC应用程序必须有一个从CWinApp派生的类,而且,一般在InitInstance里调用扩展DLL的初始化函数。
为什么要这样做和具体的代码形式,将在后面9.4.2节说明。
MFC类库也是以DLL的形式提供的。通常所说的动态链接到MFC 的DLL,指的就是实现MFC核心功能的MFCXX.DLL或者MFCXXD.DLL(XX是版本号,XXD表示调试版)。至于提供OLE(MFCOXXD.DLL或者MFCOXX0.DLL)和NET(MFCNXXD.DLL或者MFCNXX.DLL)服务的DLL就是动态链接到MFC核心DLL的扩展DLL。
其实,MFCXX.DLL可以认为是扩展DLL的一个特例,因为它也具备扩展DLL的上述特点。
DLL的几点说明
DLL应用程序的入口点是DllMain。
对程序员来说,DLL应用程序的入口点是DllMain。
DllMain负责初始化(Initialization)和结束(Termination)工作,每当一个新的进程或者该进程的新的线程访问DLL时,或者访问DLL的每一个进程或者线程不再使用DLL或者结束时,都会调用DllMain。但是,使用TerminateProcess或TerminateThread结束进程或者线程,不会调用DllMain。
DllMain的函数原型符合DllEntryPoint的要求,有如下结构:
BOOL WINAPI DllMain (HANDLE hInst,
ULONG 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:
...
}
return TRUE;
}
其中:
参数1是模块句柄;
参数2是指调用DllMain的类别,四种取值:新的进程要访问DLL;新的线程要访问DLL;一个进程不再使用DLL(Detach from DLL);一个线程不再使用DLL(Detach from DLL)。
参数3保留。
如果程序员不指定DllMain,则编译器使用它自己的DllMain,该函数仅仅返回TRUE。
规则DLL应用程序使用了MFC的DllMain,它将调用DLL程序的应用程序对象(从CWinApp派生)的InitInstance函数和ExitInstance函数。
扩展DLL必须实现自己的DllMain。
_DllMainCRTStartup
为了使用“C”运行库(CRT,C Run time Library)的DLL版本(多线程),一个DLL应用程序必须指定_DllMainCRTStartup为入口函数,DLL的初始化函数必须是DllMain。
_DllMainCRTStartup完成以下任务:当进程或线程捆绑(Attach)到DLL时为“C”运行时的数据(C Runtime Data)分配空间和初始化并且构造全局“C++”对象,当进程或者线程终止使用DLL(Detach)时,清理C Runtime Data并且销毁全局“C++”对象。它还调用DllMain和RawDllMain函数。
RawDllMain在DLL应用程序动态链接到MFC DLL时被需要,但它是静态的链接到DLL应用程序的。在讲述状态管理时解释其原因。
DLL的函数和数据
DLL的函数分为两类:输出函数和内部函数。输出函数可以被其他模块调用,内部函数在定义它们的DLL程序内部使用。
虽然DLL可以输出数据,但一般的DLL程序的数据仅供内部使用。
DLL程序和调用其输出函数的程序的关系
DLL模块被映射到调用它的进程的虚拟地址空间。
DLL使用的内存从调用进程的虚拟地址空间分配,只能被该进程的线程所访问。
DLL的句柄可以被调用进程使用;调用进程的句柄可以被DLL使用。
DLL使用调用进程的栈。
DLL定义的全局变量可以被调用进程访问;DLL可以访问调用进程的全局数据。使用同一DLL的每一个进程都有自己的DLL全局变量实例。如果多个线程并发访问同一变量,则需要使用同步机制;对一个DLL的变量,如果希望每个使用DLL的线程都有自己的值,则应该使用线程局部存储(TLS,Thread Local Strorage)。
输出函数的方法
传统的方法
在模块定义文件的EXPORT部分指定要输入的函数或者变量。语法格式如下:
entryname[=internalname] [@ordinal[NONAME]] [DATA] [PRIVATE]
其中:
entryname是输出的函数或者数据被引用的名称;
internalname同entryname;
@ordinal表示在输出表中的顺序号(index);
NONAME仅仅在按顺序号输出时被使用(不使用entryname);
DATA表示输出的是数据项,使用DLL输出数据的程序必须声明该数据项为_declspec(dllimport)。
上述各项中,只有entryname项是必须的,其他可以省略。
对于“C”函数来说,entryname可以等同于函数名;但是对“C++”函数(成员函数、非成员函数)来说,entryname是修饰名。可以从.map映像文件中得到要输出函数的修饰名,或者使用DUMPBIN /SYMBOLS得到,然后把它们写在.def文件的输出模块。DUMPBIN是VC提供的一个工具。
如果要输出一个“C++”类,则把要输出的数据和成员的修饰名都写入.def模块定义文件。
在命令行输出
对链接程序LINK指定/EXPORT命令行参数,输出有关函数。
使用MFC提供的修饰符号_declspec(dllexport)
在要输出的函数、类、数据的声明前加上_declspec(dllexport)的修饰符,表示输出。MFC提供了一些宏,就有这样的作用,如表7-2所示。
表7-2 MFC定义的输入输出修饰符
宏名称
宏内容
AFX_CLASS_IMPORT
__declspec(dllexport)
AFX_API_IMPORT
__declspec(dllexport)
AFX_DATA_IMPORT
__declspec(dllexport)
AFX_CLASS_EXPORT
__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
AFX_EXT_DATADEF
像AFX_EXT_CLASS这样的宏,如果用于DLL应用程序的实现中,则表示输出(因为_AFX_EXT被定义,通常是在编译器的标识参数中指定该选项/D_AFX_EXT);如果用于使用DLL的应用程序中,则表示输入(_AFX_EXT没有定义)。
要输出整个的类,对类使用_declspec(_dllexpot);要输出类的成员函数,则对该函数使用_declspec(_dllexport)。如:
class AFX_EXT_CLASS CTextDoc : public CDocument
{
…
}
extern "C" AFX_EXT_API void WINAPI InitMYDLL();
这几种方法中,最好采用第三种,方便好用;其次是第一种,如果按顺序号输出,调用效率会高些;最次是第二种。
在“C++”下定义“C”函数,需要加extern “C”关键词。输出的“C”函数可以从“C”代码里调用。
|