进程通讯的相关知识
在Windows程序中,各个进程之间常常需要交换数据,进行数据通讯。常用的方法有
使用内存映射文件
通过共享内存DLL共享内存
使用SendMessage向另一进程发送WM_COPYDATA消息
比起前两种的复杂实现来,WM_COPYDATA消息无疑是一种经济实惠的一种方法
WM_COPYDATA消息分析
WM_COPYDATA消息,在win32中用来进行进程间的数据传输。
typedef struct tagCOPYDATASTRUCT { // cds
DWORD dwData;
DWORD cbData;
PVOID lpData;
} COPYDATASTRUCT;
其中dwData为32位的自定义数据, lpData为指向数据的指针,cbData为lpData指针指向数据的大小(字节数)
一般推荐用SendMessage函数进行发送,这样就能确保在接收方复制数据前避免发送方能修改或删除数据;
LRESULT SendMessage(
HWND hWnd, // handle of destination window
UINT Msg, // message to send
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
hWnd:指定要接收消息的窗口的句柄。如果此参数为HWND_BROADCAST,则消息将被发送到系统中所有顶层窗口,包括无效或不可见的非自身拥有的窗口、被覆盖的窗口和弹出式窗口,但消息不被发送到子窗口。32位的窗口句柄。窗口可以是任何类型的屏幕对象,因为Win32能够维护大多数可视对象的句柄(窗口、对话框、按钮、编辑框等)
Msg:指定被发送的消息。这些常量可以是Windows单元中预定义的常量,也可以是自定义的常量。 消息详情点这里
wParam:指定附加的消息特定信息。通常是一个与消息有关的常量值,也可能是窗口或控件的句柄。
IParam:指定附加的消息特定信息。通常是一个指向内存中数据的指针。
返回值:返回值指定消息处理的结果,依赖于所发送的消息。
备注:需要用HWND_BROADCAST通信的应用程序应当使用函数RegisterWindowMessage来为应用程序间的通信取得一个唯一的消息。
如果指定的窗口是由正在调用的线程创建的,则窗口程序立即作为子程序调用。如果指定的窗口是由不同线程创建的,则系统切换到该线程并调用恰当的窗口程序。 线程间的消息只有在线程执行消息检索代码时才被处理。发送线程被阻塞直到接收线程处理完消息为止。
这里需要注意的:传输的COPYDATA数据在发送后要避免被发送进程中的其他线程修改,其次要确保被传输的数据中没有包含接收进程无法访问的对象的指针或引用。比如:发送进程自身的HDC、HBITMAP之类的东西,它们对于接收进程来说是无效的。另外,接收进程窗体句柄可以通过FindWindow函数获取:
const char szDlgTitle[] = "ReceiveProcess";
HWND hRecvWindow = ::FindWindow(NULL, szDlgTitle);
这里的“ReceiveProcess”为接收进程的名字;
同时,接收数据的进程中,首先在消息映射表中增加WM_COPYDATA消息映射,然后定义消息映射函数,其函数的格式为:
BOOL CCanCollectorDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
....
}
需要注意的问题
- WM_COPYDATA消息只可以使用SendMessage函数(同步)发送,不可以使用PostMessage(异步)发送,笔者认为可能是异步形式下无法保证指向数据的指针可以正确的释放。
- COPYDATA结构体的实质依然是共享内存,区别是这一片特殊的共享内存由操作系统管理而不用用户手动申请管理。
- WM_COPYDATA适合小数据量的进程间通信,大数据量可能造成内存问题,以及界面卡死,因为消息的发送形式是同步的。
源代码实现
主要代码如下:(这里发送进程发送当前的时间给接收进程)
发送端
参考:
CString strDataToSend = _T( "Hello" ); //需要传递的数据
HWND hWndReceived; //进程B的接收数据窗口对象
//COPYDATASTRUCT结构是WM_COPYDATA传递的数据结构对象
COPYDATASTRUCT cpd;
cpd.dwData = 0;
cpd.cbData = strDataToSend.GetLength(); //传递的数据长度
cpd.lpData = (void*)strDataToSend.GetBuffer(cpd.cbData); //传递的数据地址
SendMessage( hWndReceived, WM_COPYDATA, 0, (LPARAM) & cpd );
strDataToSend.ReleaseBuffer();
实例:
void CModelCompareOperation::SendProcessData(const string sData, size_t len)
{
if (NULL == m_hwnd) return;
if (sData.length() > 0) {
int num = (sData.length() < BUFFER_SIZE) ? BUFFER_SIZE : (sData.length() + 1);
char* strchar = new char[num];
memset(strchar, 0, num);
strcpy_s(strchar, strlen(sData.c_str()) + 1, sData.c_str());
COPYDATASTRUCT copyData = { 0 };
copyData.lpData = strchar;
copyData.cbData = (unsigned)sData.size();
::SendMessage(m_hwnd, WM_COPYDATA, 0, (LPARAM)©Data);
if (strchar != NULL)
{
delete[]strchar;
strchar = NULL;
}
}
}
接收端
// 声明
afx_msg BOOL OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct);
// 实现
BEGIN_MESSAGE_MAP(CCanCollectorDlg, CDialog)
//{{AFX_MSG_MAP(CCanCollectorDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_COPYDATA()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
BOOL CCanCollectorDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
// TODO: Add your message handler code here and/or call default
if (pCopyDataStruct->cbData > 0)
{
int nDataLen = pCopyDataStruct->cbData;
char* strchar = new char[nDataLen + 1];
if (strchar != NULL)
{
memset(strchar, 0, nDataLen + 1);
memcpy_s(strchar, nDataLen + 1, (char*)pCopyDataStruct->lpData, pCopyDataStruct->cbData);
LOG_DEBUGA(strchar);
StringUtf8 finalData = StringAnsiToUtf8(strchar);
Invoke(finalData);
delete[]strchar;
strchar = NULL;
}
}
return CDialog::OnCopyData(pWnd, pCopyDataStruct);
}