?? 17.1.1 數據發送.txt
字號:
17.1.1 數據發送
在把數據放置到剪貼板之前,首先需要打開剪貼板,這可以利用 CWnd類的 OpenClipBoard成員函數
實現,該函數的原型聲明如下所示。
BOOL OpenClipboard( );
OpenClipboard函數的返回值是 BOOL類型。如果打開剪貼板操作成功,則該函數返回非 0值:如果其
他程序,或者當前窗口己經打開了剪貼板,則該函數返回 0值。如果某個程序己經打開了剪貼板,
則其他應用程序將不能修改剪貼板,直到前者調用了 CloseClipboard函數。并且只有調用了
EmptyClipboard函數之后,打開剪貼板的當前窗口才擁有剪貼板。 EmptyClipboard函數將清空剪貼
板,并釋放剪貼板中數據的句柄,然后將剪貼板的所有權分配給當前打開剪貼板的窗口。因為剪貼
板是所有進程都可以訪問的,所以在我們編寫的這個 Clipboard進程使用剪貼板之前,可能己經有
其他進程把數據放置到剪貼板上了,那么在該進程打開剪貼板之后,需要調用 EmptyClipboard函數,
清空剪貼板,釋放剪貼板上數據的句柄,并將剪貼板的所有權分配給當前打開剪貼板的窗口,之后
就可以向剪貼板中放置數據了。向剪貼板中放置數據,可以通過調用 SetClipboardD ata函數實現。
這個函數是以指定的剪貼板格式向剪貼板上放置數據,該函數的原型聲明如下所示:
HANDLE SetClipboardData(UINT uFormat , HANDLE hMem );
需要注意的是,當前調用 SetClipboardData函數的窗口必須是剪貼板的擁有者,而且
在這之前,該程序必須已經調用了 OpenClipboard函數打開剪貼板。在隨后響應 WM—_RENDEFORMAT
和WM二.RENDERALLFORMATS消息時,當前剪貼板的擁有者在調用SetClipboardData函數之前就不必再
調用句enClipboard函數了。 SetClipboardData函數有兩個參數,其含義分別如下所述。
. uFolIuat
指定剪貼板格式,這個格式可以是已注冊的格式,或者是任一種標準的剪貼板格式(讀者可自行查看
MSDN中提供的幫助信息)。本程序中只是利用剪貼板作為進程間通信的一種方式,因此選擇標準的剪
貼板格式,并且因為將傳輸文本數據,所以選擇CF_TEXT格式,該格式表示文本格式,在這種格式下,
每行數據以 "0x0A0x0D"(回車換行)這一組合字符終止,并以空字符作為數據的結尾。
.hMem
具有指定格式的數據的句柄。該參數可以是NULL,指示調用窗口直到有對剪貼板數. 據的請求時,
才提供指定剪貼板格式的數據。如果窗口采用延遲提交技術,則該窗口必須處理WM_RENDERFORMAT
和WM_RENDERALLFORMATS消息。
當一個提供數據的進程創建了剪貼板數據之后,直到其他進程獲取剪貼板數據之前,
這些數據都要占據內存空間。如果在剪貼板上放置的數據過大,就會浪費內存空間,降低對資源的
利用率。為了避免這種浪費,就可以采取延遲提交技術,也就是由數據提供進程先提供一個指定格
式的空剪貼板數據塊,即把 SetClipboardD咽U函數的 hMem參數設置為 NULL。當需要獲取數據的進
程想要從剪貼板上得到數據時,操作系統會向數據提供進程發送WM_RENDEFORMAT消息,而數據提
供進程可以響應這個消息,并在此消息的響應函數中,再一次調用 SetClipboardData函數,將實際
的數據放到剪貼板上。當再次調用 SetClipboardData函數時,就不再需要調用OpenClipboard函數,
也不再需要調用 EmptyClipboard函數。
也就是說,為了提高資源利用率,避免浪費內存空間,可以采取延遲提交技術。第一次調用 SetClipbo
arData函數時,將其hMem參數設置為NULL,在剪貼板上以指定的剪貼板格式放置一個空剪貼板數據
塊。然后直到有其他進程需要數據或者自身進程需要終止運行時再次調用 SetClipboardData函數,
這時才真正提交數據。
應用程序在調用SetClipboardData函數之后,系統就擁有了hMem參數所標識的數據對象。該應用程
序可以讀取這個數據對象,但是在應用程序調用CloseClipboard函數之前,它不能釋放該對象的句
柄,或者鎖定這個句輛。如果hMem參數標識了一個內存對象,那么這個對象必須是利用GMEM_MOVEABLE
標志調用GlobalAlloc函數為其分配內存的。
GlobalAlloc函數是從堆上分配指定數目的字節, Win32內存管理沒有提供一個單獨的本地堆和全局
堆。也就是說,在 Win32平臺下,已經沒有本地堆和全局堆了,在以前的 Win16平臺下有本地堆和
全局堆。因為與其他內存管理函數相比,全局內存函數的運行速度要稍稍慢些,而且它們沒有提供
更多的特性,所以新的應用程序應該使用堆函數。然而全局函數仍然與動態數據交換,以及剪貼板
函數一起使用。本程序是利用剪貼板在進程間進行通信,因此還是需要使用GlobalAlloc這個函數。
該函數的原型聲明如下所示=
HGLOBAL GlobalAlloc( UINT uFlags , SIZE_T dwBytes);
GlobalAlloc函數有兩個參數,其中 dwBytes指定分配的字節數, uFlags是一個標記,用來指定分
配內存的方式,該參數可以取表17.1中列出的一個或多個值,但是應注意,這些值中的有些值是不
能一起使用的。如果 uFlags參數值是 0,則該標記就是默認的 GMEM FIXED。
表17.1 uFlags參數取值
值 說明
GHND GMEM_MOVEABLE和GMEM_ZERO的IT的組合
GMEM FIXED 分配一塊固定內存,返回值是一個指針
GMEM MOVEABLE 分配一塊可移動的內存,在 Win32平臺下,內存塊在物理內存中從來不被移動,但
可在一個默認堆中被移動。創建一個進程時,系統為應用程序分配一塊默認堆。返回值是一塊內存
對 象句柄,如果想將這個句柄轉換為一個指針,可以使用 GlobalLock函數。這個標志不能與 GMEM_F
反ED標志一起使用。
GME如LZEROINIT 初始化內存的內容為0
GPTR G!'.伍M_FDαD和G!'.伍M_ZEROINIT的組合
如表 17.1所示中提到的GlobalLock函數的作用是對全局內存對象加鎖,然后返回該對象內存塊第一
個字節的指針。該函數的原型聲明如下所示:
LPVOID GlobalLock( HGLOBAL hMern) ;
GlobalLock函數的參數是一個全局內存對象句柄 (HGLOBAL類型),返回值是一個指針。每個內存對
象的內部數據結構中都包含了一個初始值為O的鎖計數,對于可移動的內存對象來說, GlobalLock
函數將其鎖計數加1.而GlobalUnlock函數將該鎖計數減10對于一個進程來說,每一次調用
GlobalLock函數后,最后一定要記住調用 GlobalUnlock函數。被鎖定的內存對象的內存塊將保持鎖
定,直到它的鎖計數為 0,這時,該內存塊才能被移動,或者被廢棄。另外,已被加鎖的內存不能
被移動,或者被廢棄,除非調用了 GlobalReal1oc函數重新分配了該內存對象。
使用GMEM FIXED標志分配的內存對象其鎖計數總是0。對于這些對象, GlobalLock 函數返回的指針
值等于指定的句柄值。 GMEM_FIXED與GMEM_MOVEABLE這兩個標志的區別是:如果指定的是前者,那么
GlobalAlloc函數返回的句柄值就是分配的內存地址:如果指定的是后者,那么 GlobalAlloc函數返
回的不是實際內存的地址,而是指向該進程中句柄表條目的指針,在該條目中包含有實際分配的內
存指針。
很多函數都使用HGLOBAL類型作為返回值或參數來代替內存地址,如果這樣的一個函數返回了一個
HGLOBAL類型的值,那么我們就應該假定它的內存是采用 GMEM MOVEABLE標志來分配的,這也就意味
著必須調用 GlobalLock函數對該全局內存對象加鎖,并且返回該內存的地址。如果一個函數采用
HGLOBAL類型的參數,為了保證安全,我們就應該用GMEM MOVEABLE標志調用GlobalAl1oc函數來生成
這個參數值。在這個剪貼板程序中,需要采用GMEM_MOVEABLE標志來分配內存。
下面就編寫向剪貼板發送數據的代碼。雙擊 Clipboard程序中的主界面對話框資源上
的【發送】按鈕,VC++開發環境將為我們自動創建該按鈕的單擊命令響應函數:OnBtnSend, 然后在
此函數中添加代碼以實現向剪貼板發送數據的功能,結果如例17-1所示。例17-1
void CClipboardDlg::OnBtnSend{)
// TODO: Add your control notification handler code here
if(OpenClipboard () ) / /打開剪貼板
{
CString str; //保存發送編輯框控件上的數據
HANDLE hClip; //保存調用 GlobalAlloc函數后分配的內存對象的句柄
char *pBuf; //保存調用 GlobalLock函數后返回的內存地址
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 ( ) ; / /關閉剪貼板
在上述例17-1所示OnBtnSend函數中,首先調用OpenClipboard打開剪貼板,如果成功打開,則調用
EmptyClipboard函數清空剪貼板,釋放剪貼板上數據的句柄,并將剪貼板的所有權分配給當前窗口。
接著,調用GetDlgItemText函數獲得發送編輯框中的數據,并保存到str變量中。
這時,就可以采用GMEM_MOVEABLE標志調用GlobalAlloc函數來分配內存對象了。該函數的第二個參
數用來指定分配的字節數,可以利用 CString類提供的 GetLength成員方法得到將要發送的數據的
長度。因為如果設定的是文本數據,那么在剪貼板中,該數據是以空字符作為結尾的。這樣的話,
如果在分配時按照數據實際大小分配內存空間,那么當把該數據放置到剪貼板上以后,剪貼板會在
該數據的最后一個字節中放置一個空字符,這樣就會丟失一個數據,因此這里在分配內存時要多分
配一個字節。
接下來,需要把GlobalAlloc函數返回的句柄轉換為指針,這可以通過調用GlobalLock函數,對內存
對象加鎖,并返回它的內存地址。因為GlobalLock函數返回的類型是LPVOID,而這里需要的是char*
類型,所以需要進行強制轉換。
之后,可以調用strcpy函數將由對象中的數據復制到pBuf指向的內存中,然后可以調用GlobalUnlock
函數對該內存塊解鎖。解鎖完成之后,就可以調用SetClipboardData函數以指定的剪貼板格式向剪
貼板上放置數據了,該函數第一個參數指定使用文本格式 CF_TEXT),第二個參數就是包含了將要
放置的數據的內存的句柄 ChClip)。
最后,讀者一定要記住,在把數據放置到剪貼板之后,一定要記得調用。CloseClipboard
,否則其他進程將無法打開剪貼板。
Build并運行Clipboard程序,在左邊發送編輯框中任意輸入一些數據,例如:"Hello",
之后單擊【發送】按鈕。然后,打開記事本程序,選擇【編輯\粘貼】菜單命令,即可以看到記事本
程序接收到了 "Hello"這串字符。結果如圖 17.2所示。這就說明我們編寫的 Clipboard程序與系統
提供的記事本程序之間通過剪貼板完成了數據的傳輸。
圖 17.2 Clipboard程序與記事本程序之間實現的數據傳輸
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -