淺析Windows編程的剪貼板機制

 作者:中國電波傳播研究所青島分所郎銳 時間:2004-08-03 出處:天極網

 摘要: 本文對Windows剪貼板機製作了深入、全面的闡述,具體內容包括:文本、位圖、DSP、自定義格式剪貼板的使用和多數據項和延遲提交技術。

  關鍵詞: VC++6.0; 剪貼板機制;數據格式;延遲提交

  Windows剪貼板

  Windows剪貼板是一種比較簡單同時也是開銷比較小的IPC(InterProcess Communication,進程間通訊)機制。Windows系統支持剪貼板IPC的基本機制是由系統預留的一塊全局共享內存,用來暫存在各進程間進行交換的數據:提供數據的進程創建一個全局內存塊,並將要傳送的數據移到或複製到該內存塊;接受數據的進程(也可以是提供數據的進程本身)獲取此內存塊的句柄,並完成對該內存塊數據的讀取。

  爲使剪貼板的這種IPC機制更加完善和便於使用,需要解決好如下三個問題:提供數據的進程在結束時 Windows系統將刪除其創建的全局內存塊,而接受數據的進程則希望在其退出後剪貼板中的數據仍然存在,可以繼續爲其他進程所獲取;能方便地管理和傳送剪貼板數據句柄;能方便設置和確定剪貼板數據格式。爲完善上述功能,Windows提供了存在於USER32.dll中的一組API函數、消息和預定義數據格式等,並通過對這些函數、消息的使用來管理在進程間進行的剪貼板數據交換。

  Windows系統爲剪貼板提供了一組API函數和多種消息,基本可以滿足編程的需要。而且Windows還爲剪貼板預定義了多種數據格式。通過這些預定義的格式,可以使接收方正確再現數據提供方放置於剪貼板中的數據內容。

  文本剪貼板和位圖剪貼板的使用

  這兩種剪貼板是比較常用的。其中,文本剪貼板是包含具有格式CF_TEXT的字符串的剪貼板,是最經常使用的剪貼板之一。在文本剪貼板中傳遞的數據是不帶任何格式信息的ASCII字符。若要將文本傳送到剪貼板,可以先分配一個可移動全局內存塊,然後將要複製的文本內容寫入到此內存區域。最後調用剪貼板函數將數據放置到剪貼板:

DWORD dwLength = 100; // 要複製的字串長度
HANDLE hGlobalMemory = GlobalAlloc(GHND, dwLength + 1); // 分配內存
LPBYTE lpGlobalMemory = (LPBYTE)GlobalLock(hGlobalMemory); // 鎖定內存
for (int i = 0; i < dwLength; i++) // 將"*"複製到全局內存塊
 *lpGlobalMemory++ = '*';
 GlobalUnlock(hGlobalMemory); // 鎖定內存塊解鎖
 HWND hWnd = GetSafeHwnd(); // 獲取安全窗口句柄
 ::OpenClipboard(hWnd); // 打開剪貼板
 ::EmptyClipboard(); // 清空剪貼板
 ::SetClipboardData(CF_TEXT, hGlobalMemory); // 將內存中的數據放置到剪貼板
 ::CloseClipboard(); // 關閉剪貼板

  這裏以OpenClipboard()打開剪貼板,並在調用了EmptyClipboard()後使hWnd指向的窗口成爲剪貼板的擁有者,一直持續到 CloseClipboard()函數的調用。在此期間,剪貼板爲擁有者所獨佔,其他進程將無法對剪貼板內容進行修改。

  從剪貼板獲取文本的過程與之類似,首先打開剪貼板並獲取剪貼板的數據句柄,如果數據存在就拷貝其數據到程序變量。由於GetClipboardData()獲取的數據句柄是屬於剪貼板的,因此用戶程序必須在調用CloseClipboard()函數之前使用它:

HWND hWnd = GetSafeHwnd(); // 獲取安全窗口句柄
::OpenClipboard(hWnd); // 打開剪貼板
HANDLE hClipMemory = ::GetClipboardData(CF_TEXT);// 獲取剪貼板數據句柄
DWORD dwLength = GlobalSize(hClipMemory); // 返回指定內存區域的當前大小
LPBYTE lpClipMemory = (LPBYTE)GlobalLock(hClipMemory); // 鎖定內存
m_sMessage = CString(lpClipMemory); // 保存得到的文本數據
GlobalUnlock(hClipMemory); // 內存解鎖
::CloseClipboard(); // 關閉剪貼板

  大多數應用程序對圖形數據採取的是位圖的剪貼板數據格式。位圖剪貼板的使用與文本剪貼板的使用是類似的,只是數據格式要指明爲CF_BITMAP,而且在使用SetClipboardData()或GetClipboardData()函數時交給剪貼板或從剪貼板返回的是設備相關位圖句柄。下面這段示例代碼將把存在於剪貼板中的位圖數據顯示到程序的客戶區:

HWND hWnd = GetSafeHwnd(); // 獲取安全窗口句柄
::OpenClipboard(hWnd); // 打開剪貼板
HANDLE hBitmap = ::GetClipboardData(CF_BITMAP); // 獲取剪貼板數據句柄
HDC hDC = ::GetDC(hWnd); // 獲取設備環境句柄
HDC hdcMem = CreateCompatibleDC(hDC); // 創建與設備相關的內存環境
SelectObject(hdcMem, hBitmap); // 選擇對象
SetMapMode(hdcMem, GetMapMode(hDC)); // 設置映射模式
BITMAP bm; // 得到位圖對象
GetObject(hBitmap, sizeof(BITMAP), &bm);
BitBlt(hDC, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); //位圖複製
::ReleaseDC(hWnd, hDC); // 釋放設備環境句柄
DeleteDC(hdcMem); // 刪除內存環境
::CloseClipboard(); // 關閉剪貼板
多數據項和延遲提交技術

  要把數據放入剪貼板,在打開剪貼板後一定要調用EmptyClipboard()函數清除當前剪貼板中的內容,而不可以在原有數據項基礎上追加新的數據項。但是,可以在 EmptyClipboard()和CloseClipboard()調用之間多次調用SetClipboardData()函數來放置多個不同格式的數據項。例如:

OpenClipboard(hWnd);
EmptyClipboardData();
SetClipboardData(CF_TEXT, hGMemText);
SetClipboardData(CF_BITMAP, hBitmap);
CloseClipboard();

  這時如果用CF_TEXT或CF_BITMAP等格式標記去調用IsClipboardFormatAvailable()都將返回TRUE,表明這幾種格式的數據同時存在於剪貼板中。以不同的格式標記去調用GetClipboardData()函數可以得到相應的數據句柄。

  對於多數據項的剪貼板數據,還可以用CountClipboardFormats()和EnumClipboardFormats()函數得到當前剪貼板中存在的數據格式數目和具體的數據格式。EnumClipboardFormats()的函數原型爲:

UINT EnumClipboardFormats(UINT format);

  參數format指定了剪貼板的數據格式。如果成功執行將返回format指定的格式的下一個數據格式值,如果format爲最後的數據格式值,那麼將返回0。由此不難寫出處理剪貼板中所有格式數據項的程序段代碼:

UINT format = 0; // 從第一種格式值開始枚舉
OpenClipboard(hWnd);
while(format = EnumClipboardFormats(format))
{
…… // 對相關格式數據的處理
}
CloseClipboard();

  在數據提供進程創建了剪貼板數據後,一直到有其他進程獲取剪貼板數據前,這些數據都要佔據內存空間。如在剪貼板放置的數據量過大,就會浪費內存空間,降低對資源的利用率。爲避免這種浪費,可以採取延遲提交(Delayed rendering)技術,即由數據提供進程先創建一個指定數據格式的空(NULL)剪貼板數據塊,直到有其他進程需要數據或自身進程要終止運行時才真正提交數據。

  延遲提交的實現並不複雜,只需剪貼板擁有者進程在調用SetClipboardData()將數據句柄參數設置爲NULL 即可。延遲提交的擁有者進程需要做的主要工作是對WM_RENDERFORMAT、WM_DESTORYCLIPBOARD和 WM_RENDERALLFORMATS等剪貼板延遲提交消息的處理。

  當另一個進程調用GetClipboardData()函數時,系統將會向延遲提交數據的剪貼板擁有者進程發送WM_RENDERFORMAT消息。剪貼板擁有者進程在此消息的響應函數中應使用相應的格式和實際的數據句柄來調用SetClipboardData()函數,但不必再調用OpenClipboard()和EmptyClipboard()去打開和清空剪貼板了。在設置完數據有也無須調用CloseClipboard()關閉剪貼板。如果其他進程打開了剪貼板並且調用EmptyClipboard()函數去清空剪貼板的內容,接管剪貼板的擁有權時,系統將向延遲提交的剪貼板擁有者進程發送WM_DESTROYCLIPBOARD消息,以通知該進程對剪貼板擁有權的喪失。而失去剪貼板擁有權的進程在收到該消息後則不會再向剪貼板提交數據。另外,在延遲提交進程在提交完所有要提交的數據後也會收到此消息。如果延遲提交剪貼板擁有者進程將要終止,系統將會爲其發送一條WM_RENDERALLFORMATS消息,通知其打開並清除剪貼板內容。在調用 SetClipboardData()設置各數據句柄後關閉剪貼板。

  下面這段代碼將完成對數據的延遲提交,WM_RENDERFORMAT消息響應函數OnRenderFormat()並不會立即執行,當有進程調用GetClipboardData()函數從剪貼板讀取數據時纔會發出該消息。在消息處理函數中完成對數據的提交:

  進行延遲提交:

HWND hWnd = GetSafeHwnd(); // 獲取安全窗口句柄
::OpenClipboard(hWnd); // 打開剪貼板
::EmptyClipboard(); // 清空剪貼板
::SetClipboardData(CF_TEXT, NULL); // 進行剪貼板數據的延遲提交
::CloseClipboard(); // 關閉剪貼板

  在WM_RENDERFORMAT消息的響應函數中:

DWORD dwLength = 100; // 要複製的字串長度
HANDLE hGlobalMemory = GlobalAlloc(GHND, dwLength + 1); // 分配內存塊
LPBYTE lpGlobalMemory = (LPBYTE)GlobalLock(hGlobalMemory); // 鎖定內存塊
for (int i = 0; i < dwLength; i++) // 將"*"複製到全局內存塊
*lpGlobalMemory++ = '*';
GlobalUnlock(hGlobalMemory); // 鎖定內存塊解鎖
::SetClipboardData(CF_TEXT, hGlobalMemory); // 將內存中的數據放置到剪貼板

 DSP和自定義數據格式的使用

  Windows系統預定義了三個帶“DSP”前綴的數據格式:CF_DSPTEXT、CF_DSPBITMAP和 CF_DSPMETAFILEPICT。這是一些僞標準格式,用於表示在程序中定義的私有剪貼板數據格式。對於不同的程序,這些格式的規定是不同的,因此這些格式只針對某一具體程序的不同實例纔有意義。

  爲使用DSP數據格式,必須確保進程本身與剪貼板擁有者進程同屬一個程序。可以調用GetClipboardOwner()函數來獲取剪貼板擁有者窗口句柄,並調用GetClassName()來獲取窗口類名:

HWND hClipOwner = GetClipboardOwner();
GetClassName(hClipOwner, &ClassName, 255);

  如果剪貼板擁有者窗口類名同本進程的窗口類名一致,就可以使用帶有DSP前綴的剪貼板數據格式了。
除了使用Windows預定義的剪貼板數據格式外,也可以在程序中使用自定義的數據格式。對於自定義的數據格式lpszFormat,可以調用RegisterClipboardFormat()函數來登記,並獲取其返回的格式標識值:

UINT format = RegisterClipboardFormat(lpszFormat);

  對此返回的格式標識值的使用與系統預定義的格式標識是一樣的。可以通過GetClipboardFormatName()函數來獲取自定義格式的ASCII名。

  小結

  本文主要對Windows編程中的剪貼板機製作了較爲深入的討論,對其中常用的文本、位圖、DSP和自定義數據格式的使用方法以及多數據項和延遲提交等重要技術一併做了闡述。並給出了具體的程序示例代碼,使讀者能夠更好的掌握剪貼板機制的使用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章