轉自:http://www.cnblogs.com/BoyXiao/archive/2010/12/25/1916677.html
引子
由於在啓動一個進程後,操作系統會給這個進程分配 4GB 的私有地址空間,至於爲何有 4GB 這麼大,
那得考慮進程的私有地址空間和實際物理內存地址空間之間的映射以及頁交換等等細節問題了,這裏不予討論,
從名字就可以知道,既然操作系統給每一個進程分配的是私有地址空間,
自然,這段地址空間也只有這個進程自己才能訪問了,不然還稱爲私有幹嗎呢?
既然這段私有地址空間只能由進程本身訪問,那也就說明別的進程是不能夠隨意的訪問這個進程的地址空間的,
而本篇博文介紹的是進程間的通信,而上面又說任意兩個進程之間是並能夠互相訪問對方的私有地址空間的,
都不能訪問了,那還通信個屁啊 ?
自然上面的訪問對方進程的私有地址空間是行不通了,那應該還有其他辦法的 !!!
解決方法:
如果我在物理內存中劃分出一塊內存,這一塊內存不爲任何的進程所私有,但是任何的進程又都可以訪問這塊內存,
那麼 進程 A 就可以往這塊內存中存放數據 Data ,然後 進程 B 也是可以訪問這塊內存的,從而 進程 B 就可以訪問到數據 Data 了,
這樣不就實現了 進程 A 和 進程 B 之間的通信了 !!!
而上面的這種思路就是剪貼板了。
當然解決進程間通信還有好幾種思路,這將會在後續博文中介紹,本篇博文暫只介紹利用剪貼板來實現進程間的通信。
剪貼板定義
剪貼板是由操作系統維護的一塊內存區域,這塊內存區域不屬於任何單獨的進程,但是每一個進程又都可以訪問這塊內存區域,
而實質上當在一個進程中複製數據時,就是將數據放到該內存區域中,
而當在另一個進程中粘貼數據時,則是從該塊內存區域中取出數據。
剪貼板操作
其實在剪貼板中也就那麼幾個 API 在使用,所以在這裏的還是本着 API 介紹爲主,
不管三七二十一,先列出常用的 API 再說(到後面結合 Demo 的使用即可)。
剪貼板的打開 – OpenClipboard
要想把數據放置到剪貼板中,則必須先打開剪貼板,而這是通過 OpenClipboard 成員函數實現:
BOOL OpenClipboard(HWND hWndNewOwner );
第一個參數 hWndNewOwner 指向一個與之關聯的窗口句柄,即代表是這個窗口打開剪貼板,
如果這個參數設置爲 NULL 的話,則以當前的任務或者說是進程來打開剪貼板。
如果打開剪貼板成功,則該函數返回非 0 值,如果其他程序已經打開了剪貼板,
那麼當前這個程序就無法再打開剪貼板了,所以會致使打開剪貼板失敗,從而該函數返回 0 值。
其實這也好理解,你想啊,剪貼板總共才那麼一塊內存區域,你 進程 A 要往裏面寫數據,你 進程 B 又要往裏面寫數據,那不亂套去,
解決這個亂套的辦法就是,如果我 進程 A 正在往剪貼板裏面寫數據(可以理解爲 進程 A 打開剪貼板了),那麼 進程 B 就不能往剪貼板裏頭寫數據了,
既然要讓 進程 B 不能往剪貼板中寫數據了,那我就讓 進程 B 打開剪貼板失敗不就得了。
所以如果某個程序已經打開了剪貼板,那麼其他應用程序將不能修改剪貼板,
直到打開了剪貼板的這個程序調用了 CloseClipboard 函數,
並且只有在調用了 EmptyClipboard 函數之後,打開剪貼板的當前窗口才能擁有剪貼板,
注意是必須要在調用了 EmptyClipboard 函數之後才能擁有剪貼板。
剪貼板的清空 - EmptyClipboard
這個函數將清空剪貼板,並釋放剪貼板中數據的句柄,然後將剪貼板的所有權分配給當前打開剪貼板的窗口,
因爲剪貼板是所有進程都可以訪問的,
所以應用程序在使用這個剪貼板時,有可能已經有其他的應用程序把數據放置到了剪貼板上,
因此該進程打開剪貼板之後,就需要調用 EmptyClipboard 函數來清空剪貼板,
釋放剪貼板中存放的數據的句柄,並將剪貼板的所有權分配給當前的進程,
這樣做之後當前打開這個剪貼板的程序就擁有了剪貼板的所有權,因此這個程序就可以往剪貼板上放置數據了。
BOOL EmptyClipboard(void);
剪貼板的關閉 - CloseClipboard
如果某個進程打開了剪貼板,則在這個進程沒有調用 CloseClipboard 函數關閉剪貼板句柄之前,
其他進程都是無法打開剪貼板的,所以我們每次使用完剪貼板之後都應該關閉剪貼板。
注意,這裏的關閉剪貼板並不代表當前打開剪貼板的這個程序失去了對剪貼板的所有權,
只有在別的程序調用了 EmptyClipboard 函數之後,當前的這個程序纔會失去對剪貼板的所有權,
而那個調用 EmptyClipboard 函數的程序才能擁有剪貼板。
BOOL CloseClipboard(void);
數據發送到剪貼板 - SetClipboardData
可以通過 SetClipboardData 函數來實現往剪貼板中放置數據,這個函數以指定的剪貼板格式向剪貼板中放置數據。
HANDLE SetClipboardData(UINT uFormat, HANDLE hMem );
第一個參數 uFormat 用來指定要放到剪貼板上的數據的格式,
比如常見的有 CF_BITMAP ,CF_TEXT ,CF_DIB 等等(其他格式可以參考 MSDN)。
第二個參數 hMem 用來指定具有指定格式的數據的句柄,該參數可以是 NULL ,
如果該參數爲 NULL 則表明直到有程序對剪貼板中的數據進行請求時,
該程序(也就是擁有剪貼板所有權的進程)纔會將數據複製到剪貼板中,也就是提供指定剪貼板格式的數據,
上面提及的就是延遲提交技術,這個延遲提交技術將會在後面做詳細的介紹。
剪貼板中數據格式判斷 – IsClipboardFormatAvaliable
BOOL IsClipboardFormatAvailable( UINT format );
該函數用來判斷剪貼板上的數據格式是否爲 format 指定的格式。
剪貼板中數據接收 - GetClipboardData
HANDLE GetClipboardData( UINT uFormat );
該函數根據 uFormat 指定的格式,返回一個以指定格式存在於剪貼板中的剪貼板對象的句柄。
全局內存分配 – HGLOBAL
剪貼板中的內存從何而來
從上面的介紹中可以知道剪貼板其實就是一塊內存,那麼這塊內存是什麼時候分配的呢?
難不成說一開機,操作系統就給剪貼板分配個幾 M 的內存的吧?
這種方式也太遜色了,你想啊,我的程序要往剪貼板中放置的數據,我事先又不曉得數據長度,
所以,一開機操作系統究竟要給剪貼板分配多少內存呢?很明顯,太不動態了,不可取。
要想動態的話,那有一種方案,就是當我的程序要往剪貼板中放置數據的時候來確定要分配給剪貼板的內存的大小,
很明顯,既然我都知道要往剪貼板中放置那些數據了,自然我也就知道了這些數據的長度,
那麼我就可以以這個數據長度來給剪貼板分配內存了,這是很動態的了吧,所以這種方案是可取的,
但關鍵是,當我們以前在程序中分配內存的時候,都是使用的標準 C 運行庫中的 malloc 或者是 C++ 中的 new 關鍵字,
(當然分配內存還有很多其他的函數,比如就有內核中的執行體中就有很多分配內存的函數,這裏不討論),
而使用 malloc 或者 new 有一個問題,那就是,用這個兩個東西來分配的內存空間都是在當前進程的私有地址空間上分配內存,
也就是它們兩個東東所分配的內存空間爲進程私有地址空間所有,並不爲所有進程所共享,
上面提到了,任何進程之間都是不能訪問對方的私有地址空間的,你把剪貼板中的內存分配到了你當前進程的私有地址空間上,
而其他進程又不能訪問你這個進程的私有地址空間,那怎麼能夠訪問剪貼板呢?
很明顯,不能使用 malloc 和 new 關鍵字來分配內存給剪貼板。
我們應該要使用另外一個特殊一點的函數來分配內存給剪貼板,
這個特殊函數所分配的內存不能夠是在進程的私有地址空間上分配,而是要在全局地址空間上分配內存,
這樣這個函數所分配的內存才能夠被所有的進程所共享,這樣,剪貼板中的數據就可以被其他的進程所訪問了。
GlobalAlloc 函數
GlobalAlloc 函數是從堆上分配指定數目的字節,
與其他的內存管理函數相比,全局內存函數的運行速度會稍微慢一些(等下會解釋爲什麼會慢),
但是全局函數支持動態數據交換,同時,其分配的內存也不爲任何一個進程所私有,而是由操作系統來管理這塊內存,
所以用在給剪貼板分配內存空間是很適合的。
這裏有讀者可能會問:
爲什麼我們在自己的應用程序中不使用 GlobalAlloc 函數來分配內存,而是要使用 malloc 或者 new 來實現?
其實,這個也只用稍微想想就知道了,你想啊,使用 malloc 或者 new 分配的內存是在進程的私有地址空間上分配的,
這片私有地址空間都是歸這個進程所擁有,所管理的,自然,在以後對這塊內存的讀寫會快很多的,
而全局內存不屬於這個進程,你下次要去訪問全局內存的時候,還得通過映射轉換,這樣肯定是運行效率低下一些了,
簡單點就可以這樣理解,你使用 malloc 或者 new 分配的內存和你的進程隔得很近,程序要過去拿數據 - 得,很近吧,
而是用 GlobalAlloc 函數分配的內存和你的進程隔得很遠,程序要過去拿數據 - 太遠了,耗時。
應用程序在調用了 SetClipboardData 函數之後,
系統就擁有了 hMem 參數所標識的數據對象,該應用程序可以讀取這個數據對象,
但是在應用程序調用 CloseClipboard 函數之前,它都是不能釋放該對象的句柄的,或者鎖定這個句柄,
如果 hMem 標識一個內存對象,那麼這個對象必須是利用 GMEM_MOVEABLE 標識調用 GlobalAlloc 函數爲其分配內存的。
HGLOBAL WINAPI GlobalAlloc( UINT uFlags, SIZE_T dwBytes );
第一個參數 uFlags 用來指定分配內存的方式。其取值如下列表所示
(但是在剪貼板的使用中,由於要實現動態數據交換,所以必須得使用 GHND 或者 GMEM_MOVEABLE):
值 |
描述 |
GHND |
即 GMEM_MOVEABLE 和 GMEM_ZEROINIT 的組合。 |
GMEM_FIXED |
分配一塊固定內存,返回值是一個指針。 |
GMEM_MOVEABLE |
分配一塊可移動內存。 |
GMEM_ZEROINIT |
初始化內存的內容爲 0 |
GPTR |
即 GMEM_FIXED 和 GMEM_ZEROINIT 的組合。 |
第二個參數 dwBytes 用來指定分配的字節數。
GlobalReAlloc 函數
HGLOBAL WINAPI GlobalReAlloc(HGLOBAL hMem, SIZE_T dwBytes, UINT uFlags);
該函數爲再分配函數,即在原有的數據對象 hMem 上,爲其擴大內存空間。
第一個參數 hMem 代表由 GlobalAlloc 函數返回的數據對象句柄。
第二個參數 dwBytes 指定需要重新分配的內存的大小。
第三個參數 uFlags 指定分配的方式(可以參考 GlobalAlloc 函數)。
GlobalSize 函數
SIZE_T WINAPI GlobalSize( HGLOBAL hMem );
該函數用來返回內存塊的大小。
第一個參數 hMem 代表由 GlobalAlloc 函數返回的數據對象句柄。
GlobalLock 函數
LPVOID WINAPI GlobalLock( HGLOBAL hMem );
該函數的作用是對全局內存對象加鎖,然後返回該對象內存塊第一個字節的指針。
第一個參數 hMem 代表由 GlobalAlloc 函數返回的數據對象句柄。
GlobalUnLock 函數
BOOL WINAPI GlobalUnlock( HGLOBAL hMem );
你通過上面的 GlobalLock 函數可以獲得這塊全局內存的訪問權,
加鎖的意思就是你已經在使用這塊全局內存了,別的程序就不能再使用這塊全局內存了,
而如果你一直不解鎖,那也不是個事啊,別的程序將會一直都使用不了這塊全局內存,
那還叫全局內存幹嗎啊?所以這個函數就是用來對全局內存對象解鎖。
第一個參數 hMem 代表由 GlobalAlloc 函數返回的數據對象句柄。
GlobalFree 函數
HGLOBAL WINAPI GlobalFree( HGLOBAL hMem );
該函數釋放全局內存塊。
第一個參數 hMem 代表由 GlobalAlloc 函數返回的數據對象句柄。
Demo1 – ConsoleClipboard(剪貼板常用手法)
整個項目結構很簡單:
ConsoleClipboard.h
#ifndef CONSOLE_CLIP_BOARD_H
#define CONSOLE_CLIP_BOARD_H
#include <Windows.h>
#include <iostream>
using namespace std;
const char * pStrData = "Zachary";
void SetClipBoardData();
void GetClipBoardData();
#endif
ConsoleClipboard.cpp
#include "ConsoleClipboard.h"
int main(int argc, char * argv)
{
SetClipBoardData();
GetClipBoardData();
system("pause");
}
void SetClipBoardData()
{
//將 OpenClipboard 函數的參數指定爲 NULL,表明爲當前進程打開剪貼板
if(OpenClipboard(NULL))
{
char * pDataBuf;
//全局內存對象
HGLOBAL hGlobalClip;
//給全局內存對象分配全局內存
hGlobalClip = GlobalAlloc(GHND, strlen(pStrData) + 1);
//通過給全局內存對象加鎖獲得對全局內存塊的引用
pDataBuf = (char *)GlobalLock(hGlobalClip);
strcpy(pDataBuf, pStrData);
//使用完全局內存塊後需要對全局內存塊解鎖
GlobalUnlock(hGlobalClip);
//清空剪貼板
EmptyClipboard();
//設置剪貼板數據,這裏直接將數據放到了剪貼板中,而沒有使用延遲提交技術
SetClipboardData(CF_TEXT, hGlobalClip);
//關閉剪貼板
CloseClipboard();
cout<<"設置剪貼板爲: "<<pStrData<<endl<<endl;
}
}
void GetClipBoardData()
{
if(OpenClipboard(NULL))
{
//判斷剪貼板中的數據格式是否爲 CF_TEXT
if(IsClipboardFormatAvailable(CF_TEXT))
{
char * pDataBuf;
HGLOBAL hGlobalClip;
//從剪貼板中獲取格式爲 CF_TEXT 的數據
hGlobalClip = GetClipboardData(CF_TEXT);
pDataBuf = (char *)GlobalLock(hGlobalClip);
GlobalUnlock(hGlobalClip);
cout<<"從剪貼板中獲取到數據: "<<pDataBuf<<endl<<endl;
}
CloseClipboard();
}
}
效果展示:
程序運行效果:
打開記事本進行粘貼操作:
延遲提交技術
什麼是延遲提交技術?
當把數據放入剪貼板中時,一般來說要製作一份數據的副本,
也就是要分配全局內存,然後將數據再複製一份,然後再將包含這份副本的內存塊句柄傳遞給剪貼板,
對於小數據量來說,這個沒什麼,但是對於大數據量的話,就有問題了,
你一使用剪貼板,就往裏面複製個什麼幾百 MB 的數據,
那這個數據在剪貼板中的數據被其他數據取代之前都是存放在內存中的啊,
這個方法也太齷齪了,你想啊,要是我就複製了一個 500MB 的數據,然後我一直不再複製其他的東西,
那麼這個 500MB 的數據就會一直駐留在內存中,咦 . . . 太可怕了 !!!太浪費內存的使用效率了 !!!
爲了解決上面這個問題,就需要通過使用延遲提交技術來避免內存的浪費,
當使用延遲提交技術時,實際上,直到另一個程序需要數據時,程序纔會提供這份數據,
也就是,其實我一開始 程序 A 並不往剪貼板中存放真實的數據,
而只是告訴剪貼板,我往裏面放了數據(其實數據還沒有放進去),
而後,如果有其他的 程序 B 訪問了剪貼板中的數據,也就是執行了“粘貼”操作,
那麼此時操作系統就會去檢查數據是不是真正的存放在了剪貼板中,
如果剪貼板中存放了數據,那麼直接把數據送出去就可以了(這就沒有使用延遲提交技術了),
而如果剪貼板中沒有數據,那麼 Windows 就會給上次往剪貼板中存放數據(儘管沒有存放實際的數據)的程序,
也就是 程序 A發送消息,
而後,我們的 程序 A 就可以再次調用 SetClipboardData 來將真實的數據放入到剪貼板中了,這樣就是延遲提交技術了。
要實現延遲提交技術,則在 程序 A 中不應該將數據句柄傳送給 Windows ,
而是在 SetClipboardData 調用中使用 NULL。
然後當另外一個 程序 B 調用 GetClipboardData 函數時,
Windows 就會檢查這種格式的數據在剪貼板中的句柄是否爲 NULL ,
如果爲 NULL ,則 Windows 會給程序 A發送一個消息,從而請求到數據的實際句柄,
這個數據的實際句柄是 程序 A 在響應消息的處理函數中重新調用 SetClipboardData 來提供的。
延遲提交技術中涉及的三個消息:
下面提及的 程序 A 代表剪貼板當前擁有者,也就是 程序 A 負責往剪貼板中寫入數據,
而 程序 B 則代表從剪貼板中讀取出數據,其沒有對剪貼板的所有權。
WM_RENDERFORMAT :
當 程序 B 調用 GetClipboardData 時,Windows 將會給 程序 A 的窗口過程發送這個消息,
其中 wParam 參數的值是所要求的格式。
在處理這個消息時,程序 A 就不再需要打開或者清空剪貼板了,
也就是不需要再次調用 OpenClipboard 和 EmptyClipboard 函數了,
爲什麼不需要再次調用這兩個函數?
這是因爲,我們一開始的時候已經調用了這兩個函數(如果一開始沒有調用的話,窗口根本就不會接受到這個消息),
而此舉已經告訴操作系統剪貼板已經歸我所有了,而且裏面的數據已經被清空了,
剪貼板所有權都歸我了,那還去打開個鬼啊,不是浪費嘛?
在處理這個消息時,應該爲 wParam 所指定的格式創建一個全局內存塊,
然後再把數據傳遞到這個全局內存塊中,並要正確的格式和數據句柄再一次調用 SetClipboardData 函數。
也就是需要將數據真實的複製到剪貼板中了。
WM_RENDERALLFORAMTS :
如果 程序 A 在它自己仍然是剪貼板所有者的時候就要終止運行,
並且剪貼板上仍然包含着該 程序 A 用 SetClipboardData 所設置的 NULL 數據句柄(延遲提交技術),
也就是 程序 A 當前還是剪貼板的所有者,但是用戶又單擊了關閉窗口,
而剪貼板中還沒有真實的數據存在(因爲使用了延遲提交技術),
即數據還沒有被提交給剪貼板,程序 A 就要死了,則此時 程 序 A 的窗口過程將接收到這個消息,
這個消息的一般處理爲打開剪貼板,並且清空剪貼板,然後把數據加載到內存中,
併爲每種格式調用 SetClipboardData ,然後再關閉剪貼板即可。
WM_DESTROYCLIPBOARD :
當在 程序 B 中調用 EmptyClipboard 時,Windows 將會給 程序 A 的窗口過程發送這個消息。
即通知 程序 A 其已不再是剪貼板的擁有者了。
Demo2 – MFCClipboard(延遲提交技術的使用)
整個項目結構很簡單:
主界面:
添加 3 個消息處理:
消息映射函數聲明:
protected:
HICON m_hIcon;
// 生成的消息映射函數
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnDestroyClipboard();
afx_msg void OnRenderAllFormats();
afx_msg void OnRenderFormat(UINT nFormat);
afx_msg void OnBnClickedBtnWrite();
afx_msg void OnBnClickedBtnRead();
CString m_CStrWrite;
CString m_CStrRead;
};
消息映射實現:
void CMFCClipboardDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT_WRITE, m_CStrWrite);
DDX_Text(pDX, IDC_EDIT_READ, m_CStrRead);
}
BEGIN_MESSAGE_MAP(CMFCClipboardDlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_DESTROYCLIPBOARD()
ON_WM_RENDERALLFORMATS()
ON_WM_RENDERFORMAT()
ON_BN_CLICKED(ID_BTN_WRITE, &CMFCClipboardDlg::OnBnClickedBtnWrite)
ON_BN_CLICKED(ID_BTN_READ, &CMFCClipboardDlg::OnBnClickedBtnRead)
END_MESSAGE_MAP()
消息映射函數實現
//WM_DESTROYCLIPBOARD 消息處理函數
void CMFCClipboardDlg::OnDestroyClipboard()
{
//當有另外的程序調用 EmptyClipboard 時,
//Windows 將向當前窗口過程發送 WM_DESTROYCLIPBOARD 消息
MessageBox(TEXT("很抱歉 , 您已失去對剪貼板的擁有權 ..."),
TEXT("提示"), MB_ICONINFORMATION);
CDialogEx::OnDestroyClipboard();
}
//WM_RENDERALLFORMATS 消息處理函數
void CMFCClipboardDlg::OnRenderAllFormats()
{
//當剪貼板中的數據句柄爲當前程序所擁有,而當前程序又將被退出時,
//Windows 給該程序窗口發送 WM_RENDERALLFORMATS 消息
OpenClipboard();
EmptyClipboard();
CloseClipboard();
CDialogEx::OnRenderAllFormats();
}
//WM_RENDERFORMAT 消息處理函數
void CMFCClipboardDlg::OnRenderFormat(UINT nFormat)
{
//當有另外的程序訪問剪貼板時
//Windows 給該程序窗口過程發送 WM_RENDERFORMAT 消息
int dataNum;
int dataIndex;
char * pDataBuf;
HGLOBAL hGlobalClip;
dataNum = this->m_CStrWrite.GetLength();
hGlobalClip = GlobalAlloc(GHND, dataNum + 1);
pDataBuf = (char *)GlobalLock(hGlobalClip);
for(dataIndex=0;dataIndex<dataNum;dataIndex++)
{
pDataBuf[dataIndex] = this->m_CStrWrite.GetAt(dataIndex);
}
GlobalUnlock(hGlobalClip);
//此時需要將有效數據寫入到剪貼板中
SetClipboardData(CF_TEXT, hGlobalClip);
CDialogEx::OnRenderFormat(nFormat);
}
void CMFCClipboardDlg::OnBnClickedBtnWrite()
{
UpdateData();
if(this->m_CStrWrite.GetLength() > 0)
{
if(OpenClipboard())
{
EmptyClipboard();
SetClipboardData(CF_TEXT, NULL);
CloseClipboard();
MessageBox(TEXT(" 恭喜您 , 設置剪貼板成功 ..."),
TEXT("提示"), MB_ICONINFORMATION);
}
}
}
void CMFCClipboardDlg::OnBnClickedBtnRead()
{
if(OpenClipboard())
{
//判斷剪貼板中的數據格式是否爲 CF_TEXT
if(IsClipboardFormatAvailable(CF_TEXT))
{
char * pDataBuf;
HGLOBAL hGlobalClip;
//從剪貼板中獲取到指定格式的數據
hGlobalClip = GetClipboardData(CF_TEXT);
pDataBuf = (char *)GlobalLock(hGlobalClip);
this->m_CStrRead = pDataBuf;
GlobalUnlock(hGlobalClip);
UpdateData(FALSE);
}
CloseClipboard();
}
}
效果展示:
設置剪貼板中數據:
當前程序讀取剪貼板中數據:
記事本程序讀取剪貼板中數據:
測試當前進程失去剪貼板所有權:
首先單擊當前程序設置好剪貼板中的數據,
然後打開一個記事本文件,在在其中輸入一些數據,然後選擇這部分數據,按下複製:
結束語
對於剪貼板的使用呢,也就是那麼幾個 API 在使用而已,熟悉一下就可以了,
關鍵是延遲提交技術的使用,同時還有對於全局內存對象的理解還是有點難度的,
不過,我相信我解釋的還是比較明白了,大家可以通過我的解釋再對照 Demo 來理解,
這樣理解起來容易快速一些。
上面介紹的是通過剪貼板來實現進程之間的通信,其實這還是有問題的,
因爲我們的剪貼板是位於本地機器上,所以,利用剪貼板還是無法實現本地進程與遠程進程通信,
當然要想實現本地進程和遠程進程的通信,那也還是有辦法的,這會在後續博文中引出的。
然後的話,今天聖誕節嘛,祝諸位節日快樂,也不是我崇洋媚外,說個節日快樂還是可以的。