回调,线程和MFC

似乎困扰程序员的许多问题之一是使用回调方法的问题。这些回调是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
     }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值