作者:中國電波傳播研究所青島分所郎銳 時間: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和自訂資料格式的使用方法以及多資料項目和延遲提交等重要技術一併做了闡述。並給出了具體的程式範例程式碼,使讀者能夠更好的掌握剪貼簿機制的使用。