TN058:MFC模块状态执行

备注

以下技术声明,则它在联机文档,首先包括了不更新。因此,某些过程和主题可能已过时或不正确。有关最新信息,建议您搜索议题在联机文档的索引。

此方法声明描述 “MFC 模块状态”结构化实现。 对于模块状态执行的请务必明确用于从 DLL (或 OLE 进程内服务器) MFC 共享 DLL。

在读取此说明之前,请参见中的 “管理 MFC 模块状态数据”在 创建新文档,窗口和视图。 本文包含关键用法信息和概述有关此主题。

概述

有三个 MFC 状态信息:模块状态,处理状态,并线程状态。 有时这些状态类型可以结合使用。 例如, MFC 的句柄映射是模块本地和线程本地变量。 这使两个不同模块中有不同的映射它们的每个线程。

进程状态,并且该线程状态类似。 这些数据项是传统上是全局变量的内容,但是,需要特定于给定进程或适当的 Win32s 的线程支持或为适当的多线程支持。 哪些类别特定数据项都依赖于该项目,并且其预期语义有关进程和线程边界。

模块状态是唯一的因为它可能包含或确实是进程本地或线程本地的全局状态或声明。 此外,它可以快速被切换。

模块状态切换

每个线程都包含指向 “当前”或 “活动”模块状态 (因此奇怪,指针是 MFC 的线程本地状态的一部分)。 调用此的指针进行更改,当执行线程将模块边界时,例如应用程序到 OLE 控件或 DLL 或 OLE 控件调用返回到应用程序。

当前模块的状态通过调用 AfxSetModuleState切换。 大多数情况下,不会直接相关 API。 MFC,在许多情况下,将调用它。 (在 WinMain, OLE 入口点, AfxWndProc等)。 这在所有元素完成编写由已知的静态链接在特定 WndProc和特殊 WinMain (或 DllMain) 哪个模块状态应当前。 可以通过查看 DLLMODUL.CPP 或 APPMODUL.CPP 看到此代码在 MFC \ SRC 内容。

很少见要设置模块状态然后不将其设置为。 大多数时候要 “按”您的模块状态作为当前消息,则,在执行后,然后 “、”原始上下文返回。 这是由宏 AFX_MANAGE_STATE 和特殊类 AFX_MAINTAIN_STATE完成。

CCmdTarget 具有支持的模块状态切换特殊功能。 具体而言, CCmdTarget 是用于 OLE 自动化的根类,并 OLE COM 入口点。 与其他入口点显示在系统,这些入口点必须设置正确的模块状态。 特定 CCmdTarget 如何知道 “右”模块状态应为? 答案是 “记忆”内容 “当前”模块状态是构造,这样它可以将当前模块的状态值设置为 “记忆”值,则之后调用时。 因此,模块,指出特定 CCmdTarget 关联的对象是当前的模块状态,当对象构造完成。 采用加载 INPROC 服务器,创建对象并调用其方法的简单示例。

  1. 使用 LoadLibrary, DLL 由 OLE 加载。

  2. RawDllMain 首先调用。 它将模块状态到 DLL 的已知静态模块状态。 因此 RawDllMain DLL 静态链接。

  3. 类工厂的构造函数与我们的对象调用。 COleObjectFactory 从派生 CCmdTarget 结果,因此,它在哪个模块状态确保其实例化。 这一点很重要 ),以便类工厂请求创建对象时,它现在了解作为当前的哪些模块状态。

  4. DllGetClassObject 调用获取类工厂。 MFC 搜索工厂列表与此模块的类并将其返回。

  5. COleObjectFactory::XClassFactory2::CreateInstance 调用。 在创建对象并返回它之前,此功能集模块状态设置为当前在步骤 3 中的模块状态 (当前的类似,当 COleObjectFactory 实例化)。 这是在 METHOD_PROLOGUE中。

  6. 在创建对象时时,它也是 CCmdTarget 的派生对象,并象模块状态为活动记忆的 COleObjectFactory ,因此执行此新对象。 现在对象了解切换的哪个模块状态,只要它调用。

  7. 客户端调用它从其 CoCreateInstance 接收对 OLE COM 对象的函数。 当对象调用时它使用 METHOD_PROLOGUE 切换到模块状态,如 COleObjectFactory

可以看到,模块状态从对象中传播到对象,则所创建 将模块状态正确设置非常重要。 如果未设置,则 DLL 或 COM 对象可以与调用它的 MFC 应用程序不进行交互,或者无法找到其特定资源或可能会其他凄惨的方式。

请注意某些类型的 DLL, “MFC 扩展 DLL”不专门切换到其 RawDllMain 的模块状态 (实际上,他们甚至通常没有 RawDllMain)。 这是因为,这些属性应该行为 “,就象”它们确实存在使用它们的应用程序。 它们是运行应用程序的一部分,并且是它们的视图修改该应用程序的全局状态。

OLE 控件和其他 DLL 是截然不同。 它们不要修改调用应用程序的状态;调用它们的应用程序不能甚至是 MFC 应用程序和因此可能不在不修改状态。 这是一个原因模块状态切换中开发了。

对于从 DLL 的导出函数,例如生成在 DLL 的对话框的一个,需要添加以下代码添加到该函数的开头:

AFX_MANAGE_STATE(AfxGetStaticModuleState( ))

此交换与直至当前范围的结束的 AfxGetStaticModuleState 返回的状态的当前模块的状态。

; 如果没有使用,资源的问题在于 DLL 将发生 AFX_MODULE_STATE 宏。 默认情况下, MFC 使用主应用程序的资源句柄加载资源模板。 此模板在 DLL 确实存储。 原因是 MFC 的模块状态信息不是由 AFX_MODULE_STATE 宏切换。 资源句柄从 MFC 的模块状态恢复。 导致错误的资源句柄使用不切换模块状态。

AFX_MODULE_STATE 在 DLL 不需要将在每个函数。 例如, InitInstance 可以由应用程序的 MFC 代码调用,而无需 AFX_MODULE_STATE ,因为 MFC 在 InitInstance 然后切换之前自动转换模块状态它,请在 InitInstance 返回之后。 上述情况同样适用于所有消息映射处理程序。 规则 DLL 确实存在特定主窗口过程该自动切换模块状态在路由任何消息之前。

处理本地数据

处理本地数据不会这样优先如果不 Win32s DLL 模型的难点。 在 Win32s 所有 DLL 共享全局数据,因此,即使由多个应用程序。 这非常与 “实际” Win32 DLL 数据模型不同,每个 DLL 获取其中的每个的数据空间的单独副本进程附加到 DLL。 若要添加到复杂,在 Win32s DLL 的堆分配的数据实际上处理特定 (,至少只要所有权为)。 考虑以下数据和代码:

static CString strGlobal; // at file scope

__declspec(dllexport) 
void SetGlobalString(LPCTSTR lpsz)
{
   strGlobal = lpsz;
}

__declspec(dllexport)
void GetGlobalString(LPCTSTR lpsz, size_t cb)
{
   StringCbCopy(lpsz, cb, strGlobal);
}

想想发生了什么,如果上述代码驻留在 DLL,并且该 DLL 由两个加载进程 A 和 B (它可能,事实上,在该应用程序的两个实例)。 A 调用 SetGlobalString("Hello from A")。 因此,内存。 CString 分配数据。在中处理 A。 注意 CString 是全局的并且是可见的。 A 和 B。 现在 B 调用 GetGlobalString(sz, sizeof(sz))。 B 可以查看所设置的数据。 这是因为, Win32s 不提供之间保护处理与 Win32。 这是第一个问题;在许多情况下具有视为由不同的应用程序拥有的应用程序影响全局数据是不想要的。

还有其他问题。 假定 A 现在退出。 当退出时, “strGlobal”字符串使用的内存能够为该系统,即所有内存分配处理一个由操作系统自动释放。 ,因为 CString 调用析构函数,它不会释放;它未调用。 它被释放,因为它分配的应用程序出了投入。 此时,如果 B 调用 GetGlobalString(sz, sizeof(sz)),它不会有效数据。 某些其他应用程序能用于任何其他使用了该内存。

清除存在问题。 MFC 那样使用了一个方法调用线程本地 (TLS)存储区。 MFC 那样将在 Win32s 下实际上作为进程本地存储 (tls) 索引是 TLS 索引,因此,即使未调用这将引用基于此的所有数据 TLS 索引。 这类似于 Win32 用于存储线程本地数据的 TLS 索引 (如参见有关该主题的更多信息)。 这使每个 MFC DLL 使用至少两 TLS 索引每个处理。 在占用大量加载 OLE 控件 DLL (OCXs),则很快就会用完所有 TLS 索引 (只有 64 可用)。 此外, MFC 必须在放在一个所有这些数据,一个结构的。 它不具有高度可扩展性且不需要的有关 TLS 索引其使用。

MFC 4.x 解决该设置可以在 “数据环绕”应为进程内本地的类模板。 例如,该问题上述可以通过编写解决:

struct CMyGlobalData : public CNoTrackObject
{
   CString strGlobal;
};
CProcessLocal<CMyGlobalData> globalData;

__declspec(dllexport) 
void SetGlobalString(LPCTSTR lpsz)
{
   globalData->strGlobal = lpsz;
}

__declspec(dllexport)
void GetGlobalString(LPCTSTR lpsz, size_t cb)
{
   StringCbCopy(lpsz, cb, globalData->strGlobal);
}

MFC 实现此在两个步骤。 首先,具有层在只能使用两个 TLS 索引处理的 Win32 Tls* API (TlsAllocTlsSetValueTlsGetValue等) 的顶部,,而不论涉及多少 DLL 有。 接下来, CProcessLocal 模板提供访问此数据。 它重写为的 operator-> 什么使您可以看到上面的语法直观。 由 CProcessLocal 包装的所有对象必须从 CNoTrackObject派生。 CNoTrackObject 提供底部分配器 (LocalAlloc/LocalFree) 和一个虚拟析构函数使 MFC 能够自动销毁处理本地对象,则该进程已停止时。 ,如果需要,这些对象可以具有自定义析构函数其他清理。 ,因为编译器将生成默认析构函数销毁嵌入 CString 对象,上面的示例不需要一。

具有其他有趣的优点此方法。 不仅自动销毁所有 CProcessLocal 对象,则不会构造,直到它们是必需的。 CProcessLocal::operator-> 速度将实例化关联的对象,首次调用,而不是。 在上面的示例中,则意味着 “strGlobal”字符串第一次不会构造直到 SetGlobalStringGetGlobalString 调用。 在某些情况下,这有助于减小 DLL 启动时间。

线程本地数据

处理数据,则必须是本地的特定线程时,类似本地数据,线程本地数据。 即需要数据的单独实例为每个线程的数据的访问。 这可能多次代替广泛的同步机制。 如果数据不需要由多个线程共享,这种结构的开销可能会很大和不必要的。 假设有一个 CString 对象 (这与上面的示例)。 我们可以通过包装它以使其成为线程本地变量与 CThreadLocal 模板:

struct CMyThreadData : public CNoTrackObject
{
   CString strThread;
};
CThreadLocal<CMyThreadData> threadData;

void MakeRandomString()
{
   // a kind of card shuffle (not a great one)
   CString& str = threadData->strThread;
   str.Empty();
   while (str.GetLength() != 52)
   {
      unsigned int randomNumber;
      errno_t randErr;
      randErr = rand_s( &randomNumber );
      if ( randErr == 0 )
      {
         TCHAR ch = randomNumber % 52 + 1;
         if (str.Find(ch) < 0)
            str += ch; // not found, add it
      }
   }
}

如果 MakeRandomString 从两个不同线程调用,每个 “将拖曳”字符串以不同的方式,而不会干扰其他。 这是因为,实际上有 strThread 实例每个线程而不是全局实例。

引用说明如何在一次获取 CString 地址而不是一次循环迭代。 循环代码可以编写与任何 threadData->strThread “str”使用; 但是,代码速度上执行。 这样,当在循环时,引用这是最佳缓存每对数据。

CThreadLocal 类模板使用相同的结构 CProcessLocal 执行和相同的实现技术。

请参见

其他资源

由Number "技术说明

技术说明按类别