您的位置:首页 > 其它

MFC和模块状态

2009-01-16 13:37 169 查看
什么是模块状态?
在每个使用了Microsoft Foundation Classes (MFC)的模块(EXE,DLL)中,都存在一种“全局”数据,MFC正是依靠这种全局数据才能执行正确的操作。这种全局数据就叫做MFC 模块状态。
例如,MFC应用程序经常使用下面的代码从资源文件中加载字符串:

CString str;

str.LoadString(IDS_MYSTRING)

使用这种代码非常方便,但它掩盖了这样一个事实:即此程序中IDS_MYSTRING可能不是唯一的标识符。一个程序可以加载多个DLL,某些DLL可能用IDS_MYSTRING标识符定义一个资源。MFC怎样知道应该加载哪个资源呢?MFC使用“当前模块状态”查找资源句柄。

资源句柄不仅仅是MFC全局数据的缓冲区。请参阅MFC/include/afxstat_.h中所述的AFX_MODULE_STATE结构来查找完整的列表。

何时需要管理模块状态?
当编译使用了MFC的DLL时,可以通过3种方法使用MFC:
静态链接
编译为扩展DLL的动态链接
编译为常规DLL的动态链接
如果应用程序与MFC静态链接,那么MFC不能共享。这样就不存在管理模块状态的问题。MFC知道从静态链接的DLL中加载资源。使用这种方法的缺点是DLL(ARX)将会变大,而且会在程序中留下重复代码(比如,that of MFC’s)。
当应用程序与MFC动态链接时,比如LoadString函数需要一些附加信息来识别正确的资源。(MFC会找到调用栈并确定调用DLL的模块句柄。但是,这是一个不可靠的解决办法,因为编译器在编译release版时会优化掉某些调用。)

MFC从当前的模块状态中拾取这些附加信息。应用程序(EXE,DLL)负责正确地管理当前的模块状态。换句话说,在动态链接时,MFC需要知道是哪个模块被才能正确操作。

常规DLL与扩展DLL的区别
When dynamically linking MFC into a so-called Regular DLL you specify the _USRDLL preprocessor directive to the preprocessor.
当将MFC动态链接到常规DLL中时,要将_USRDLL指定为预处理器。

在MFC/include/afx.h中,使用了以下语句将此预处理器符号强制定义为符号参照:

#ifdef _USRDLL

#pragma comment(linker, "/include:__afxForceUSRDLL")

#endif

该符号由MFC/src/dllmodul.cpp提供。Dllmodul.obj是静态库MFCS42.lib的一个部分,此静态库链接到所有使用了MFC42.DLL的模块(EXE,DLL)。

dllmodul.cpp也被声明为AFX_MODULE_STATE的一个实例,它为常规DLL创建并分配模块状态。

当将MFC动态链接到扩展DLL时,_USRDLL不被指定为预处理器。因此,dllmodul.obj就没有了的符号参照,也就没有了为扩展DLL分配的模块状态。

MFC在线程局部变量(见Visual C++ 文档中的__declspec( thread ))中存储当前的模块状态。当每次在模块之间切换执行线程时,此线程局部变量被重新设置。这种切换可以是自动的(见TN058中的示例)。但是在ARX或DBX应用程序中,只能手工进行切换。(见MFC/src/afxstate.cpp中的AfxSetModuleState函数)

简而言之,常规DLL与扩展DLL的区别就是常规DLL具有自己的模块状态,而扩展DLL则没有自己的模块状态。扩展DLL通过EXE或者常规DLL的模块状态进行操作。其区别可参见下图:

  
因此,如果创建与MFC动态链接的DLL(ARX),必须知道常规DLL与扩展DLL的区别,并按此管理MFC的模块状态。

如何管理模块状态?
静态链接应用程序不需要管理模块状态,因为MFC总是使用它所链接的DLL的模块状态。现在,让我们来检查MFC动态链接DLL时的两种情况。
MFC42.DLL怎样知道该使用哪个模块状态(例如资源)呢?如果DLL没有告诉MFC使用哪个模块状态,它不会知道。那么MFC将继续运行并使用当前的模块状态。

扩展DLL通常不关心MFC的模块状态。它们被设计为带类的扩展MFC,应用程序和常规DLL能够使用其中定义的实例。这种情况下,对象存在于调用的模块及其模块状态中。无论是否当前模块状态,它们都可以运行。但是,在AutoCAD环境中,我们会发现这种情况:需要使用扩展DLL来管理模块状态。

如果一个扩展DLL需要一些私有资源才能执行正确的操作(例如显示错误提示),它会使用下列API函数覆盖当前模块状态的资源句柄:

AfxSetResourceHandle() 设置当前资源句柄
AfxGetResourceHandle() 返回当前资源句柄
在从扩展DLL返回之前,前面的资源句柄必须被重新设置。
(注意:如果创建了一个扩展DLL,要使它输出的资源或者类可以被其它的DLL或EXE模块使用,那么还需要创建CDynLinkLibrary对象的实例。由于这是一个在开发ObjectARX应用程序中不常见的问题,请读者参考MFC文档中的TN033以获取更多信息。)

常规DLL和EXE模块有它们自己的模块状态,并且它们负责正确管理这些模块状态。MFC提供了一系列API函数和帮助类来完成这些工作:

AfxGetStaticModuleState()得到调用模块的模块状态。它只返回与模块静态链接的dllmodul.cpp中的全局变量。
AfxGetAppModuleState() 返回进程中EXE模块的模块状态。
AfxGetModuleState() 返回当前模块状态。
AfxSetModuleState() 设置当前模块状态。
AFX_MANAGE_STATE 是一个宏,示例了一个简单的封装类。它在构造器中存储前一个模块状态以覆盖当前模块状态,并在解除程序中恢复前一个模块状态。
常规DLL和EXE模块需要对每一个可以使执行程序进入模块的指针使用AFX_MANAGE_STATE(AfxGetStaticModuleState())。这包括明确地输出全局和成员函数以及虚拟覆盖(在rx中称为反应器回叫)。
如何在ARX应用程序中使用MFC
除非ARX应用程序本身的模块状态管理属于下列情形之一,否则ARX在使用MFC时会遇到特殊的问题:
AutoCAD 2000的语境体系(fiber architecture)
多种应用模块(静态、常规DLL、扩展DLL)同时加载到同一个进程空间
使用了AutoCAD的 MFC API
在执行过程中使用了使用MFC的AutoCAD API
我们将一个个的讨论这些情况。
语境转换

AutoCAD 2000使用语境(fibers,在ObjectARX文档中称为“contexts”)来实现MDI环境。(请参考Microsoft 平台的SDK文档来获取更多有关语境的信息。)由于MFC当前的模块状态存储在一个线程局部变量中,当AutoCAD从一个语境切换到同线程中的另一个语境时,需要确认模块状态和资源句柄也都已正确地切换。

此过程完全在AutoCAD 2000中执行,所以ARX应用程序不需要再做任何工作。

多个MFC应用模块同时运行
考虑下面的情况:我们有两个ARX应用程序。第一个.ARX应用程序作为MFC的常规DLL来执行。第二个ARX应用程序作为MFC的扩展DLL来执行。在启动第二个ARX应用程序时出现一个非模态对话框并显示添加到数据库中的对象数目,它通过在数据库中插置一个数据库反应器以得到objectAppended的通知来实现此功能。第一个ARX应用程序则通过执行一个命令来显示对话框,在这个对话框中有一个按钮,可以在数据库中创建一条直线。
考虑下面的事件过程:

加载第二个应用程序后,它不切换转换模块状态,但将资源句柄置为当前。非模态对话框正确显示。
加载第一个应用程序后,由用户调用用来显示模态对话框的命令。
MFC的当前模块状态转换到第一个应用程序,并将应用程序1的资源句柄置为当前。
对话框正确显示,用户单击按钮可以在数据库中创建一条直线。
第一个.ARX程序在数据库中创建直线并依次触发通知。
第二个.ARX程序接到通知后试着更新非模态对话框。
这时,MFC有失效的可能,这决定于应用程序2要执行的功能。执行简单的SetWindowText()不会出现问题,但是如果调用在扩展DLL中注册的MFC类的CObject::IsKindOf就会失败。这时更常见的问题是扩展DLL试图访问AutoCAD的MFC API。在下个部分中将讨论这种情况。
问题在于设计为扩展DLL的ARX应用程序通常假设当前的模块状态是AutoCAD的。这通常是一个安全的假设,但有时候并不安全,例如象上面一样的情况。那么应该怎么做呢?
AutoCAD应该在通知到达第二个应用程序之前(上面第六步),将模块状态切换到它本身。但是,如果进一步研究这个问题,就会发现它将不可能实现。因为发生改变和引发通知的ObjectDBX子系统不能理解MFC(它本身不使用MFC)。
扩展DLL在执行任何state-sensitive操作前,应该声明假设并确认AutoCAD的模块状态已置为当前。ARX应用程序可以通过调用AFX_MANAGE_STATE(AfxGetAppModuleState())来实现这一功能。
现在,大多数可用的ARX应用程序(包括AutoCAD 2000核心程序)不能如上面所示地保护自己。如果在acad进程中出现一个常规DLL,它们会失效或运行不正常。
AutoCAD的MFC API

AutoCAD中有一些有用的API直接输出AutoCAD使用的MFC对象。比如,CWinApp* acedGetAcadWinApp()输出AutoCAD使用的MFC应用程序对象。只有AutoCAD的模块状态为当前时,这些对象的成员函数才能安全使用。因此,如果使用MFC动态链接,只能使用这些API。而且必须确定当前使用的模块状态确实是AutoCAD的模块状态。

请注意3.1.2节描述的情况。

同时注意这部分与3.1.4节描述的情况不同。这些API直接输出MFC对象,这样AutoCAD没有机会封装这些成员函数来提供适当的模块状态管理。

AutoCAD 2000同时提供了两个MFC扩展DLL(adui.DLL、acui.DLL),如果ARX应用程序与MFC动态链接就可以使用它们。由于这些扩展DLL不能用作静态库,所以不能在静态链接MFC的应用程序中使用。

在执行中使用MFC的AutoCAD APIs
很多AutoCAD API在执行中使用MFC。由于acad模块有它自身的模块状态,因此需要在所有入口点上提供适当的模块状态管理。
不幸的是,R14根本不管理模块状态。R2000试图解决这个问题并在ACAD.EXE中提供了适当的模块状态管理,但是仍然存在一些问题。最严重的问题是:如果AutoCAD的模块状态不是当前的,使用acutPrintf函数会导致崩溃。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: