在每个模块(exe或dll)中,都存在一种全局的状态数据,mfc依靠这种全局的状态数据来区分不同的模块,以执行正确的操作。这种数据包括:windows实例句柄(用于加载资源),指向应用程序当前的cwinapp和cwinthread对象的指针,ole模块引用计数,以及维护windows对象句柄与相应的mfc对象实例之间连接的各种映射等。但当应用程序使用多个模块时,每个模块的状态数据不是应用程序范围的。相反,每个模块具有自已的mfc状态数据的私有副本。这种全局的状态数据就叫做mfc模块状态。
模块的状态数据包含在结构中,并且总是可以通过指向该结构的指针使用。当代码在执行时进入了某一个模块时,只有此模块的状态为“当前”或“有效”状态时,mfc才能正确的区分此模块并执行正确的操作。
例如,mfc应用程序可以使用下面代码从资源文件中加载字符串:
cstring str;
str.loadstring(ids_mystring);
使用这种代码非常方便,但它掩盖了这样一个事实:即此程序中ids_mystring可能不是唯一的标识符。一个程序可以加载多个dll,某些dll可能也用ids_mystring标识符定义了一个资源。mfc怎样知道应该加载哪个资源呢?mfc使用当前模块状态查找资源句柄。如果当前模块不是我们要使用的正确模块,那么就会产生不正确的调用或者错误。
按照mfc库的链接方法,一个mfc dll有两种使用mfc库的方法:静态链接到mfc的dll和动态链接到mfc的dll。下面我们就按照这两种类型的mfc dll来介绍如何切换当前模块状态以正确的在mfc dll中使用资源。
1、静态链接到mfc的dll
静态链接到mfc的规则dll与mfc库静态链接,则此时mfc库不能共享,所以mfc总是使用它所链接的dll的模块状态。这样也就不存在管理模块状态的问题。但使用这种方法的缺点是dll程序将会变大,而且会在程序中留下重复代码。下面给出的例子验证了这一点。本例可以按照以下步骤来完成:
1)在vc菜单中file->new新建一个命名为dllstatic的mfc appwizard的工程,下一步选择regular dll with mfc statically linked。
2)在工程中添加一个对话框资源,其id为:idd_aboutbox。并在resource.h之中将idd_aboutbox 的数值改为100。
3)在dllstatic.cpp中定义如下函数:
void showdlg()
{
cdialog dlg(idd_aboutbox);
dlg.domodal();
}
4)在dllstatic.def文件中的exports语句中添加一行:showdlg,以导出showdlg函数。
5)编译生成dllstatic.dll和dllstatic.lib。
继续使用上一节中的use工程,将前面生成的dllstatic.dll和dllstatic.lib两个文件复制到工程的debug目录内,并将
extern "c" __declspec(dllexport) void showdlg();
#pragma comment(lib,"debug/usedlg")
这两行改为:
void showdlg();
#pragma comment(lib,"debug/dllstatic")
编译并运行use.exe。点击按钮,可以看到dllstatic中的模态对话框弹出。
本例中,可以注意到dll中所定义的about对话框资源与exe中所定义的about对话框资源id完全相同,但是当我们点击use.exe上面的按钮时,弹出的是dll中的模态对话框。说明,当使用静态链接到mfc的规则dll时,不存在管理模块状态的问题。
2、动态链接到mfc的dll
在讨论关于动态链接到mfc的dll的模块状态问题之前,先来看一个例子。本例可以通过如下步骤来完成:
1)在vc菜单中file->new新建一个命名为dllshared的mfc appwizard的工程,下一步选择regular dll using shared mfc dll。
2)在工程中添加一个对话框资源,其id为:idd_aboutbox。并在resource.h之中将idd_aboutbox 的数值改为100。
3)在dllshared.cpp中定义如下函数:
void showdlg()
{
cdialog dlg(idd_aboutbox);
dlg.domodal();
}
4)在dllshared.def文件中的exports语句中添加一行:showdlg,以导出showdlg函数。
5)编译生成dllshared.dll和dllshared.lib。
继续使用上面的use工程,将前面生成的dllshared.dll和dllshared.lib两个文件复制到工程的debug目录内,并将
extern "c" __declspec(dllexport) void showdlg();
#pragma comment(lib,"debug/dllstatic")
这两行改为:
void showdlg();
#pragma comment(lib,"debug/dllshared")
编译并运行use.exe。点击按钮,这次你看到了什么?对,没错,这次弹出的是use.exe的关于对话框。将上述例子的dll类型换成mfc extension dll(using shared mfc dll)也会出现相同的问题。
为什么会出现上面的问题?这是因为在使用了mfc共享库的时候,默认情况下,mfc使用主应用程序的资源句柄来加载资源模板。虽然我们调用的是dll中的函数来显示dll中的对话框,并且对应的对话框模板是存储在dll中的,但mfc仍旧在主应用程序也就是use.exe中寻找相应的对话框模板。由于在dll中所定义的对话框资源id与主应用程序中所定义的关于对话框的资源id相同,所以mfc就把主应用程序中的关于对话框显示了出来。如果二者不同,则mfc就认为dll中所定义的对话框资源不存在,dlg.domodal会返回0,也就是什么都不会显示。
那么如何解决上述问题呢?解决办法就是在适当的时候进行模块状态切换,以保证具有当前状态的模块是我们所需要的模块从而使用正确的资源。mfc提供了下列函数和宏来完成这些工作:
afxgetstaticmodulestate:这是一个函数,其函数原型为:
afx_module_state* afxapi afxgetstaticmodulestate( );
此函数在堆栈上构造afx_module_state类的实例pmodulestate并对其赋值后将其返回。在afx_module_state类的构造函数中,该类获取指向当前模块状态的指针并将其存储在成员变量中,然后将pmodulestate设置为新的有效模块状态。在它的析构函数中,该类将存储在其成员变量中的指针还原为存贮的前一个模块状态。
afx_manage_state:这是一个宏,其原型为:
afx_manage_state( afx_module_state* pmodulestate )
该宏用于将pmodulestate(指向包含模块全局数据也就是模块状态的afx_module_state结构的指针)设置为当前的即时作用空间中(the remainder of the immediate containing scope)的有效模块状态。在离开包含该宏的作用空间时,前一个有效的模块状态自动还原。
afxgetresourcehandle:这个函数的原型为:
hinstance afxgetresourcehandle( );
该函数返回了一个保存了hinstance类型的、应用程序默认所加载资源的模块的句柄。
afxsetresourcehandle:这个函数的原型为:
void afxsetresourcehandle( hinstance hinstresource );
该函数将hinstresource所代表的模块设置为具有当前状态的模块。
通过使用上述四个函数或宏就可以正确的在动态链接到mfc的dll中切换模块状态。接下来我们将通过修改上面出现问题的那个例子来介绍如何使用上述四个函数或宏。先来看看regular dll using shared mfc dll类型:
在上述例子的第三步的showdlg函数的第一条语句前加上如下语句(要确保该语句在函数实现的第一行):
afx_manage_state(afxgetstaticmodulestate());
之后重新编译生成dllshared.dll和dllshared.lib,并将这两个文件重新拷贝到use工程的debug目录内。这次编译生成use.exe并运行,点击按钮,可以看到弹出的时我们在dll中所加入的那个对话框,而不再是use.exe的关于对话框了。
通过上面的讲解,相信你已经知道该语句的作用了。在函数showdlg的第一行加上这么一句后,每次调用dll的应用程序使用该函数的时候,mfc库都会自动切换当前模块状态,这样就保证了资源读取的正确性。
afx_manage_state(afxgetstaticmodulestate());是自动切换当前模块状态,也可以通过使用afxgetresourcehandle和afxsetresourcehandle来手动切换当前模块状态。具体使用方法如下:
在上述例子的第三步的showdlg函数的第一条语句前加上如下语句(要确保该语句在函数实现的第一行):
hinstance save_hinstance = afxgetresourcehandle();
afxsetresourcehandle(theapp.m_hinstance);
在调用对话框成功之后,也就是dlg.domodal();之后,添加:
afxsetresourcehandle(save_hinstance);