亚洲成精品动漫久久精久,九九在线精品视频播放,黄色成人免费观看,三级成人影院,久碰久,四虎成人欧美精品在永久在线

掃一掃
關注微信公眾號

管理遠程Windows 剪貼板
2006-09-06   ccidnet

當 DCOM 在若干年前登上歷史舞臺時,用于演示其功能的最常用示例之一就是遠程剪貼板管理器。通過使用 DCOM 編程模型,一個組件能夠讀取和寫入存儲在另一臺計算機上、但連接到同一網絡的剪貼板內容。(當然,只有在安全設置允許的情況下才能生效。)

但是,當 DCOM 提供基礎結構以構建到系統組件(例如剪貼板)的遠程訪問時,無論是 Windows 還是 DCOM 都無法提供能夠直接對剪貼板進行遠程訪問的 API。開發人員可以利用的技巧是以本地代理與遠程存根之間的交互為基礎的。應用程序調入本地代理,進而在網絡傳輸層上序列化該調用,并將其傳送到遠程主機。然后,該應用程序宿主將剪貼板處理程序組件的一個本地副本實例化,以對 Windows 本地副本的剪貼板進行讀取和寫入操作。

Microsoft.NET Framework 提供了 Clipboard 類來包裝系統剪貼板上的主要操作。該 Clipboard 類作為 Windows 窗體基礎結構的一部分,在 System.Windows.Forms 命名空間中進行聲明。該類上的方法允許您在單個應用程序的上下文中獲得并設置剪貼板的當前內容。

我的一位客戶要構建一個不使用剪貼板的 ASP.NET 電子商務站點。然而,該團隊中的一名開發人員意識到,負責填寫后端表格的人員需要持續不斷地在計算機間傳送大量數據(大部分為純文本)。他們找到的最快方法是創建能夠跨網絡共享的臨時文本文件。盡管可以接受這個特定技巧,但整個過程都不太智能。特別是,文本首先會在 Microsoft Word 或 Microsoft Internet Explorer 中突出顯示,然后被復制到剪貼板,接著再粘貼到一個新的 Notepad 文檔中。最后,該文檔被拖放到網絡文件夾中。在具備了良好的意志和新的 DCOM 存儲器后,有悟性的開發人員會想到,一個定制的遠程剪貼板查看器和管理器可以更快、更有效地完成工作。


圖 1 剪貼板查看器


剪貼板查看器(請參見圖 1)是一個舊的 Windows 附件,它不再出現在開始菜單中,但是依然可以作為 clipbrd.exe 從 System32 文件夾中使用。剪貼板查看器充當所有連接到網絡的計算機上的剪貼板的管理器。如果當前的安全設置允許,您就可以連接到 Windows 的一個遠程實例,并監視計算機的剪貼板。雖然查看器只是一個查看器(它顯示當前內容,并可讓您刪除內容),但是它不提供向剪貼板中輸入新文本的用戶界面。此外,剪貼板查看器基于分布式技術(十幾年前的舊技術)— 網絡動態數據交換(或縮寫為 NetDDE)。所以,我的客戶決定編寫一個自定義版本的剪貼板查看器,以作為實際的分布式應用程序。因為他使用的是 .NET Framework,所以他在計劃時就想到了使用 .NET Remoting 來設計實用工具。

安裝遠程組件


.NET Remoting 是一種機制,能夠實現在不同 AppDomains 中運行的組件之間的通訊。所有基于 .NET Framework 的應用程序至少由一個(主)AppDomain 構成,但是更多的 AppDomains 可以通過編程方式創建。AppDomain 代表一個托管子進程,該子進程存在于由操作系統和 CPU 管理的物理進程上下文中。公共語言運行庫 (CLR) 可確保不能從一個 AppDomain 訪問包含在另一個 AppDomain 中的任何數據。無論兩個應用程序域是位于同一個應用程序中、同一計算機的兩個截然不同的應用程序中還是運行于物理分隔的計算機上,分離機制都是完全相同的。

從體系結構上講,.NET Remoting 的角色取決于系統將調用上下文從客戶端封送到服務器、然后將結果發送回客戶端的能力。遠程組件是一個具有公共方法的類,它可以直接或間接地來自 MarshalByRefObject。該類被編譯為程序集,它部署在服務器計算機上,并通過宿主應用程序與客戶端進行交互。宿主應用程序負責偵聽傳入調用的特定端口、將它接收到的參數轉換為對象的本地調用,并將返回值封送回調用方。圖 2 顯示了訪問網絡計算機剪貼板的遠程組件的體系結構。

MIissues0309CuttingEdgefig02

圖 2 訪問剪貼板的遠程組件


客戶端(假設運行在 Machine1 上)發出一個對服務器端對象(假設駐留在 Machine2 上)的遠程調用。該調用由偵聽協議端口的應用程序宿主接收,然后再進行處理。該調用上下文包括要調用的遠程方法的名稱及其參數。通過圖 3 中所示的代碼,可以實現封裝剪貼板函數的組件。marshal-by-ref 類將公開一個 Copy 方法,以使客戶端能夠將文本寫入遠程剪貼板,以及一個 Paste 方法,以將遠程剪貼板的內容粘貼到本地上下文中。圖 3中的代碼和基本模式應該比較易于理解,特別是,如果您閱讀了我在 2002 年 10 月刊上發表的 .NET Remoting 介紹文章(請參閱 .NET Remoting: Design and Develop Seamless Distributed Applications for the Common Language Runtime),則更是如此。截至目前,一切都還不錯。然而,您可能已經注意到,Copy 和 Paste 方法的主體都是空的。起初,我認為這部分代碼的內容無足輕重。但是,我大錯特錯了。我解決問題的方式只是對 Win32? 和 Visual Studio? 6.0 進行了一些改善。

返回頁首返回頁首

.NET 剪貼板 API


正如先前提到的那樣,基于 .NET Framework 的應用程序使用 System.Windows.Forms 命名空間中的 Clipboard 類來管理剪貼板。該類(不一定要實例化)包含一對靜態方法 — GetDataObject 和 SetDataObject。GetDataObject 檢索當前存儲在系統剪貼板中的數據;SetDataObject 將指定的數據對象置于系統內存中。

剪貼板支持多種數據格式,包括用戶定義的格式。Win32 剪貼板 API 定義一組預先定義的格式,并用助記鍵常量(一個整數值,例如 CF_TEXT 或 CF_BITMAP)對其進行標識。

由于該剪貼板是系統組件,因此表示它的 .NET Framework 類只為一組低級別的 API 函數提供包裝。可能會使您感到驚訝的是,剪貼板的 .NET Framework 實現不是基于原始 Win32 函數和消息的。.NET Framework 中的 Clipboard 類通過 OLE 剪貼板協議執行操作。您是說 OLE 是的,沒錯!本專欄打算回顧一些早期技術,它們是 Windows 發展過程中的重要里程碑。

大約在 10 年前,Microsoft 引入了 OLE 作為一種全包含組件技術。引用 Kraig Brockschmidt 在 Inside OLE(Microsoft Press,1995 年)一書中的話,OLE 被定義為一個“基于對象服務的統一環境”。與使用 Win32 不同,您無法只使用 OLE(或者是它的繼任者 COM)來編寫一個完整的應用程序。但是,OLE 采用了某些現有的系統功能,并以一種更通用、更廣泛的方式來公開它們。那么,什么是 OLE 剪貼板協議呢?

從本質上說,OLE 剪貼板是一組接口,旨在泛化基于 Windows 的應用程序與剪貼板之間發生的數據交換。Platform SDK 只定義幾個基本的數據類型(多數為文本和位圖)。OLE 剪貼板協議通過提供對任意數據格式和存儲介質的支持來擴展模型。只要應用程序所需的數據格式未超出 Platform SDK 提供的范圍,那么對于基于 Windows 的應用程序來說,OLE 剪貼板協議就不是必需的。基于 OLE 的協議比 Win32 剪貼板 API 的功能更強大,并且在 .NET Framework 中構建剪貼板支持時,OLE 是一個相當合理的選擇。

在 OLE 和 .NET Framework 中執行剪貼板操作的關鍵接口是 IDataObject。這兩個接口具有不同的方法集,但都扮演著同樣的角色。IDataObject 提供一種獨立于格式的機制以傳輸數據,并且由 Clipboard 類在拖放操作中使用。(在 .NET Framework 中,OLE IDataObject 接口被重命名為 IOleDataObject。)圖 4 列出了在 IDataObject 接口上定義的方法。

以下代碼片段顯示了一個基于 .NET Framework 的應用程序如何將純文本復制到剪貼板:

Clipboard.SetDataObject(text);

SetDataObject 方法提取一個對象并將它作為 IDataObject 實例復制到剪貼板中。如果該參數是一個已實現 IDataObject 接口的對象,則直接進行復制。否則,該方法將對象打包到一個動態創建的DataObject 類實例中,如圖 5 所示。

DataObject 類可實現 IDataObject 和 IOleDataObject 接口。剪貼板的 SetDataObject 方法接受普通的對象參數:

public static void SetDataObject(object); 
public static void SetDataObject(object, bool);

這段代碼允許您將簡單數據(例如,文本和位圖)作為原生對象傳入,而將數據改寫的重擔轉嫁給內置的基礎結構。SetDataObject 方法還具有一個要求額外 Boolean 參數的重載。該參數指示,在當前應用程序終止之后,置于剪貼板中的數據是否應該保持可用。如果您使用一個參數的重載,則剪貼板的內容會在退出時刷新。

與將數據寫入剪貼板相比,從剪貼板讀取數據更明了些,這是因為數據推斷不能委托給 .NET Framework。以下代碼片段顯示了托管應用程序如何從剪貼板中進行讀取:

IDataObject data;
data = Clipboard.GetDataObject();

GetDataObject 方法返回一個 IDataObject 數據包,應用程序必須將其解包:

// Verify that the data object contains plain text
if (data.GetDataPresent(DataFormats.Text)) 
{
   // Extrapolate and display the text
   string text = data.GetData(DataFormats.Text);
   MessageBox.Show(text); 
}

IDataObject 接口上的 GetDataPresent 方法(請參見圖 4)采用一個參數來識別數據類型(例如,純文本)。如果該數據對象的內容與指定的類型相匹配,則該方法會返回真。請注意,無論是存儲對象的原生類型相匹配,還是對象的類型能夠轉換為所需類型,該方法都會返回真。例如,如果數據對象包含 HTML 文本,但是用戶要求純文本,那么 GetDataPresent 會返回真。

從基于 .NET Framework 的應用程序中使用剪貼板 API 不費吹灰之力,但是如果您試圖從遠程對象來使用它,就不那么簡單了。

構建遠程剪貼板處理程序


Windows 窗體應用程序通常以單線程單元 (STA) 模式運行。通過將 [STAThread] 屬性添加到應用程序的 Main 例程,C# 項目將這一模式清晰地呈現在您面前。在 Visual Basic .NET 中,該設置是隱式的。對于許多 GUI 應用程序而言,STA 模式絕對是必要的,因為這些應用程序依賴于由并不始終支持純多線程環境的操作系統所公開的服務。這是典型的 OLE 和 COM 服務,例如剪貼板和拖放操作。

事實上,您不能在來自 MTA 池的線程中使用剪貼板對象。請試驗下面的小型控制臺應用程序:

using System.Windows.Forms;
class Test {
  [MTAThread]
  static void Main() {
    Clipboard.SetDataObject("MSDNMag");
  }
}

這段代碼會拋出一個線程狀態異常,如圖 6 所示。

MIissues0309CuttingEdgefig06

圖 6 線程狀態異常


能夠在基于 .NET Framework 的應用程序中進行 OLE 調用之前,程序員必須確保當前線程以 STA 模式運行。實際上,托管對象負責以一種線程安全的方式來公開它們的共享數據。.NET Framework 支持線程單元只為獲得向后兼容性。相反,COM 組件使用線程單元。出于該原因,CLR 需要在與 COM 對象發生任何交互之前先創建一個線程單元。STAThread 和 MTAThread 屬性都是聲明性編程接口,用于為應用程序選擇線程模型。在上面的代碼片段中,MTAThread 屬性設置了控制臺應用程序,以創建一個托管線程并對其進行配置以輸入一個 MTA 單元。只要應用程序調入 OLE/COM 的內容,就可以檢測到單元沖突并引發一個異常。通常,控制臺和 Windows 窗體應用程序以 STA 模式運行(除非指定其他模式)。

當我第一次構建 marshal-by-ref 對象以將文本復制到遠程剪貼板時,我保留了默認設置。但是,只要執行流到達 Clipboard 類,就會引發線程異常。請記住,遠程調用始終是通過 MTA 線程解決的。

在調入 Win32 OLE 方法之前,Clipboard 類會根據當前線程的單元模式執行預備檢查。這通過發出一個對 Application.OleRequired 方法的調用來完成。該方法檢驗 OLE 是否在當前線程上進行初始化,或者親自進行初始化(如果需要)。該方法從列出三種可行值(TA、STA 和 Unknown)的 ApartmentState 枚舉中返回一個值。您可以通過下面的代碼檢查當前的線程模型:

Console.WriteLine(Application.OleRequired().ToString());

調入遠程對象的客戶端應用程序是一個單線程的 Windows 窗體應用程序。.NET Remoting 宿主是一個顯式標記為 STA 的控制臺應用程序。但是,用于遠程調用的線程的單元狀態是 MTA。您在圖 6 中看到的錯誤消息其實是以前的結論。

對于如何解決 .NET Remoting 文檔和可用參考資料(包括出色的 http://www.dotnetremoting.cc)中的問題,我還沒有找到理想的解決方法。所以我采用了 Kraig Brockschmidt 在有關 OLE 剪貼板的章節中給出的建議。Kraig 指出,只有當 OLE 剪貼板能夠為您帶來附加值時,才應該使用它(如 .NET Framework 所做的那樣)。在只需要交換文本和位圖時,您可以堅持使用 Win32 剪貼板 API。作為回報,這樣做會減少線程之爭。圖 7 顯示了一個 Win32 DLL,它導出一對公共函數以將純文本復制并粘貼到剪貼板。

當編碼 Win32 方式時,您必須首先打開并清空剪貼板。您需要一個窗口句柄來打開剪貼板,這是因為剪貼板只能屬于一個窗口對象。OpenClipboard 是要調用的 API 函數。EmptyClipboard 函數會釋放存儲在剪貼板中的全局數據的所有句柄。之后,當前讓剪貼板打開的窗口便成為新的所有者。要將數據復制到剪貼板,您必須分配一塊最好以 GHND 標志標記的共用內存,該標志表示其可移動,并可初始化為零。復制到剪貼板或從中讀取的任何數據,都被打包到共用內存的句柄中。在 Win32 級別上,可以打包到存儲介質中的數據格式種類限制為幾種,例如 HTML、純文本或獨立于設備的位圖。如果您使用 OLE,則可以使用更多格式和存儲介質。

雖然將 Win32 DLL 和 P/Invoke 平臺用于 Win32 交互操作會限制剪貼板只能使用幾種格式,但是不需要更改線程模型,并且也可以通過遠程使用。圖 8 顯示了遠程剪貼板處理程序對象的最終代碼。兩個 DLL 公共函數映射到 marshal-by-ref 類的靜態外部成員。CopyToClipboard 的簽名不難轉換為 CLR 類型系統。利用字符串更改 LPCTSTR 并完成操作。導入 PasteFromClipboard 函數需要一點技巧。在圖 7中,通過引用聲明該函數接受一個字符串,并返回一個布爾值。

將類似簽名轉換為 .NET 代碼的有效方式涉及到 StringBuilder 對象的使用:

[DllImport("clip32.dll")]
private static extern bool PasteFromClipboard(StringBuilder text);

您首先將對字符串的引用替換為初始化的 StringBuilder 對象。然后,使用 StringBuilder 類上的 ToString 方法來獲取該字符串:

StringBuilder buf = new StringBuilder("");
PasteFromClipboard(buf);
return buf.ToString(); 

應當注意的是,StringBuilder 對象與 String 對象不同,前者是可增長的對象,而后者是傳統的字符串,它在本質上不會改變。換言之,在您串聯兩個字符串時,.NET Framework 會創建一個新的字符串,其大小為二者之和。在您將字符串添加到 StringBuilder 對象時,只是將輸入文本追加到現有緩沖區而已。

遠程剪貼板客戶端


遠程剪貼板客戶端由兩個元素組成,即客戶端和遠程組件的主服務器。要部署該客戶端,您必須將主服務器(請參見圖 9)和帶有遠程組件的程序集一起復制到要到達的任何計算機上。您必須將客戶端應用程序復制到您希望在網絡中從其進行復制或粘貼的所有計算機上。

MIissues0309CuttingEdgefig09

圖 9 遠程剪貼板應用程序


.NET Remoting 基礎結構不會自動啟動服務器端主機,來接收對遠程組件的傳入調用。用戶負責啟動并運行這種 stub 程序。您可以對該任務使用 Microsoft Internet 信息服務 (IIS),或編寫自己的應用程序。使用 IIS 會受到一些限制(需要 HTTP 通道),但是該方法可讓您不必管理遠程處理宿主。或者,您可以編寫自定義應用程序,該應用程序只需注冊一條服務器通道(TCP、HTTP 或自定義類型),并通過服務器 URI 將遠程類型與已知類型相關聯。

圖 10 中,宿主是為端口 2222 創建 TCP 通道并將 MsdnMag.ClipboardHandler 類型(請參見圖 8)標記為已知的控制臺應用程序。用于調用對象的 URI 是 ClipboardHandler。您必須手動啟動和終止控制臺應用程序。您也可以決定將其編寫為 GUI 應用程序,并提供一個用戶界面來暫停或終止端口監視。要暫停監視,您可以取消注冊通道。如果您不希望編寫控制臺應用程序,則可以選擇創建一個 Windows 服務,該服務提供相同的功能,而不需要手動處理啟動/停止操作。

.NET Remoting 組件的客戶端必須完成一項基本任務,即,使遠程類型可由其余的應用程序識別。該任務通過使用 RemotingConfiguration 類的其中一個靜態成員(RegisterWellKnownClientType 方法)來執行。以下代碼片段顯示了如何將類型注冊為已知:

RemotingConfiguration.RegisterWellKnownClientType(     
  typeof(MsdnMag.ClipboardHandler),
  "tcp://expo-two:2222/ClipboardHandler");

RegisterWellKnownClientType 方法使用兩個參數。第一個是待定的類型。第二個是一個 URI,它包括用于封送的傳輸協議、服務器名稱或 IP 地址、要使用的端口以及遠程對象的昵稱,如之前的代碼所示。當然,該端口必須與遠程主機在其上進行偵聽的端口相匹配。雖然已知類型的概念易于理解,但不能在本地定義遠程類型,并且對任何方法或屬性的任何引用都必須以不同的方式進行處理。在每個調用前面,編譯器都必須生成一些普通的代碼,以將調用封送到遠程服務器并返回。出于這個原因,必須識別并標記源代碼中對遠程對象的所有引用,以便實時 (JIT) 編譯器可以為其正確生成動態代碼。

RegisterWellKnownClientType 方法的內部實現并不復雜。它可緩存類型信息,并將其添加到已知類型的全局哈希表。它將類型用作密鑰,而將 URI 字符串用作成對的值。已知類型的編程接口決不允許您取消注冊類型。如果您試圖將一個已知類型重定向到另一個 URI,則會引發一個異常。

正如您看到的那樣,RegisterWellKnownClientType(以及類似方法,如 RegisterActivatedClientType)通過在類型和服務器 URI 之間建立一對一的關系來運行。如果應用程序需要從不同的服務器調用相同的類型,應該怎么做呢?好,這就是在構建遠程剪貼板處理程序時要面對的下一個問題。

圖 10所示,客戶端應用程序必須多次注冊相同的遠程類型,為每臺連接的服務器注冊一次。遠程剪貼板客戶端必須能夠對網絡上可用的所有計算機的剪貼板進行讀取和寫入操作。這些計算機都會運行同一宿主和同一類型的實例,即圖 8中的 MsdnMag.ClipboardHandler 類。如何多次將同一類型注冊到不同的 URI 您有兩個選擇。第一個要求跳過內置的配置機制。您不用將遠程類型標記為已知,而只需使用 Activator.GetObject 方法的一個重載來創建并獲取對象的遠程實例:

string uri = @"tcp:\\expo-two:2222\ClipboardHandler";
object o = Activator.GetObject(typeof(MsdnMag.ClipboardHandler), uri);
MsdnMag.ClipboardHandler clip;
clip = (MsdnMag.ClipboardHandler) o;

GetObject 方法為指定類型和 URL 所指示的對象獲取或創建代理。該解決方案為您提供了極大的靈活性,因為它并不依賴于服務器的數量及其位置。

第二個選擇是,如果您明確知道客戶端始終與之協同工作的服務器,則可使用更為嚴格的方法,將傳統的已知類型方法擴展到多臺服務器的情況。其思想是通過派生幾乎完全相同的新類來重命名遠程類型。在計算機名之后的命名空間中將新類設為根。(盡管這是任意的。)

圖 11 中的代碼顯示了如何使用繼承來重命名 MsdnMag.ClipboardHandler 類。如果重新編譯,則 ExpoTwo 和 ExpoStar 命名空間中的新類將不會添加額外的代碼,并且不需要系統開銷。要支持新服務器,您必須添加新的命名空間聲明。應當注意的是,該解決方案缺乏靈活性,因為它對服務器的名稱進行硬編碼,并且任何更改(例如,將新服務器添加到列表)都要求重新編譯。以下代碼說明了來自客戶端的遠程調用:

ExpoTwo.RemoteClipboardHandler rc;
rc = new   
  ExpoTwo.RemoteClipboardHandler();
rc.Copy(TextToCopy1.Text);

在調用已知類型上的方法時,.NET Remoting 基礎結構會驗證宿主應用程序是否已啟動并處于運行狀態。如果不是,則會引發一個套接字異常。引發的特定異常是 SocketException;該類屬于 System.Net.Sockets 命名空間。客戶端應用程序的示例項目引用了包含遠程對象的組件。

盡管其功能不會引發異常,但該方法或許不是實際情況中的最佳選擇。版本控制、類依賴項甚至大小都是確定備選方法的理由。例如,您可以通過為所有公共方法甚至接口定義帶有空實現的基類,來避免鏈接原始程序集。盡管在后一種情況中,您不能在客戶端上只使用 new operator 來獲取遠程對象的實例。實際上,按照設計,您無法在接口上調用 new。您可以使用 Activator.GetObject 來檢索在指定 URI 處實現給定接口的對象的實例。有關該特定點的詳細信息,以及與 .NET Remoting 相關的工作的有價值資源,請參閱 http://www.ingorammer.com/RemotingFAQ。

小結


圖 12 中所示的客戶端應用程序提供了在特定計算機上復制和粘貼純文本的按鈕。通過使用低級別 API 調用的 Win32 DLL 來完成系統剪貼板上的物理操作。對于跨網絡中的計算機共享剪貼板這一問題,http://www.codeproject.com/dotnet/clipsend.asp 上的文章提供了更為有用的方法。

MIissues0309CuttingEdgefig12

圖 12 復制和粘貼到特定計算機


對于我的客戶端用途而言,Win32 基本格式就足夠了。希望將來沒有這些限制。我的解決方案將模擬 ASP.NET 會話的分布式體系結構,引入充當宿主的 Windows 服務,并添加在 STA 線程上以遠程方式操縱剪貼板的 marshal-by-ref 類。最后,我會將一對重載添加到現有 Clipboard 類的方法中。


請將給 Dino 的問題和意見發送至 cutting@microsoft.com

Dino Esposito 是一位講師兼顧問,現居住在意大利的羅馬。他著有 Building Web Solutions with ASP.NET and ADO.NETApplied XML Programming for .NET,這兩本書均出版自 Microsoft Press。他的大部分時間都用于講授有關 ASP.NET 的課程以及會議演講。Dino 最近為 Microsoft Press 出版了 Programming ASP.NET 一書。您可以通過 dinoe@wintellect.com 與 Dino 取得聯系。

熱詞搜索:

上一篇:手工清除流氓軟件方法詳解
下一篇:必不可少:安裝Win XP后的五個設置步驟

分享到: 收藏