天天看點

《VC++深入詳解》學習筆記[14]——第17章 程序間通信

第17章 程序間通信

1.剪貼闆

剪貼闆實際上是系統維護管理的一塊記憶體區域。如果某個程式已經打開了剪貼闆,則其他應用程式将不能修改剪貼闆,知道前者調用了CloseClipboard函數。并且,隻有在調用了EmptyClipboard函數之後,打開剪貼闆的目前視窗才擁有剪貼闆。

資料發送:

void CClipboardDlg::OnBtnSend()

{

       if(OpenClipboard())

       {

              CString str;

              HANDLE hClip; //記憶體對象句柄;記憶體是會移動的(作業系統移動)隻能用句柄辨別

              char *pBuf;

              EmptyClipboard();//清空剪貼闆并釋放剪貼闆中資料的句柄,然後将剪貼闆的所有權配置設定給目前打開剪貼闆的視窗

              GetDlgItemText(IDC_EDIT_SEND,str);

              //配置設定一個記憶體對象

              hClip = GlobalAlloc(GMEM_MOVEABLE, str.GetLength()+1);

              //對一個記憶體位址加鎖并傳回記憶體位址

              pBuf = (char *) GlobalLock(hClip); //将記憶體對象句柄轉換為指針

              //将資料拷貝到記憶體中

              strcpy(pBuf,str);

              GlobalUnlock(hClip);

              SetClipboardData(CF_TEXT,hClip); //

              CloseClipboard();//記住要關閉剪貼闆

       }

}

注:一般情況下在程式設計的時候,給應用程式配置設定的記憶體都是可以移動的或者是可以丢棄的,這樣能使有限的記憶體資源充分利用,是以,在某一個時候我們配置設定的那塊記憶體的位址是不确定的,因為他是可以移動的,是以得先鎖定那塊記憶體塊,這樣應用程式才能存取這塊記憶體。使用GlobalLock的目的是為了保證記憶體管理時真的是用記憶體而不是“虛拟記憶體的磁盤鏡像”,否則效率會降低。

資料接收:

void CClipboardDlg::OnBtnRecv()

{

       if(OpenClipboard())

       {//因為在接收端隻需從剪貼闆中得到資料,而不用向剪貼闆中寫入資料,是以不要調用EmptyClipboard;

              if(IsClipboardFormatAvailable(CF_TEXT))//檢查剪貼闆中是否有想要的特定格式的資料

              {

                     HANDLE hClip;

                     hClip = GetClipboardData(CF_TEXT);

                     char *pBuf;

                     pBuf = (char*)GlobalLock(hClip);

                     GlobalUnlock(hClip);//将記憶體解鎖

                     SetDlgItemText(IDC_EDIT_RECV,pBuf);

                     CloseClipboard();

              }

       }

}

2.匿名管道

匿名管道是一個未命名的單向管道,通常用來在一個父程序和一個子程序之間傳輸資料。匿名管道隻能實作本地機器上兩個程序間的通信,而不能實作跨網絡的通信。因為匿名管道隻能在父子程序之間進行通信,子程序如果想要獲得匿名管道的句柄,隻能從父程序繼承而來。當一個子程序從其父程序繼承了匿名管道的句柄之後,這兩個程序就可以通過該句柄進行通信了。

父程序的實作:

定義兩個HANDLE變量:m_hRead, m_hWrite

1.建立匿名管道

void CParentView::OnPipeCreate()

{

       SECURITY_ATTRIBUTES sa;//定義安全屬性結構體

       sa.bInheritHandle = TRUE; //子程序可以繼承父程序建立的匿名管道的讀寫句柄

       sa.lpSecurityDescriptor = NULL;

       sa.nLength = sizeof(SECURITY_ATTRIBUTES);

       if(!CreatePipe(&m_hRead,&m_hWrite,&sa,0))

       {

              MessageBox ("建立匿名管道失敗!");

              return ;

       }

//匿名管道建立成功則啟動子程序并将其讀、寫句柄傳遞給子程序

       STARTUPINFO sui;//用來指定新程序主視窗如何出現的結構體

       PROCESS_INFORMATION pi;//程序資訊結構體

       ZeroMemory(&sui,sizeof(STARTUPINFO));//結構體所有成員置為0,防止未設值的屬性擁有随機值

       sui.cb = sizeof(STARTUPINFO);//指定結構體大小

       sui.dwFlags = STARTF_USESTDHANDLES;

       sui.hStdInput = m_hRead;//将标準讀取句柄設定為管道讀取句柄

       sui.hStdOutput =m_hWrite;//标準寫入句柄設定為管道寫入句柄

       sui.hStdError = GetStdHandle(STD_ERROR_HANDLE);

              //通過GetStdHandle傳回一個父程序的标準錯誤句柄

       if(!CreateProcess("..\\Child\\Debug\\Child.exe", //啟動子程序,并将子程序的标準輸入輸出句柄設定為匿名管道的讀、寫句柄

              NULL,//傳遞指令行參數

              NULL,//程序安全屬性

              NULL,//線程安全屬性

              TRUE,// handle inheritance flag

              0,    //建立标記

              NULL,//環境塊

              NULL,//目前路徑,NULL讓子程序與父程序有相同路徑

              &sui,//指定新程序主視窗如何出現

              &pi))//用來接收關于新的程序的辨別資訊

       {//如果建立子程序失敗

              CloseHandle (m_hRead);

              CloseHandle (m_hWrite);

              m_hRead=NULL;

              m_hWrite=NULL;

              MessageBox ("建立子程序失敗!");

              return;

       }///...endof if

       else

       {

              CloseHandle (pi.hProcess);  //關閉所傳回的子程序句柄

              CloseHandle (pi.hThread);  //關閉子程序中主線程句柄

       }

}

注:為了讓子程序從衆多繼承的句柄中區分出管道的讀寫句柄,就必須将子程序的特殊句柄設定為管道的讀寫句柄。這裡将子程序的标準輸入輸出句柄分别設定為管道的讀、寫句柄,這樣在子程序中,隻要得到了标準輸入和标準輸出句柄,就相當于得到了這個管道的讀寫句柄。

2.讀取資料:

void CParentView::OnPipeRead()

{

       char buf[100];

       DWORD dwRead;

       if(!ReadFile(m_hRead,buf,100,&dwRead,NULL))

       {

              MessageBox ("讀取資料失敗!");

              return ;

       }

       else MessageBox (buf);

}

3.寫入資料:

void CParentView::OnPipeWrite()

{

       char buf[]="http://blog.csdn.net/teshorse";

       DWORD dwWrite;

       if(!WriteFile(m_hWrite,buf,strlen(buf)+1,

              &dwWrite,NULL))

       {

              MessageBox ("寫入資料失敗");

              return ;

       }

}

對于管道的讀取和寫入實際上是通過調用ReadFile和WriteFile這兩個函數完成的。

子程序的實作:

定義兩個HANDLE變量:hRead, hWrite

1.獲得管道的讀取和寫入句柄

void CChildView::OnInitialUpdate() //當視窗成功調用之後第一個創造的函數

{

       CView::OnInitialUpdate();

       //擷取子程序的标準輸入輸出句柄.

       m_hRead =GetStdHandle(STD_INPUT_HANDLE);

       m_hWrite = GetStdHandle(STD_OUTPUT_HANDLE);

}

2.讀取資料

void CChildView::OnPipeRead()

{

       char buf[100];

       DWORD dwRead;

       if(!ReadFile(m_hRead,buf,100,&dwRead,NULL))

       {

              MessageBox ("讀取資料失敗!");

              return ;

       }

       else MessageBox (buf);

}

3.寫入資料

void CChildView::OnPipeWrite()

{

       char buf[]="匿名管道測試程式";

       DWORD dwWrite;

       if(!WriteFile(m_hWrite,buf,strlen(buf)+1,

              &dwWrite,NULL))

       {

              MessageBox ("寫入資料失敗");

              return ;

       }

}

注:匿名管道隻能在父子程序之間通信。兩個程序如果想要具有父子關系,必須由父程序通過調用CreateProcess函數去啟動子程序。因為匿名管道沒有名稱,所有隻能在父程序中調用CreateProcess函數建立子程序時将管道的讀、寫句柄傳遞給子程序。

       利用匿名管道也可以實作在同一個程序内讀取和寫入資料。

3.命名管道

基礎知識:

       ①命名管道通過網絡來完成程序間的通信,它屏蔽了底層的網絡協定細節。

       ②命名管道充分利用了Windows内建的安全機制,可以指定使用者權限。是以差別于Sockets編寫網絡應用,使用命名管道無需編寫驗證使用者身份的代碼。

       ③命名管道實際上是建立了一個CS通信體系,并在其中可靠地傳輸資料。命名管道是圍繞Windows檔案系統設計的一種機制,采用“命名管道檔案系統”接口,是以,客戶機和伺服器可利用标準Win32檔案系統函數來進行資料的收發。

       ④命名管道伺服器和客戶機的差別在于:伺服器是唯一一個有權建立命名管道的程序,也隻有它才能接受管道客戶機的連接配接請求。而客戶機隻能同一個現成的命名管道伺服器建立連接配接。命名管道伺服器隻能在WindowsNT、2000等系統上建立。

       ⑤命名管道提供了兩種基本通信模式:位元組模式和消息模式。在消息模式下通過一系列不連續的資料機關進行資料發送。

       ⑥對同一個命名管道的執行個體來說,在某一時刻,它隻能和一個用戶端進行通信。

實作過程:在伺服器端調用CreateNamePipe建立命名管道之後,調用ConnectNamedPipe函數讓伺服器端程序等待用戶端程序連接配接到該命名管道的執行個體上。在用戶端首先調用WwaitNamePipe函數判斷目前是否有可以利用的命名管道執行個體,如果有,就調用CreateFile函數打開該命名管道的執行個體,并建立一個連接配接。

4.郵槽

       郵槽是基于廣播通信體系設計出來的,它采用無連接配接的不可靠的資料傳輸。郵槽是一種單向通信機制,建立郵槽的伺服器程序讀取資料,打開郵槽的客戶機程序寫入資料。郵槽适用于開發一對多的廣播通信系統。

繼續閱讀