受欢迎的博客标签

Accessing MFC Objects from Multiple Threads with PostMessage

Published

MFC objects are not thread-safe by themselves. Two separate threads cannot manipulate the same object unless you use the MFC synchronization classes and/or the appropriate Win32 synchronization objects, such as critical sections.

::SendMessage() is synchronous in which the message is sent and a response is returned.

跨线程SendMessage,比如在线程A中给主线程发消息,线程A会挂起,直到主线程里这个消息处理完。
::PostMessage() is asynchronous in which the message is sent and a response is not returned

using ::PostMessage() be careful that pointers sent in messages do not go out of scope before the receiver uses them as there is typically a delay between when ::PostMessage() returns and the message handle receiving the message actually does something with the message

 

不同进程发送消息传递字符串

1.两个进程间

(1)两个不同的进程不能用上面的方法,当然只发送消息是可以的。

(2)两个进程由于使用的是相互独立的两个虚拟内存空间,同一地址对不同的进程来说并不一定指向同一物理内存,内容也就不一定一样,因此不同进程无法通过传地址的方式传递字符串(但是同一进程下的不同线程是可以的)

2.解决办法

发送WM_COPYDATA消息在进程间传送数据

see:SendMessage和PostMessage发送消息(不同进程传递字符串)

 

同一进程下的不同线程发送消息传递字符串

PostMessage使用自定义消息传递 CString 类型的字符串乱码

PostMessage有两个参数WPARAM,LPARAM,PostMessage是异步操作。因为TMessage中的WParam是数值型,所以我们发送消息就只能发送字符串的起始地址,然后在接收端通过起始地址获得这个字符串的值。但是这样做会有一个隐形的问题,就是在上分配的内存,会在当前作用域结束后释放掉。

当使用自定义消息时,可以使用下面的方法,进行传递。

 这时候就可以获得接收到的字符串。可是由于PostMessage是异步,不等待Revieve处理完后就反悔了,继续往下面执行。仔细查看Send函数,你会发现postmessage执行完后,该过程就结束,那么这个局部变量mess就会被回收,它所占用的内存空间里面的值可能会被其他数据占用,从而导致在Revcieve的时候,你接收到的值不正确。

以前一直使用PostMessage来发送字符串数据到主界面,由于字符串是临时变量,而PostMessage是异步发送,有时候由于主界面接收到数据的时候,系统已经将字符串占用的内存释放了,造成获取的字符串可能出现乱码的现象!

step 1:检查项目编码

项目\属性\字符集设置的是“使用多字节字符集”。

检查你的代码文件编码是什么,可能影响了编译器的编码。你可以用unicode等字符集的工程,然后代码文件用unicode格式。

MFC 程序,建议你使用 ASCII 或 Unicode 编码的字符串,这是 API 接口参数的标准。如果要使用 UTF-8 的字符串,在显示前需要做字符串编码转换。

把LPCTSTR换成_T。

 


现在有一个字符串,在某函数中需要将它作为参数Post出去,
如果字符串在栈上分配,则退出此函数后,字符串就会被释放
消息处理函数可能得到一个无效的字符串参数。

如果在堆上分配,则要很频繁申请/释放空间(new delete),
如果字符串比较大,就增加了出错机会。

 

可选方案

1.定义成一个全局变量或者静态变量数组,使用这个字符数组。

存在的问题:

若定义成一个全局变量或者静态变量数组,则被POST两次,
两次的参数内容都不一样,第二次的参数会将第一次的参数覆盖

2.如果有多参数要传递,定义个结构,传结构的指针是可以的。

同一工程的话要用同一结构体,并保证发送的是动太生成的结构体,不能是栈变量,因为PostMessage是不阻塞的,也就是说PostMessage返回了,另一窗口不一定执行到了.你的栈变量已经没了.
不同工程的两个窗口要用特定的消息,且结构内不能有CString的那种类型。

3.采用POST池技术+消息桥

将每个POST消息-->发送(SendMessage)到一个中间的处理-->
利用这个处理将POST参数压入队例中(此为消息池),同时发送(PostMessage)另一个消息-->
处理此Post消息的函数再次将每条发送(SendMessage)发送出去


这样其结果与你想要的结果一样,且是采用Post处理的,不会影响性能

以上技术经常用于显示更新视图(里面有一个不同就是第二次SendMessage时对相同消息只处理一次就够了--因为连续要求显示更新只要最后一次更新结果)

4.采用消息的方式通知主线程的运行结果

该方法比较简单有效。一般的多线程程序都是采用主线程负责显示,辅助线程来完成比较耗时的任务,等任务完成后再通知主线程运行结果。

如何避免这种情况

     首先,我们应该手动申请一段内存空间,在堆上申请的内存需要自己手动释放

     我们使用New方法来申请内存,使用Dispose来释放申请的内存。

   

CString* aaa  = new CString("dir");
::PostMessage(GetParent()->  m_hWnd,  WM_USER+1007,  0, (LPARAM) aaa );
// 记得接下来不要调用 delete aaa!!!
 
 
case WM_USER+1007:
{
CString *bbb = (CString*)lParam;
TRACE("\n%s", *bbb);
delete bbb;
}

 

LRESULT CCustomSelectorView::OnDownloadMessage(WPARAM wParam, LPARAM lParam)
{

	CString cstr1 = (LPCTSTR)wParam;
	CString cstr2 = (LPCTSTR)lParam;
	//TRACE(_T(" CCustomSelectorView::OnDownloadMessage(WPARAM wParam, LPARAM lParam):wParam is: - %s,lParam is: - %s\n"), cstr1, cstr2);

	//TRACE(" CCustomSelectorView::OnDownloadMessage(WPARAM wParam, LPARAM lParam):wParam is: - %s,lParam is: - %s\n", cstr1, cstr2);

	//LPARAM类型转化为CString类型
	CString	sTip;

	//sTip.Format(_T("%s"), (char*)(DWORD)lParam);

	sTip.Format(_T("%s"), (TCHAR*)lParam);

	CString* bbb = (CString*)lParam;
	m_staticInfo.SetWindowText((LPCTSTR)bbb);
	//TRACE("\n%s", *bbb);
	delete bbb;

	//m_staticInfo.SetWindowText(sTip);
	////m_staticInfo.SetWindowText(cstr2);

	m_staticInfo.SetRedraw(TRUE);

	return 1;
}

这样,就不会造成由于内存空间释放而产生乱码了!

see:解决PostMessage发送字符串造成数据错乱问题
 

 

 

Learn MFC

https://www.tutorialspoint.com/mfc/mfc_internet_programming.htm