我正在尝试缓存 MDI 应用程序的活动子窗口,因为分析表明不断调用 MDIGetActive() 是一个热点,更改结构以避免它可能很困难。
我有一个继承自 CMDIChildWnd 的 child_frame 类,我的 OnMDIActivate() 函数基本上看起来像这样:
afx_msg void child_frame::OnMDIActivate(BOOL bActivate, CWnd* pActivateWnd, CWnd* pDeactivateWnd)
{
if(bActivate) {
// Cache the new active child window.
main_frame *parent = dynamic_cast<main_frame>(this->GetMDIFrame());
parent->set_cached_active_child(pActivateWnd);
}
// Do application-specific activation/deactivation stuff
// ...
// ...
// ...
// Run the base class event handler
CMDIChildWnd::OnMDIActivate(bActivate, pActivateWnd, pDeactivateWnd);
}
我已经在 OnMDIActivate() 中的每个点都用断言测试了缓存的子级与父>MDIGetActive(),并且在开头、中间、结尾等处总是正确的。 我还在 child_frame::OnActivate()、main_frame::OnMDIActivate() 和 main_frame::OnActivate() 的每个点都对此进行了测试,并且缓存的结果也总是正确的......因此,更新这些特定处理程序中的缓存指针是没有必要或有用的。
不幸的是,当我在实际的应用程序代码中检索缓存结果时,缓存结果有时与实际的 MDIGetActive() 结果不同。 这似乎表明我缺少一些事件处理程序,除了 OnMDIActivate() 之外,活动 MDI 窗口可以更改。
所以。。。究竟有哪些事件可以更改活动的 MDI 窗口?
编辑:至少其中一部分看起来与线程相关。 根据这个线程,来自 MDIGetActive() 的 CWnd* 在线程中是不可靠的。 就我而言,MDIGetActive() 在生成工作线程的函数中返回有效的 CWnd*,并在工作线程中返回一个 nullptr,并且没有调用明显相关的事件处理程序。 如果事实证明这是唯一的例外/问题,我会将其重新发布为答案。
CWnd* 在线程中无效,因此当您从除主线程之外的任何线程获取 MDIActive() 时,Windows 会撒谎并返回一个 nullptr。 由于线程更改不会发送任何WM_ACTIVATE、WM_MDIACTIVATE等消息,因此它们不会触发 OnActivate()、OnMDIActivate() 等。
至少有两种潜在的解决方法,但我选择了第一种,因为它很简单并且符合我的用例:
- 如果您正在生成一堆工作线程来执行相同的函数调用,并且您永远不会在主线程上查找缓存的活动子帧,直到至少在所有这些线程重新加入之后,您可以在每个工作线程的开头缓存一个 nullptr,并在它们重新加入后重新缓存真正的 MDIGetActive() 值。 主线程在此期间将继续泵送消息,在此处理过程中缓存的值将不正确,但如果主线程中没有实际使用它也没关系。
- 否则,您可以找到一种方法来对缓存的子帧使用线程本地存储并将其初始化为 nullptr,并确保在调用 OnMDIActivate() 时(在主线程上)仅更新相应的线程本地缓存值。 根据您的程序结构,这可能很棘手,但它应该更健壮并与更多线程模式兼容。
最后,缓存子帧将相关代码从运行时的大约 8% 减少到大约 0.02%。