似乎困扰程序员的许多问题之一是使用回调方法的问题。这些回调是Windows API工作的一部分,有些是枚举器的结果,它们提供对底层Windows信息集的访问。
本文讨论了如何从原始API回调域转移回MFC域,这极大地简化了实际编写代码以处理回调的方式。它主要在回调的上下文中完成,最后一个示例是关于如何在MFC类中创建工作线程。
回调,特别是枚举器
您可以使用许多有用的方法来查找信息; 例如EnumFontFamiliesEx,EnumWindows等。对于其中一些,没有其他有效的方法来枚举对象。对于其他人,如EnumWindows,该方法是强烈推荐的枚举方法(例如,即使其他线程可能在枚举它们的同时创建或删除窗口,也可以使用EnumWindows)。
许多人看到的真正问题是这些是原始API函数,在MFC中是:: EnumWindows,:: EnumChildWindows,:: EnumFontFamiliesEx等。你得到的是一个原始API对象,例如HWND,生活变得困难,因为你无法访问枚举的实例或它的任何MFC属性。例如,您可能希望将字体列表放在列表框中,或者将组合框中的窗口列表或类似内容放置在列表框中。
这通常会强制不必要地使用全局变量,并且不必要地依赖GetDlgItem,这两者都是根本上不好的想法(如果你想知道GetDlgItem是坏的,请参阅我关于这个主题的论文)。
对于大多数枚举器,可以通过 对调用使用LPARAM参数来完全避免这种情况。例如,几个枚举器函数的规范是:
int EnumFontFamiliesEx(HDC, LPLOGFONT, FONTENUMPROC,
LPARAM, DWORD);
BOOL EnumChildWindows(HWND, WNDENUMPROC, LPARAM);
BOOL EnumWindows(WNDENUMPROC, LPARAM);
BOOL EnumThreadWindows(DWORD, WNDENUMPROC, LPARAM);
BOOL EnumMetaFile(HDC, HMETAFILE, MFENUMPROC, LPARAM);
BOOL EnumResourceNames(HMODULE, LPCTSTR,
ENUMRESOURCENAMESPROC,
LONG /* LPARAM */);
BOOL EnumResoureTypes(HMODULE, ENUMRESOURCETYPESPROC,
LONG /* LPARAM */);
唉,由于我认为设计严重失败,有些调查员不接受LPARAM,例如
EnumDateFormats
EnumDateFormatsEx
EnumTimeFormats
这使生活变得困难; 这里不可原谅的是,当设计这些功能时,使用LPARAM的方法是众所周知的。
以上仅是代表性的例子。此外,有许多“ Enum … ”函数不使用回调,这些讨论可以安全地忽略。
为了我们的例子,我们将看一个典型的回调枚举器,EnumWindows。请注意,我将要描述的回调技术适用于所有 回调,无论是来自枚举器还是其他来源,您都可以向其提供任意LPARAM样式的值。
使用EnumWindows时,请记住您调用的函数无法访问您的类,除非您采取特定操作来实现它。例如:
EnumWindows(enumwndfn, NULL)
将调用枚举器函数enumwndfn,该函数必须是普通的C函数或静态类成员(因此无法访问该类的任何实例,例如您的CDialog派生类)
对此的首选解决方案是使用LPARAM 值传递类实例:
EnumWindows(enumwndfn, (LPARAM)this)
将两个声明添加到您的类:
static BOOL CALLBACK enumwndfn(HWND hWnd, LPARAM lParam);
BOOL enumwndfn(CWnd * wnd);
我经常使用相同的名称,简化了我必须处理的概念的数量; C ++重载规则有助于对它们进行排序。请注意使用“静态”一词来限定第一个声明。这样做的结果是,有没有这个参数调用,这也意味着,该方法的类的实例没有隐式访问隐。
在执行静态成员函数的时候
BOOL CALLBACK CMyDialog::enumwndfn(HWND hWnd, LPARAM lParam)
{
CMyDialog * me = (CMyDialog *)lParam;
return me->enumwndfn(CWnd::FromHandle(hWnd);
}
对于非静态成员,您现在可以直接访问该类的对象,例如,列表框c_Windows:
BOOL CMyDialog::enumwndfn(CWnd * wnd)
{
// full access to class here
CString s;
wnd->GetWindowText(s);
c_Windows.AddString(s);
}
这显然更方便MFC编程。
所以,你说,“但我真的使用LPARAM传递信息!现在我该怎么办?” 答案很简单。简单的答案是:将您用于通过LPARAM传递的信息存储在类的实例变量中。您可以改为编写代码,而不是访问lParam变量,如下所示:
enumParam = whatever;
EnumWindows(enumwndfn, this);
BOOL CMyDialog::enumwndfn(CWnd * wnd)
{
if(enumParam == whatever) ...
}
你们中的一些人现在会说“啊哈!有你!如果我有多个线程进行枚举,那么这种技术就不是线程安全的!”。的确,就是这样。所以有一个稍微复杂的解决方案。
typedef struct EnumParm {CMyDialog * me; LPARAM lParam; }
然后写
EnumParam p;
p.me = this;
p.lParam = whatever;
EnumWindows(enumwndfn, (LPARAM)&p);
并重写处理程序
BOOL CMyDialog::enumwndfn(HWND hWnd, LPARAM lParam)
{
EnumParm * p = (EnumParm *)lParam;
return p->me->enumwndfn(CWnd::FromHandle(hWnd),
p->lParam);
}
BOOL CMyDialog::enumwndfn(CWnd * wnd, LPARAM lParam)
{
// enumeration handler code here
}
问题解决了。
工作线程在课堂上
我在使用工作线程时使用相同的技术。我经常希望工作线程在我的类的上下文中作为MFC代码的集合运行,尽管它不是基于GUI的线程。相同的铸造技术适用:
static UINT threading(LPVOID p);
void threading();
要启动一个帖子,请致电
AfxBeginThread(threading, this);
而代码是
UINT CMyClass::threading(LPVOID p)
{
CMyClass * me = (CMyClass *)p;
me->threading();
return 0;
}
void CMyClass::threading()
{
// complete class instance access available
}