進(jìn)程間通信的四種方式: Ø 剪貼板 Ø 匿名管道 Ø 命名管道 Ø 郵槽 1. 剪貼板: 剪貼板其實是系統(tǒng)管理的一個內(nèi)存區(qū)域,當(dāng)一個程序發(fā)生拷貝的時候,,將是該內(nèi)存區(qū)域得到填充,,使用粘貼的時候是重該區(qū)域取出數(shù)據(jù),然后顯示的對應(yīng)窗口上,。 將指定內(nèi)容賦值到剪貼板上: a. 打開剪貼板:OpenClipboard,,注意:一旦打開了剪貼版,其它運用程序?qū)o法修改剪貼板,,直到調(diào)用了CloseClipboard,。 b. 清空剪貼板:EmptyClipboard,清空剪切板,,并將所有權(quán)交付給打開剪貼板的運用程序 c. 為即將拷貝的內(nèi)容分配內(nèi)存空間:GlobalAlloc,,第一個參數(shù)指示分配內(nèi)存的類型,重要的有兩類,,GMEM_FIXED:Allocates fixed memory. The return value is a pointer,;GMEM_MOVEABLE:Allocates movable memory. In Win32, memory blocks are never moved in physical memory, but they can be moved within the default heap. The return value is a handle to the memory object. To translate the handle into a pointer, use the GlobalLock function. This flag cannot be combined with the GMEM_FIXED flag. 本例中采用GMEM_MOVEABLE,其返回值是一個指向內(nèi)存對象的句柄,。 d. 將句柄轉(zhuǎn)換為指針:GlobalLock,,將指定內(nèi)存塊鎖定。 The internal data structures for each memory object include a lock count that is initially zero. For movable memory objects, GlobalLock increments the count by one, and the GlobalUnlock function decrements the count by one. For each call that a process makes to GlobalLock for an object, it must eventually call GlobalUnlock. Locked memory will not be moved or discarded, unless the memory object is reallocated by using the GlobalReAlloc function. The memory block of a locked memory object remains locked until its lock count is decremented to zero, at which time it can be moved or discarded. e. 將字符串的內(nèi)容拷貝到可移動堆中:strcpy f. 釋放內(nèi)存塊鎖定:GlobalUnlock g. 放置數(shù)據(jù):SetClipboardData, The SetClipboardData function places data on the clipboard in a specified clipboard format. The window must be the current clipboard owner, and the application must have called the OpenClipboard function. (本例程序沒有采用)SetClipboardData的第一個參數(shù)可以是指定的格式或NULL,,如果是NULL,,則采用的是延遲提交的技術(shù),所謂延遲提交表示的是為了避免下面這種情況:當(dāng)一個拷貝數(shù)據(jù)到剪貼板的動作發(fā)生時,,直到下一個從剪貼板上取出數(shù)據(jù)的過程中,,數(shù)據(jù)一直占用著內(nèi)存空間,造成了資源浪費,。為了改善這種情況,,延遲提交技術(shù)采用SetClipboardData調(diào)用一個空的內(nèi)存區(qū)塊,當(dāng)下一個從剪貼板取出數(shù)據(jù)的動作發(fā)生時,,自動發(fā)送一個WM_RENERFORMAT消息,剪貼板的所有者程序再次調(diào)用具有實際內(nèi)存區(qū)塊參數(shù)的SetClipboardData方法,,發(fā)生實際剪貼動作,。第二次調(diào)用前不用再調(diào)用OpenClipboard方法,。 h. 關(guān)閉剪貼板:CloseClipboard 實現(xiàn)代碼如下: if(OpenClipboard()) //打開剪貼板 { CString str; HANDLE hClip; //剪貼板句柄 char* pBuf; EmptyClipboard(); GetDlgItemText(IDC_EDIT_SEND,str); //分配內(nèi)存的長度一般是字符串的長度加1用來存放空字符,否則系統(tǒng)將自動覆蓋掉現(xiàn)有字符串的最后一位用來存放空字符,,空字符作為結(jié)尾標(biāo)識 hClip=GlobalAlloc(GMEM_MOVEABLE,str.GetLength()+1); pBuf=(char*)GlobalLock(hClip); //將句柄轉(zhuǎn)換為指針,,如果GlobalAlloc參數(shù)是GMEM_FIXED,則這樣不需要這樣的轉(zhuǎn)換,。該語句將增長Lock數(shù) strcpy(pBuf,str); //為分配好的內(nèi)存空間填充想賦的值 GlobalUnlock(hClip); //如果GlobalAlloc參數(shù)是GMEM_FIXED,,則不起作用。該語句將減少Lock數(shù),,如果Lock數(shù)為0,,則指定動態(tài)內(nèi)存區(qū)域?qū)⒖杀灰苿雍蛼仐?/span> SetClipboardData(CF_TEXT,hClip); //以指定格式存放數(shù)據(jù),不完成指定格式轉(zhuǎn)換,,不能完成粘貼 CloseClipboard(); } 從剪貼板上提取數(shù)據(jù): 具體代碼如下: if(OpenClipboard()) { if(IsClipboardFormatAvailable(CF_TEXT)) //指定格式數(shù)據(jù)在接貼板上是否存在 { HANDLE hClip=GetClipboardData(CF_TEXT); //從剪貼板上得到數(shù)據(jù),,且拿到了數(shù)據(jù)塊的句柄 char* pBuf; pBuf=(char*)GlobalLock(hClip); GlobalUnlock(hClip); SetDlgItemText(IDC_EDIT_RECV,pBuf); } CloseClipboard(); } 2. what is the pipes A pipe is a section of shared memory that processes use for communication. The process that creates a pipe is the pipe server. A process that connects to a pipe is a pipe client. One process writes information to the pipe, then the other process reads the information from the pipe. This overview describes how to create, manage, and use pipes. 3. 匿名管道 創(chuàng)建父進(jìn)程: a. CreatePipe:其中第三個參數(shù)代表安全屬性結(jié)構(gòu)體 b. CreateProcess:如果創(chuàng)建管道成功,,則創(chuàng)建子進(jìn)程,并將管道的讀寫句柄傳遞給子進(jìn)程,。 創(chuàng)建匿名管道具體代碼: SECURITY_ATTRIBUTES sa; //總共就三個參數(shù) sa.bInheritHandle=TRUE; //表示可被子進(jìn)程所繼承 sa.lpSecurityDescriptor=NULL; //安全描述符號一般都設(shè)置成NULL,,即默認(rèn)描述符 sa.nLength=sizeof(SECURITY_ATTRIBUTES); //管道長度 if(!CreatePipe(&hRead,&hWrite,&sa,0)) { MessageBox("創(chuàng)建匿名函數(shù)失敗,!"); return; } //管道創(chuàng)建成功后,,接著創(chuàng)建子進(jìn)程,并將讀寫句柄傳遞給子進(jìn)程 STARTUPINFO sui; PROCESS_INFORMATION pi; //調(diào)用ZeroMemory方法將該結(jié)構(gòu)體中的所有成員都置為0,,這是因為這個結(jié)構(gòu)體的成員很多,,如果開始的時候沒有置為0的話,那它的值是隨機(jī)的,,將這樣的結(jié)構(gòu)體傳給CreateProcess,,可能會影響到執(zhí)行的結(jié)果,。 ZeroMemory(&sui,sizeof(STARTUPINFO)); sui.cb=sizeof(STARTUPINFO); //設(shè)置結(jié)構(gòu)體的大小 sui.dwFlags=STARTF_USESTDHANDLES; //該標(biāo)識表示標(biāo)準(zhǔn)輸入句柄,標(biāo)準(zhǔn)輸出句柄和錯誤句柄是有用的 sui.hStdInput=hRead; //將子進(jìn)程的輸入句柄設(shè)置成父進(jìn)程的讀句柄 sui.hStdOutput=hWrite; //將子進(jìn)程的輸出句柄設(shè)置成父進(jìn)程的寫句柄 sui.hStdError=GetStdHandle(STD_ERROR_HANDLE); //得到標(biāo)準(zhǔn)錯誤句柄,,是父進(jìn)程的錯誤句柄,,該行代碼在本程序中沒有實際的用途意義 //因為是匿名管道,是沒有名稱的管道,,只有通過CreateProcess由上而下的傳遞管道操作句柄,。 if(!CreateProcess("..\\Child\\Debug\\Child.exe",NULL,NULL,NULL, TRUE,0,NULL,NULL,&sui,&pi)) { MessageBox("創(chuàng)建子進(jìn)程失敗,!"); CloseHandle(hRead); CloseHandle(hWrite); //避免在析構(gòu)函數(shù)中再次關(guān)閉,,析構(gòu)函數(shù)采用: //if(hRead) CloseHandle(hRead) hRead=NULL; hWrite=NULL; return; } else { //創(chuàng)建一個新的進(jìn)程的時候,系統(tǒng)會創(chuàng)建一個進(jìn)程內(nèi)核對象和一個線程內(nèi)核對象,,內(nèi)核對象都有一個使用基數(shù),,初始調(diào)用的時候,都設(shè)置為1,。在CreateProcess返回之前,,該函數(shù)打開進(jìn)程和線程的內(nèi)核對象,,,并將進(jìn)程相關(guān)的句柄放置到結(jié)構(gòu)體PROCESS_INFORMATION的hProcess和hThread中,,當(dāng)Process在內(nèi)部打開這些對象的時候,使得每個對象的使用基數(shù)增加到2了,。如果在父進(jìn)程中不需要使用這兩個句柄,,就將這個句柄進(jìn)行關(guān)閉,使得使用基數(shù)減1,。當(dāng)子進(jìn)程終結(jié)的時候,,系統(tǒng)會在將使用基數(shù)減1,使得子進(jìn)程的進(jìn)程內(nèi)核對象和線程內(nèi)核對象的使用基數(shù)變?yōu)?/span>0,,這樣內(nèi)核對象就可以被釋放了,。 CloseHandle(pi.hProcess); //關(guān)閉子進(jìn)程的句柄 CloseHandle(pi.hThread); //關(guān)閉子進(jìn)程中主線程的句柄 } 父進(jìn)程讀匿名管道: char *buf="hello world"; DWORD dwWrite; if(!WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL)) { MessageBox("匿名管道寫入數(shù)據(jù)失敗,!"); return; } 父進(jìn)程寫匿名管道: char buf[100]; DWORD dwRead; if(!ReadFile(hRead,buf,100,&dwRead,NULL)) { MessageBox("匿名管道讀取數(shù)據(jù)失?。?/span>"); return; } MessageBox(buf); 創(chuàng)建子進(jìn)程程序: 可以將獲取父進(jìn)程的匿名管道的讀寫句柄操作放在CView類的OnInitialUpdate方法中實現(xiàn),,該方法是在CView完全構(gòu)造后調(diào)用的第一個方法,。代碼如下: hRead=GetStdHandle(STD_INPUT_HANDLE); hWrite=GetStdHandle(STD_OUTPUT_HANDLE); 子進(jìn)程的讀寫匿名管道的代碼和父進(jìn)程的一樣,這里不再累述,。 4. 命名管道: 命名管道是通過網(wǎng)絡(luò)來完成進(jìn)程間的通信,,它屏蔽了底層的網(wǎng)絡(luò)協(xié)議細(xì)節(jié)。我們在不了解網(wǎng)絡(luò)協(xié)議的情況下,,也可以利用命名管道來實現(xiàn)進(jìn)程間的通信,。而上述的匿名管道只能在本地機(jī)器上,,且連個父子進(jìn)程間通信,。命名管道也具有匿名管道的功能,。 命名管道服務(wù)器和客戶機(jī)的區(qū)別在于:服務(wù)器是唯一一個有權(quán)創(chuàng)建命名管道的進(jìn)程,也只有它才能接受管道客戶機(jī)的連接請求,。而客戶機(jī)只能同一個現(xiàn)成的命名管道服務(wù)器建立連接,。 命名管道提供了兩種基本通信模式:字節(jié)模式和消息模式。在字節(jié)模式中,,數(shù)據(jù)以一個連續(xù)的字節(jié)流的形式,,在客戶機(jī)和服務(wù)器之間流動。而在消息模式中,,客戶機(jī)和服務(wù)器則通過一系列不連續(xù)的數(shù)據(jù)單位,,進(jìn)行數(shù)據(jù)的收發(fā),每次在管道上發(fā)出了一條消息后,,它必須作為一條完整的消息讀入,。 命名管道服務(wù)器端代碼(核心為命名管道的創(chuàng)建與等待客戶端的連接): 說明:CreateNamedPipe,創(chuàng)建命名管道,,其中第一個參數(shù)管道的名稱是格式為"\\.\pipe\pipename", 在VC中使用的時候,,因涉及到轉(zhuǎn)義符,作為字符串,,應(yīng)使用"\\\\.\\pipe\\pipename",,其中pipe不能更改,大小寫沒有區(qū)分 hPipe=CreateNamedPipe("\\\\.\\pipe\\MyPipe",PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,0,1,1024,1024,0,NULL); if(INVALID_HANDLE_VALUE==hPipe) { MessageBox("創(chuàng)建命名管道失??!"); CloseHandle(hPipe); hPipe=NULL; return; } HANDLE hEvent; hEvent=CreateEvent(NULL,TRUE,FALSE,NULL); if(!hEvent) { MessageBox("創(chuàng)建事件對象失敗,!"); CloseHandle(hPipe); hPipe=NULL; return; } OVERLAPPED ovlap; //這里調(diào)用ZeroMemory和上一章的意義是一樣的,,為了避免ConnectNamedPipe在調(diào)用該結(jié)構(gòu)中使用的是一些不可欲知的參數(shù)值,防止有影響 ZeroMemory(&ovlap,sizeof(OVERLAPPED)); ovlap.hEvent=hEvent; 說明:等待客戶端連接到一個命名管道實例,。If hNamedPipe was created with FILE_FLAG_OVERLAPPED and lpOverlapped is not NULL, the OVERLAPPED structure pointed to by lpOverlapped must contain a handle to a manual-reset event object (which the server can create by using the CreateEvent function).這是為什么上面需要申明一個自動的事件的對象,。 if(!ConnectNamedPipe(hPipe,&ovlap)) { if(ERROR_IO_PENDING!=GetLastError()) { MessageBox("等待客戶端的連接失敗,!"); CloseHandle(hPipe); CloseHandle(hEvent); hPipe=NULL; } } //等待事件狀態(tài)有效,,如果當(dāng)前無效,INFINITE參數(shù)表明則一直等待下去 if(WAIT_FAILED==WaitForSingleObject(hEvent,INFINITE)) { MessageBox("等待對象失??!"); CloseHandle(hPipe); CloseHandle(hEvent); hPipe=NULL; } CloseHandle(hEvent); //說明已經(jīng)有客戶端連接到了 命名管道的讀寫和上一章是類似的,這里就省略掉了 命名管道的客戶端實現(xiàn)(核心為命名管道的連接): //WaitNamePipe的參數(shù)NMPWAIT_WAIT_FOREVER一直等待下去,,直到等待到可用的連接,,當(dāng)然也可以設(shè)置超時的時間,,但前提是所有的程序里所有的命名管道的超時時間必須一樣 if(!WaitNamedPipe("\\\\.\\pipe\\MyPipe",NMPWAIT_WAIT_FOREVER)) { MessageBox("當(dāng)前沒有可用的命名管道實例"); return; } //打開命名管道,建立連接 hPipe=CreateFile("\\\\127.0.0.1\\pipe\\MyPipe",GENERIC_READ|GENERIC_WRITE, 0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if(INVALID_HANDLE_VALUE==hPipe) { MessageBox("打開命名管道失??!"); hPipe=NULL; } 注意:程序的運行方式為先點擊服務(wù)端的創(chuàng)建命名管道,然后點擊客戶端的連接管道,,再點擊服務(wù)端的發(fā)送數(shù)據(jù),,再在客戶端點擊接收數(shù)據(jù)。 5. 郵槽: Ø 郵槽是基于廣播通信體系設(shè)計出來的,,它采用無連接的不可靠的數(shù)據(jù)傳輸,。 Ø 郵槽是一種單向通信機(jī)制,創(chuàng)建郵槽的服務(wù)器進(jìn)程讀取數(shù)據(jù),,打開郵槽的客戶機(jī)進(jìn)程寫入數(shù)據(jù),。 Ø 為保證郵槽在各種Windows平臺下都能夠正常工作,我們傳輸消息的時候,,應(yīng)將消息的長度限制在424字節(jié)以下,。 服務(wù)器端的代碼實現(xiàn): HANDLE hMailslot; hMailslot=CreateMailslot("\\\\.\\mailslot\\MyMailSlot",0,MAILSLOT_WAIT_FOREVER,NULL); if(INVALID_HANDLE_VALUE==hMailslot) { MessageBox("創(chuàng)建郵槽失敗,!"); return; } char buf[100]; DWORD dwRead; if(!ReadFile(hMailslot,buf,100,&dwRead,NULL)) { MessageBox("讀取數(shù)據(jù)失?。?/span>"); CloseHandle(hMailslot); hMailslot=NULL; return; } MessageBox(buf); CloseHandle(hMailslot); 客戶端的代碼實現(xiàn): HANDLE hMailslot; //因為郵槽的客戶是負(fù)責(zé)寫入數(shù)據(jù),,所以訪問方式只需要是寫,,但對于服務(wù)器端而言,是讀取數(shù)據(jù),,所以客戶端共享方式設(shè)置為讀FILE_SHARE_READ hMailslot=CreateFile("\\\\.\\mailslot\\MyMailSlot",GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if(INVALID_HANDLE_VALUE==hMailslot) { MessageBox("打開郵槽失?。?/span>"); return; } char buf[]="this is test!"; DWORD dwWrite; if(!WriteFile(hMailslot,buf,strlen(buf)+1,&dwWrite,NULL)) { MessageBox("寫入數(shù)據(jù)失敗"); CloseHandle(hMailslot); return; } CloseHandle(hMailslot); 注意:程序的運行方式為先點擊服務(wù)器端的接收數(shù)據(jù)以此創(chuàng)建一個郵槽,,然后在點擊客戶端的發(fā)送數(shù)據(jù),,最終服務(wù)器端會接收到數(shù)據(jù)。 |
|