共用記憶體實現進程間通訊

來源:互聯網
上載者:User

進程通常被定義為一個正在啟動並執行程式的執行個體,它由兩個部分組成:一個是作業系統用來管理進程的核心對象。核心對象也是系統用來存放關於進程的統計資訊的地方。另一個是地址空間,它包含所有的可執行模組或DLL模組的代碼和資料。它還包含動態分配的空間。如線程堆棧和堆分配空間。每個進程被賦予它自己的虛擬位址空間,當進程中的一個線程正在運行時,該線程可以訪問只屬於它的進程的記憶體。屬於其它進程的記憶體則是隱藏的,並不能被正在啟動並執行線程訪問。
兩個進程之間進行通訊方式包括:剪貼簿Clipboard、視窗WM_COPYDATA訊息、共用記憶體方式、訊息管道、郵件槽、Windows通訊端、遠端程序呼叫RPC、串列/並行通訊、COM/DCOM。
本文介紹共用記憶體方式實現進程之間進行通訊。FileMapping用於將存在於磁碟的檔案放進一個進程的虛擬位址空間,並在該進程的虛擬位址空間中產生一個地區用於“存放”該檔案,這個空間就叫做File View,系統並同時產生一個File Mapping Object(存放於實體記憶體中)用於維持這種映射關係,這樣當多個進程需要讀寫那個檔案的資料時,它們的File View其實對應的都是同一個File  Mapping  Object,這樣做可節省記憶體和保持資料的同步性,並達到資料共用的目的。使用共用記憶體在處理大資料量資料的快速交換時表現出了良好的效能,在資料可靠性等方面要遠遠高於發送WM_COPYDATA訊息的方式(WM_COPYDATA訊息用於小資料量的進程間交換)。這種大容量、高速的資料共用處理方式在設計高速數傳通訊類軟體中有著很好的使用效果。


1、共用記憶體方式實現進程之間通訊步驟:
一個進程處理過程:CreateFileMapping()函數建立一個記憶體對應檔對象,如果建立成功則通過MapViewOfFile()函數將此檔案對應物件的視圖映射進地址空間,同時得到此映射視圖的首址。訪問地址進行資料讀寫。當資料轉送結束,即將退出程式時,需要將映射進來的記憶體檔案對應物件視圖卸載和資源的釋放等處理。這部分工作主要由UnmapViewOfFile()和CloseHandle()等函數完成。
另一個進程處理過程:OpenFileMapping()函數開啟一個命名指定的檔案對應物件,如果執行成功,繼續用MapViewOfFile()函數將此檔案對應物件的視圖映射到接收應用程式的地址空間並得到其首址。訪問地址進行資料讀寫。當資料轉送結束,即將退出程式時,需要將映射進來的記憶體檔案對應物件視圖卸載和資源的釋放等處理。這部分工作主要由UnmapViewOfFile()和CloseHandle()等函數完成。
關於資料處理完成之後怎樣通知另外一個進程去讀取,有兩種方法:一是訊息通知;二是定義一個資料結構頭,設定標誌位,線程檢測標誌位,根據標誌位狀態判斷是否處理完成,如果處理完成通過事件來喚醒另外事件進行處理。

HANDLE CreateFileMapping(
HANDLE hFile,                       //物理檔案控制代碼
LPSECURITY_ATTRIBUTES lpAttributes, //安全設定
DWORD flProtect,                    //保護設定
DWORD dwMaximumSizeHigh,            //高位檔案大小
DWORD dwMaximumSizeLow,             //低位檔案大小
LPCTSTR lpName                      //共用記憶體名稱
);

1) 物理檔案控制代碼
   任何可以獲得的物理檔案控制代碼, 如果你需要建立一個物理檔案無關的記憶體映射也無妨, 將它設定成為 0xFFFFFFFF(INVALID_HANDLE_VALUE)就可以了.

   如果需要和物理檔案關聯, 要確保你的物理檔案建立的時候的訪問模式和"保護設定"匹配, 比如: 物理檔案唯讀, 記憶體映射需要讀寫就會發生錯誤. 推薦你的物理檔案使用獨佔方式建立.

   如果使用 INVALID_HANDLE_VALUE, 也需要設定需要申請的記憶體空間的大小, 無論物理檔案控制代碼參數是否有效, 這樣 CreateFileMapping 就可以建立一個和物理檔案大小無關的記憶體空間給你, 甚至超過實際檔案大小, 如果你的物理檔案有效, 而大小參數為0, 則返回給你的是一個和物理檔案大小一樣的記憶體空間位址範圍. 返回給你的檔案對應地址空間是可以通過複製, 整合或者命名得到, 初始內容為0.

2) 保護設定
   就是安全設定, 不過一般設定NULL就可以了, 使用預設的安全配置. 在win2k下如果需要進行限制, 這是針對那些將記憶體檔案對應共用給整個網路上面的應用進程使用是, 可以考慮進行限制.

3) 高位檔案大小
   弟兄們, 我想目前我們的機器都是32位的東東, 不可能得到超過32位進程所能定址的私人32位地址空間, 一般還是設定0吧, 我沒有也不想嘗試將它設定超過0的情況.
4) 低位檔案大小
   這個還是可以進行設定的, 不過為了讓其他共用使用者知道你申請的檔案對應的相關資訊, 我使用的時候是在獲得的地址空間頭部添加一個結構化描述資訊, 記錄記憶體映射的大小, 名稱等, 這樣實際申請的空間就比輸入的增加了一個頭資訊結構大小了, 我認為這樣類似BSTR的方式應該是比較合理的.

5) 共用記憶體名稱
   這個就是我今天測試的時候碰壁的禍根, 因為為了對於記憶體進行互斥訪問, 我設定了一個互斥控制代碼, 而名稱我選擇和命名共用記憶體同名, 之下就是因為他們使用共同的namespace導致了錯誤, 呵呵.

7) 調用CreateFileMapping的時候GetLastError的對應錯誤
   ERROR_FILE_INVALID     如果企圖建立一個零長度的檔案對應, 應有此報
   ERROR_INVALID_HANDLE   如果發現你的命名記憶體空間和現有的記憶體映射, 互斥量, 訊號量, 臨界區同名就麻煩了
   ERROR_ALREADY_EXISTS   表示記憶體空間命名已經存在

8) 相關服務或者平台的命名保留
   Terminal Services:
   命名可以包含 "Global" 或者 "Local" 首碼在全域或者會話名空間初級檔案對應. 其他部分可以包含任何除了()以外的字元, 可以參考 Kernel Object Name Spaces.

   Windows 2000 or later:
   如果 Terminal Services 沒有運行 "Global" 和 "Local" 首碼的特殊含義就被忽略了
LPVOID
WINAPI
MapViewOfFile(
    __in HANDLE hFileMappingObject,
    __in DWORD dwDesiredAccess,
    __in DWORD dwFileOffsetHigh,
    __in DWORD dwFileOffsetLow,
    __in SIZE_T dwNumberOfBytesToMap
    );

hFileMappingObject是共用檔案對象。
dwDesiredAccess是檔案分享權限設定屬性。
dwFileOffsetHigh是檔案分享權限設定區的位移地址。
dwFileOffsetLow是檔案分享權限設定區的位移地址。
dwNumberOfBytesToMap是共用資料長度。


HANDLE WINAPI OpenFileMapping(
  __in          DWORD dwDesiredAccess,
  __in          BOOL bInheritHandle,
  __in          LPCTSTR lpName
);
dwDesiredAccess Long,帶有首碼FILE_MAP_XXX的一個常數。參考MapViewOfFile函數的dwDesiredAccess參數的說明
bInheritHandle Long,如這個函數返回的控制代碼能由當前進程啟動的新進程繼承,則這個參數為TRUE
lpName String,指定要開啟的檔案對應物件名稱

 

2、共用記憶體實現進程間通訊執行個體:
進程1:
#define SHARE_MEMORY_SIZE 3145728 //3MB
#define WM_EARSE_DATA_WRITE WM_USER+110
#define WM_EARSE_DATA_READ  WM_USER+111
typedef struct tagFileInfo
{
 TCHAR szFileName[MAX_PATH];//檔案名稱
 long nOffset;//位移量
 long nImageSize;//大小
 int  nImageType;//映像類型
}FileInfo;
typedef struct tagEarseImageData
{
 FileInfo szSrc;//源檔案資訊
 FileInfo szDst1;//目標檔案1
 FileInfo szDst2;//目標檔案2
 int nStatus;//狀態 0,表示發送;1,表示接受
 int bSuccess;//成功 1,表示成功;0,表示失敗
 TCHAR szExtend[4096];//擴充
}EarseImageData;

//定義共用記憶體地區
HANDLE lhShareMemory1 = CreateFileMapping(HANDLE(0xFFFFFFFF), NULL, PAGE_READWRITE,0, SHARE_MEMORY_SIZE,L"EaresImageSharedMemory");
if (NULL==lhShareMemory1)
{
 if (GetLastError()!=ERROR_ALREADY_EXISTS)
 {
  return 0;
 }
}
////////////////////////////////////////////////////////////////////////////////
char* lpBuffer = NULL;
CxImage image1;//CxImage影像處理類
bool bRet = image1.Load(sSrcFn,CXIMAGE_FORMAT_TIF);
long nDataSize = 0;
int nImageType = 6;
lpBuffer = (char*)MapViewOfFile(lhShareMemory1, FILE_MAP_WRITE, 0, 0, SHARE_MEMORY_SIZE);
if(lpBuffer)
{
 EarseImageData imageData;
 long iOffset = sizeof(EarseImageData);
 _tcscpy(imageData.szSrc.szFileName,sSrcFn);
 imageData.szSrc.nImageType = nImageType;
 imageData.szSrc.nImageSize = nDataSize;
 imageData.szSrc.nOffset = iOffset;
 imageData.nStatus = 0;
 imageData.bSuccess = 0;
 memcpy(lpBuffer,&imageData,iOffset);
 HWND hFindWnd = ::FindWindow(NULL,L"EarseImage");//尋找另外一個進程2的視窗,發送與準備資料通知另外進程進行處理
 if(hFindWnd)
 {
  ::SendMessage(hFindWnd,WM_EARSE_DATA_WRITE,0,0);
 }
}

//WM_EARSE_DATA_READ訊息處理,用於接收進程2處理完的資料
LRESULT  CXXXDlg::OnEarseImageRead(WPARAM wParam,LPARAM lParam)
{
 HANDLE lhShareMemory;
 char* lpcBuffer;
 lhShareMemory = OpenFileMapping(FILE_MAP_READ, false,L"EaresImageSharedMemory");
 if (lhShareMemory!=NULL)
 {
  lpcBuffer = (char*)MapViewOfFile(lhShareMemory, FILE_MAP_READ, 0, 0, SHARE_MEMORY_SIZE);
  if (lpcBuffer!=NULL)
  {
   EarseImageData *pData = (EarseImageData*)lpcBuffer;
   if(pData)
   {
    if(pData->bSuccess==1)
    {
     bool bRet1 = m_img1.Decode((BYTE *)(lpcBuffer+pData->szDst1.nOffset),pData->szDst1.nImageSize,pData->szDst1.nImageType);
     bool bRet2 = m_img2.Decode((BYTE *)(lpcBuffer+pData->szDst2.nOffset),pData->szDst2.nImageSize,pData->szDst2.nImageType);
     //bRet1 = m_img1.Save(L"D://image1.tif",pData->szDst1.nImageType);
     //bRet2 = m_img2.Save(L"D://image2.tif",pData->szDst2.nImageType);
    }
   }
  }
 }
   return 1;
}

if(lpBuffer!=NULL)
{
   UnmapViewOfFile(lpBuffer);
}
if(lhShareMemory1!=NULL)
{
CloseHandle(lhShareMemory1);
}

進程2:
#define SHARE_MEMORY_SIZE 3145728 //3MB
#define WM_EARSE_DATA_WRITE WM_USER+110
#define WM_EARSE_DATA_READ  WM_USER+111
typedef struct tagFileInfo
{
   TCHAR szFileName[MAX_PATH];//檔案名稱
   long nOffset;//位移量
   long nImageSize;//大小
   int  nImageType;//映像類型
}FileInfo;
typedef struct tagEarseImageData
{
 FileInfo szSrc;//源檔案資訊
 FileInfo szDst1;//目標檔案1
 FileInfo szDst2;//目標檔案2
 int nStatus;//狀態 0,表示發送;1,表示接受
 int bSuccess;//成功 1,表示成功;0,表示失敗
 TCHAR szExtend[4096];//擴充
}EarseImageData;

//WM_EARSE_DATA_WRITE訊息處理函數
HANDLE lhShareMemory;
  char* lpcBuffer;
  lhShareMemory = OpenFileMapping(FILE_MAP_READ|FILE_MAP_WRITE, false,L"EaresImageSharedMemory");
  if (lhShareMemory!=NULL)
  {
   lpcBuffer = (char*)MapViewOfFile(lhShareMemory, FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, SHARE_MEMORY_SIZE);
   if (lpcBuffer!=NULL)
   { EarseImageData *pData = (EarseImageData *)lpcBuffer;
    CxImage image1,cxTmp1,cxTmp2;
    bool bRet = image1.Load(pData->szSrc.szFileName,pData->szSrc.nImageType);
    if(bRet)
    {
     CImageProcess g_imageProcess;//CImageProcess為影像處理類
     if(g_imageProcess.EraseRegion(&image1,&cxTmp1,&cxTmp2))
     {
      pData->bSuccess = 1;
      long iOffset = sizeof(EarseImageData);
      BYTE *lpImageData1= NULL;
      BYTE *lpImageData2 = NULL;
      int nImageType = pData->szSrc.nImageType;
      long nImageSize = 0;
      cxTmp1.Encode(lpImageData1,nImageSize,nImageType);
      pData->szDst1.nOffset = iOffset;
      pData->szDst1.nImageSize = nImageSize;
      pData->szDst1.nImageType = nImageType;
      memcpy(lpcBuffer+iOffset,lpImageData1,nImageSize);
      iOffset = iOffset+nImageSize;
      cxTmp2.Encode(lpImageData2,nImageSize,nImageType);
      pData->szDst2.nOffset = iOffset;
      pData->szDst2.nImageSize = nImageSize;
      pData->szDst2.nImageType = nImageType;
      memcpy(lpcBuffer+iOffset,lpImageData2,nImageSize);
      if(lpImageData1!=NULL)
      {
       delete []lpImageData1;
       lpImageData1 = NULL;
      }
      if(lpImageData2!=NULL)
      {
       delete []lpImageData2;
       lpImageData2 = NULL;
      }
     }
     else
     {
      pData->bSuccess = 0;
     }
    }
    else
    {
     pData->bSuccess = 0;
    }
    HWND hFindWnd = ::FindWindow(NULL,L"UnionRectTest");//尋找進程1的視窗,發送訊息通知數據已經處理完成
    if(hFindWnd)
    {
     ::SendMessage(hFindWnd,WM_EARSE_DATA_READ,0,0);
    }
   }
  }
  if(lpcBuffer!=NULL)
  {
   UnmapViewOfFile(lpcBuffer);
  }
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

 

3、注意事項
1)、共用名稱一定要特殊命名(否則就有可能因為與系統層級的對象同名而導致失敗。雖然機率不大,但不得不防)。
2)、在CreateFileMapping(...),OpenFileMapping(...),MapViewOfFile(..)函數中,映射的開啟檔案參數最好是一致的。
3)、每用MapViewOfFile(...)函數映射一次,都必須UnmapViewOfFile(...)一次。
4)、使用共用記憶體在處理大資料量資料的快速交換時表現出了良好的效能,在資料可靠性等方面要遠遠高於發送WM_COPYDATA訊息的方式;而WM_COPYDATA訊息用於小資料量的進程間交換實現更加方便簡單。

5)、用MapViewOfFile處理大檔案時,如果檔案過大,如400M,則無法一次性映射入記憶體,否則會出現1132錯誤,即記憶體不足。原因可能為作業系統無法找到連續的記憶體。因此需要通過分頁的方式,逐頁將檔案內容映射到記憶體。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.