本文将使用C++语言,在MFC框架的配合下给出PostMessage、SendMessage等的使用方式与使用不当造成的后果(讨论均针对自定义的消息进行)。如有什么错误,欢迎指正。

写过Windows程序的同学都知道PostMessage、SendMessage的区别,PostMessage函数调用发送之后,立即返回,不等待消息处理完成。而SendMessage则让调用的线程处于阻塞(BLOCk)状态,直到消息处理完成。

正由于这两个函数的区别导致了如下想法:

想法1:PostMessage立即返回,在程序中,处理界面显示(如处理进度条、滚动条等)时使用PostMessage,不会影响程序的用户体验。

想法2:在程序中全用PostMessage,放弃SendMessage,好处:PostMessage是立即返回的,可以不影响程序的正常流程,就算在消息处理函数中卡死了,也不影响主线程的运行。

起初“学习”到了这些想法,以为受益匪浅,但经过一段时间之后,发现此两种想法都是不可取的。

分析想法1:

这里可分为两点:

1)  在主线程中Post消息,以处理进度条显示(用WM_MY_TEST的参数WPARAM、LPARAM来处理进度条的显示)

代码:code_1

#define WM_MY_TEST (WM_USER + 100)

void CMyDlg::OnBnClickedOk()

{

int nParam1 = 0;

int nParam2 = 0;

for (int nIndex = 0; nIndex < 1000; nIndex++)

{

// Do other things

// …

nParam1++;

nParam2++;

PostMessage(WM_MY_TEST, (WPARAM)&nParam1, (LPARAM)&nParam2);

}

//OnOK();

}

LRESULT CMyDlg::OnMyTest(WPARAM wParam, LPARAM lParam)

{

static int nTimes = 0;

CString strOutPut;

int* pParam1 = (int*)wParam;

int* pParam2 = (int*)lParam;

nTimes++;

strOutPut.Format(_T("%s%d   %s%d %s%d"),

_T("Param1 = "), *pParam1,

_T("Param2 = "), *pParam2,

_T("RealTimes = "), nTimes);

OutputDebugString(strOutPut);

return 0;

}

code_1将运行的结果:

Param1 = 0 Param2 = 0 RealTimes = 1

Param1 = 0 Param2 = 0 RealTimes = 2

Param1 = 0 Param2 = 0 RealTimes = 3

Param1 = 0 Param2 = 0 RealTimes = 1000

结果远不如我们所料,表现为PostMessage多次发送时,2~1000的消息参数全被冲掉了。用pParam1、pParam2来处理进度条的话,后果可想而知(进度条根本没动)。如果将上面的PostMessage改为SendMessage,结果如下:

Param1 = 1 Param2 = 1 RealTimes = 1

Param1 = 2 Param2 = 2 RealTimes = 2

Param1 = 3 Param2 = 3 RealTimes = 3

Param1 = 1000 Param2 = 1000 RealTimes = 1000

可见,稳定的输出了需要的内容,可以很好的控制。

在此情况下(主线程中Post消息时),不仅没有改善用户体验,反而更差了。

不可以频繁使用PostMessage发送同一个消息,除非保证上一次发送的消息被处理完成(这如何保证???),这还不如直接用SendMessage。

当然OnMyTest函数可能是这样的:

LRESULT CMyDlg::OnMyTest(WPARAM wParam, LPARAM lParam)

{

static int nTimes = 0;

CString strOutPut;

int* pParam1 = (int*)wParam;

int* pParam2 = (int*)lParam;

nTimes++;

strOutPut.Format(_T("%s%d   %s%d %s%d"),

_T("Param1 = "), *pParam1,

_T("Param2 = "), *pParam2,

_T("RealTimes = "), nTimes);

OutputDebugString(strOutPut);

// 大量访问网络,磁盘等低速操作

return 0;

}

在这种情况下,如果用SendMessage的话,用户体验将会大大下降,甚至导致程序无法响应。于是有人提出了使用PostMessage,这样程序不会无法响应,最多显示不正确罢了。乍一看,提议似乎还不错,至少程序正常运行了。但是,这些网络访问、磁盘读写等操作为什么要放到界面的代码中呢?界面、代码分离才是合理的,因此可以认定,访问网络、磁盘读写等操作不应该放到这里来处理。

2)  在非主线程中Post消息,以处理进度条显示(用WM_MY_TEST的参数WPARAM、LPARAM来处理进度条的显示)

代码:code_2

DWORD WINAPI ThreadProc( LPVOID lpParam )

{

CMyDlg *pThis = (CMyDlg *)lpParam;

int nParam1 = 0;

int nParam2 = 0;

for (int nIndex = 0; nIndex < 1000; nIndex++)

{

nParam1++;

nParam2++;

pThis->PostMessage(WM_MY_TEST, (WPARAM)&(nParam1), (LPARAM)&(nParam2));

}

return 0;

}

void CMyDlg::OnBnClickedOk()

{

HANDLE hThread = CreateThread(NULL,

0,

ThreadProc,

(void*)this,

0,

NULL);

//OnOK();

}

LRESULT CMyDlg::OnMyTest(WPARAM wParam, LPARAM lParam)

{

static int nTimes = 0;

CString strOutPut;

int* pParam1 = (int*)wParam;

int* pParam2 = (int*)lParam;

nTimes++;

strOutPut.Format(_T("%s%d   %s%d %s%d"),

_T("Param1 = "), *pParam1,

_T("Param2 = "), *pParam2,

_T("RealTimes = "), nTimes);

OutputDebugString(strOutPut);

return 0;

}

code_2的运行结果:

(程序直接崩溃了)

线程函数不等待WM_MY_TEST的返回,循环1000次之后直接退出了,这导致栈上的变量nParam1、nParam2被释放,然后OnMyTest处理的时候,nParam1、nParam2的地址已经无效了,导致崩溃。SendMessage则不会出现此类情况。

修改程序

代码:code_2(2)

DWORD WINAPI ThreadProc( LPVOID lpParam )

{

CqwerDlg *pThis = (CqwerDlg *)lpParam;

int *nParam1 = NULL;

int *nParam2 = NULL;

nParam1 = new int;

nParam2 = new int;

for (int nIndex = 0; nIndex < 1000; nIndex++)

{

*nParam1 = nIndex;

*nParam2 = nIndex;

pThis->PostMessage(WM_MY_TEST, (WPARAM)nParam1, (LPARAM)nParam2);

}

return 0;

}

由于堆内存没有被释放,所以程序没有崩溃,在我的机器上运行结果为:

Param1 = 27 Param2 = 27 RealTimes = 1

Param1 = 117 Param2 = 117 RealTimes = 2

Param1 = 162 Param2 = 162 RealTimes = 3

Param1 = 218 Param2 = 218 RealTimes = 4

Param1 = 272 Param2 = 272 RealTimes = 5

Param1 = 312 Param2 = 312 RealTimes = 6

Param1 = 353 Param2 = 353 RealTimes = 7

Param1 = 391 Param2 = 391 RealTimes = 8

Param1 = 431 Param2 = 431 RealTimes = 9

程序执行非常不稳定,每次结构都不同,当然也不能用这些数据了。当把两个new int放到for循环中,执行结果是稳定的,但这样的代码晦涩难懂。在这里用PostMessage没有任何好处,所以建议使用SendMessage。

分析想法2:

1)  已知一个线程处理了A,由于其他需要,此线程还需要处理B(必须在A完成之后)。需要新加入代码来实现,以前的代码为:

代码:code_3

DWORD WINAPI ThreadProc(LPVOID lpParam)

{

HWND hWnd = (HWND)lpParam;

// Do some things

::PostMessage(hWnd, WM_MUST_DO_THING_A, 0, 0);

return 0;

}

LRESULT CMyDlg::OnMustDoThingA(WPARAM wParam, LPARAM lParam)

{

Do some things for A

Do some things for B   // 费解,这是A的处理函数!!!

}

我们可以多加个消息,WM_MUST_DO_THING_B,然后用PostMessage发送,哦,不能这样,B一定要在A完成之后,现在唯一的处理方式只有对B的处理加入到A的消息处理函数中,这将导致费解的代码。如果在原来的线程函数中PostMessage为SendMessage,则不会如此。

如果A、B是不相关联的两个操作,为了以后扩展,也不该用PostMessage,这种情况下应该多创建一个线程进行处理。

2)  对于需要处理的比较重要的操作(这些可能导致卡死):

LRESULT CMyDlg::OnDoThing(WPARAM wParam, LPARAM lParam)

{

Things To do. // 这里可能会卡死,但又必须处理

}

在这种情况下,建议使用SendMessageTimeout,当等待一段时间后,消息仍然没有处理完成,则程序放弃操作继续运行。

3)对于所有无关紧要的操作:

这些操作包括:清理磁盘临时文件等等,这些操作有没正常处理,程序并不关心,在这种情况下,则可使用PostMessage、

终上所述,我们得到如下结论:

1、  PostMessage不能频繁的发送同一个消息,除非保证上次Post过的消息处理完成。

2、  如果用SendMessage导致应用程序用户体验下降,应该检查消息处理函数,而不仅仅简单改为PostMessage。

3、  如果消息是程序必须处理的,则不能使用PostMessage。

4、  如果消息是程序必须处理,而又有可能导致程序卡死,则使用SendMessageTimeout。

5、  如果消息是无关紧要的,则可以建议使用PostMessage。

6、  对于WM_HOTKEY 等Windows特定的消息,则只能使用PostMessage(未在本文中说明)。

参考:http://blog.csdn.net/xt_xiaotian/article/details/2778689

最新文章

  1. listbox 多选删除(找了好多都不行,终于让我写出来了)
  2. C语言 经典编程100题
  3. axis2开发webservice入门到精通
  4. linux expect详解(ssh自动登录)
  5. Android 常见的广播 action常量
  6. Altera USB Blaster 仿真器(EPM240仿制版
  7. R语言常用基础知识
  8. Android WifiDirect学习(一)
  9. (图文教程)帝国cms7.0列表页模板调用多说评论次数
  10. ORACLE中关于外键缺少索引的探讨和总结
  11. 标准mysql(x64) Windows版安装过程
  12. spring boot 操作MySQL pom添加的配置
  13. 自己设置 WiFi
  14. jenkins上节点显示swap空间不足解决方案
  15. 帝国cms如何调用指定id的文章到首页?
  16. innodb mvcc,事务隔离级别,读写锁
  17. HBase启动后RegionServer自动挂原因及解决办法
  18. IntelliJ IDEA远程调试运行中的JAVA程序/项目
  19. 控制input只输入数字--- onkeyup=&quot;value=value.replace(/[^\d]/g,&#39;&#39;)&quot;
  20. web.xml中Filter,Listener,Servlet的区别

热门文章

  1. phpcms v9 源码解析(4)content模块下的index.php文件的init()方法解析
  2. 使用android.support.design.widget.TabLayout出现java.lang.reflect.InvocationTargetException
  3. 11g RAC R2 之Linux DNS 配置
  4. cocos2dx中创建动画的三种方法
  5. NodeJS - Express 3.0下ejs模板使用 partial展现 片段视图
  6. OC self super isa指针
  7. NopCommerce——Web层中的布局页
  8. Ado.Net实现简易(省、市、县)三级联动查询,还附加Access数据
  9. Caffe安装教程(原创)
  10. 给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。