閱讀954 返回首頁    go 阿裏雲 go 技術社區[雲棲]


事件和進程間的數據交換 .

//========================================================================
//TITLE:
//    事件和進程間的數據交換
//AUTHOR:
//    norains
//DATE:
//    Monday  13-July-2009
//Environment:
//    WINCE5.0 + VS2005
//========================================================================
    多線程的數據交流不難,畢竟是同屬於一個進程,更為重要的是,代碼還很可能同屬於一個工程,基本上想怎麼幹就怎麼幹,大不了我用一個全局變量來做緩存來進行數據的交換。
   
    但對於多進程來說,就沒那麼容易了。從代碼的角度來說,多進程意味不同的工程,意味著不能簡單通過全局變量來交流數據。試想一下,如果IE有一個全局變量g_iCurPage,你用什麼方法才能得到該數據?因此,在多進程的情況下,多線程那一套就沒轍了。
   
    不過,如果隻是交流數據,情況倒不顯得那麼糟糕。一般的流程無非就是:假設有兩個,分別是進程A和進程B,當線程A改變某些數值時,它會通過發送相應的事件給進程B;進程B在獲得該通知事件後,會采取一定的方式,讀取進程A所改變的數值。

    聽起來是不是很簡單?
   
    在討論這個問題之前,我們先假設這兩個進程存在如下架構的代碼。

  1. 進程A:  
  2. DWORD NotifyProc(LPVOID pParam)  
  3. {  
  4.  while(TRUE)  
  5.  {  
  6.   if(IsDataChange() != FALSE)  
  7.   {  
  8.    //TODO:準備好傳送的數據   
  9.      
  10.    PulseEvent(hEventNotify);         
  11.   }  
  12.  }  
  13. }  
  14.   
  15. 進程B:  
  16. DWORD WaitNotifyProc(LPVOID pParam)  
  17. {  
  18.  while(TRUE)  
  19.  {  
  20.   DWORD dwReturn = WaitForSingleObject(hEventNotify);  
  21.   if(dwReturn != WAIT_TIMEOUT)  
  22.   {  
  23.    //TODO:獲取相應的數據   
  24.   }  
  25.  }  
  26. }  
進程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).注冊表
   
    采用該方式後完善的代碼如下:

  1. 進程A:  
  2. DWORD NotifyProc(LPVOID pParam)  
  3. {  
  4.  while(TRUE)  
  5.  {  
  6.   if(IsDataChange() != FALSE)  
  7.   {  
  8.    //更改相應的注冊表數值   
  9.    CReg reg;  
  10.    reg.Create(HKEY_CURRENT_USER,DEVICE_INFO);  
  11.    reg.SetDW(MAIN_VOLUME,dwVal);  
  12.    reg.Close();  
  13.      
  14.    //發送通知事件   
  15.    PulseEvent(hEventNotify);         
  16.   }  
  17.  }  
  18. }  
  19.   
  20. 進程B:      
  21. DWORD WaitNotifyProc(LPVOID pParam)  
  22. {  
  23.  while(TRUE)  
  24.  {  
  25.   //等待通知事件   
  26.   DWORD dwReturn = WaitForSingleObject(hEventNotify);  
  27.     
  28.   if(dwReturn != WAIT_TIMEOUT)  
  29.   {  
  30.    //讀取注冊表   
  31.    CReg reg;  
  32.    reg.Create(HKEY_CURRENT_USER,DEVICE_INFO);  
  33.    DWORD dwVal = 0;  
  34.    dwVal = reg.GetDW(MAIN_VOLUME);  
  35.   }  
  36.  }  
  37. }  
進程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從內存中獲取數據。

  1. 進程A:  
  2. DWORD NotifyProc(LPVOID pParam)  
  3. {  
  4.  //創建內存文件印射   
  5.  HANDLE hFile = CreateFileMapping((HANDLE)-1,NULL,PAGE_READWRITE,0,MEM_SIZE,MEM_SHARE_NAME);  
  6.    
  7.  VOID * pMem = NULL;  
  8.  if(hFile != NULL)  
  9.  {  
  10.   pMem = MapViewOfFile(hFile,FILE_MAP_ALL_ACCESS,0,0,0);  
  11.  }  
  12.   
  13.  while(TRUE)  
  14.  {  
  15.   if(IsDataChange() != FALSE)  
  16.   {  
  17.    //拷貝傳輸的數據   
  18.    if(pMem != NULL)  
  19.    {  
  20.     memcpy(pMem,&dwValue,sizeof(dwValue));  
  21.    }  
  22.      
  23.    PulseEvent(hEventNotify);         
  24.   }  
  25.  }  
  26.    
  27.  //如果不再使用,應該關閉句柄   
  28.  CloseHandle(hFile);  
  29.   
  30. }  
  31.   
  32. 進程B:  
  33. DWORD WaitNotifyProc(LPVOID pParam)  
  34. {  
  35.  //創建內存文件印射   
  36.  HANDLE hFile = CreateFileMapping((HANDLE)-1,NULL,PAGE_READWRITE,0,MEM_SIZE,MEM_SHARE_NAME);  
  37.    
  38.  VOID * pMem = NULL;  
  39.  if(hFile != NULL)  
  40.  {  
  41.   pMem = MapViewOfFile(hFile,FILE_MAP_ALL_ACCESS,0,0,0);  
  42.  }  
  43.    
  44.  while(TRUE)  
  45.  {  
  46.   DWORD dwReturn = WaitForSingleObject(hEventNotify);  
  47.   if(dwReturn != WAIT_TIMEOUT)  
  48.   {  
  49.    //拷貝傳輸過來的數據   
  50.    if(pMem != NULL)  
  51.    {  
  52.     memcpy(&dwValue,pMem,sizeof(dwValue));  
  53.    }  
  54.   }  
  55.  }  
  56.    
  57.  //如果不再使用,應該關閉句柄   
  58.  CloseHandle(hFile);  
  59. }  
進程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來獲取數據。根據該原理,則代碼的樣式可以如下:

  1. 進程A:  
  2. DWORD NotifyProc(LPVOID pParam)  
  3. {  
  4.  while(TRUE)  
  5.  {  
  6.   if(IsDataChange() != FALSE)  
  7.   {  
  8.    //設置關聯數據   
  9.    SetEventData(hEventNotify,dwData);  
  10.      
  11.    PulseEvent(hEventNotify);         
  12.   }  
  13.  }  
  14. }  
  15.   
  16. 進程B:  
  17. DWORD WaitNotifyProc(LPVOID pParam)  
  18. {  
  19.  while(TRUE)  
  20.  {  
  21.   DWORD dwReturn = WaitForSingleObject(hEventNotify);  
  22.   if(dwReturn != WAIT_TIMEOUT)  
  23.   {  
  24.    //獲取關聯數據   
  25.    dwData = GetEventData(hEventNotify);  
  26.   }  
  27.  }  
  28. }  
進程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

  上一篇:go JavaEE中web.xml中的load-on-startup屬性
  下一篇:go Java麵向對象高級--包裝類