事件和進程間的數據交換 .
//========================================================================
//TITLE:
// 事件和進程間的數據交換
//AUTHOR:
// norains
//DATE:
// Monday 13-July-2009
//Environment:
// WINCE5.0 + VS2005
//========================================================================
多線程的數據交流不難,畢竟是同屬於一個進程,更為重要的是,代碼還很可能同屬於一個工程,基本上想怎麼幹就怎麼幹,大不了我用一個全局變量來做緩存來進行數據的交換。
但對於多進程來說,就沒那麼容易了。從代碼的角度來說,多進程意味不同的工程,意味著不能簡單通過全局變量來交流數據。試想一下,如果IE有一個全局變量g_iCurPage,你用什麼方法才能得到該數據?因此,在多進程的情況下,多線程那一套就沒轍了。
不過,如果隻是交流數據,情況倒不顯得那麼糟糕。一般的流程無非就是:假設有兩個,分別是進程A和進程B,當線程A改變某些數值時,它會通過發送相應的事件給進程B;進程B在獲得該通知事件後,會采取一定的方式,讀取進程A所改變的數值。
聽起來是不是很簡單?
在討論這個問題之前,我們先假設這兩個進程存在如下架構的代碼。
- 進程A:
- DWORD NotifyProc(LPVOID pParam)
- {
- while(TRUE)
- {
- if(IsDataChange() != FALSE)
- {
- //TODO:準備好傳送的數據
- PulseEvent(hEventNotify);
- }
- }
- }
- 進程B:
- DWORD WaitNotifyProc(LPVOID pParam)
- {
- while(TRUE)
- {
- DWORD dwReturn = WaitForSingleObject(hEventNotify);
- if(dwReturn != WAIT_TIMEOUT)
- {
- //TODO:獲取相應的數據
- }
- }
- }
從這段簡單的代碼之中,我們可以知道有這麼兩個難點,首先是進程A如何準備數據,其次便是進程B如何獲取進程A準備的數據。
接下來要論述的方式,都是基於這個框架的兩個難點來討論。
一般的做法,無非有三種。
1).注冊表
采用該方式後完善的代碼如下:
- 進程A:
- DWORD NotifyProc(LPVOID pParam)
- {
- while(TRUE)
- {
- if(IsDataChange() != FALSE)
- {
- //更改相應的注冊表數值
- CReg reg;
- reg.Create(HKEY_CURRENT_USER,DEVICE_INFO);
- reg.SetDW(MAIN_VOLUME,dwVal);
- reg.Close();
- //發送通知事件
- PulseEvent(hEventNotify);
- }
- }
- }
- 進程B:
- DWORD WaitNotifyProc(LPVOID pParam)
- {
- while(TRUE)
- {
- //等待通知事件
- DWORD dwReturn = WaitForSingleObject(hEventNotify);
- if(dwReturn != WAIT_TIMEOUT)
- {
- //讀取注冊表
- CReg reg;
- reg.Create(HKEY_CURRENT_USER,DEVICE_INFO);
- DWORD dwVal = 0;
- dwVal = reg.GetDW(MAIN_VOLUME);
- }
- }
- }
該方法靈活性非常高,進程A如果想增加更多的通知數據,隻需要簡單地多設注冊表項。而進程B可以不用管進程A設置了多少注冊表項,隻需要獲取自己所需要的項目即可。
另外一個更為明顯的優勢在於,由於該方法是將數據保存於注冊表,所以在進程B的運行是在進程A退出之後,進程B還能獲取數據。甚至於,機器重啟後,進程B依然能獲取相應數據——前提條件是係統的注冊表為Hive Registry。
如果說缺陷,確切地說是相對於另外兩種方法而言,便是速度。因為期間會對注冊表進行讀寫,所以速度會略有損失。如果對速度非常在意,那麼該方法並不是最理想的。
2).內存印射
其實這種方式在我blog的另一篇文章《進程間的數據共享》(https://blog.csdn.net/norains/archive/2008/07/16/2663390.aspx)有提過,但為了本篇的完整性,在這裏根據我們的論述框架重新來討論一次。
原理很簡單,進程A先調用CreateFileMapping開辟一個印射的內存區,然後往裏麵拷貝數據,最後通知進程B讀取數據;進程B接受通知時,就直接調用memcpy從內存中獲取數據。
- 進程A:
- DWORD NotifyProc(LPVOID pParam)
- {
- //創建內存文件印射
- HANDLE hFile = CreateFileMapping((HANDLE)-1,NULL,PAGE_READWRITE,0,MEM_SIZE,MEM_SHARE_NAME);
- VOID * pMem = NULL;
- if(hFile != NULL)
- {
- pMem = MapViewOfFile(hFile,FILE_MAP_ALL_ACCESS,0,0,0);
- }
- while(TRUE)
- {
- if(IsDataChange() != FALSE)
- {
- //拷貝傳輸的數據
- if(pMem != NULL)
- {
- memcpy(pMem,&dwValue,sizeof(dwValue));
- }
- PulseEvent(hEventNotify);
- }
- }
- //如果不再使用,應該關閉句柄
- CloseHandle(hFile);
- }
- 進程B:
- DWORD WaitNotifyProc(LPVOID pParam)
- {
- //創建內存文件印射
- HANDLE hFile = CreateFileMapping((HANDLE)-1,NULL,PAGE_READWRITE,0,MEM_SIZE,MEM_SHARE_NAME);
- VOID * pMem = NULL;
- if(hFile != NULL)
- {
- pMem = MapViewOfFile(hFile,FILE_MAP_ALL_ACCESS,0,0,0);
- }
- while(TRUE)
- {
- DWORD dwReturn = WaitForSingleObject(hEventNotify);
- if(dwReturn != WAIT_TIMEOUT)
- {
- //拷貝傳輸過來的數據
- if(pMem != NULL)
- {
- memcpy(&dwValue,pMem,sizeof(dwValue));
- }
- }
- }
- //如果不再使用,應該關閉句柄
- CloseHandle(hFile);
- }
該方法是最複雜的,兩個進程不僅協同設置內存的大小(MEM_SIZE),還要設置同樣的名稱(MEM_SHARE_NAME),更要判斷該內存是否能分配成功。相對的,靈活性也是最高的,隻要以上問題協商解決,則什麼數據類型都能傳遞,無論是DWORD,或是struct。當然,我們還是不能傳遞對象指針,因為簡單地傳遞對象指針,則基本上都會引發內存訪問違例的致命錯誤。
3).設置事件數據
相對於以上兩種方式,該方式徹頭徹尾隻能屬於輕量級。因為它方式最為簡單,同樣,所傳遞的數據也最少。
其原理很簡單,進程A通過SetEventData設置和事件關聯的數據,然後發送事件通知進程B;進程B接收到進程A的事件以後,則通過GetEventData來獲取數據。根據該原理,則代碼的樣式可以如下:
- 進程A:
- DWORD NotifyProc(LPVOID pParam)
- {
- while(TRUE)
- {
- if(IsDataChange() != FALSE)
- {
- //設置關聯數據
- SetEventData(hEventNotify,dwData);
- PulseEvent(hEventNotify);
- }
- }
- }
- 進程B:
- DWORD WaitNotifyProc(LPVOID pParam)
- {
- while(TRUE)
- {
- DWORD dwReturn = WaitForSingleObject(hEventNotify);
- if(dwReturn != WAIT_TIMEOUT)
- {
- //獲取關聯數據
- dwData = GetEventData(hEventNotify);
- }
- }
- }
該方式是最簡單的,在傳遞DWORD長度類型的數據有得天獨厚的優勢,無論是速度還是簡便性。但,也僅限於此,如果想采用該方式傳遞大小大於DWORD的數值,基本上會丟失精度,更不用說struct等結構體數值了。不過,這卻是這三種方法之中,和事件聯係最為緊密的。
最後更新:2017-04-03 14:53:37