當(dāng) DCOM 在若干年前登上歷史舞臺時,用于演示其功能的最常用示例之一就是遠(yuǎn)程剪貼板管理器。通過使用 DCOM 編程模型,一個組件能夠讀取和寫入存儲在另一臺計算機上、但連接到同一網(wǎng)絡(luò)的剪貼板內(nèi)容。(當(dāng)然,只有在安全設(shè)置允許的情況下才能生效。)
但是,當(dāng) DCOM 提供基礎(chǔ)結(jié)構(gòu)以構(gòu)建到系統(tǒng)組件(例如剪貼板)的遠(yuǎn)程訪問時,無論是 Windows 還是 DCOM 都無法提供能夠直接對剪貼板進(jìn)行遠(yuǎn)程訪問的 API。開發(fā)人員可以利用的技巧是以本地代理與遠(yuǎn)程存根之間的交互為基礎(chǔ)的。應(yīng)用程序調(diào)入本地代理,進(jìn)而在網(wǎng)絡(luò)傳輸層上序列化該調(diào)用,并將其傳送到遠(yuǎn)程主機。然后,該應(yīng)用程序宿主將剪貼板處理程序組件的一個本地副本實例化,以對 Windows 本地副本的剪貼板進(jìn)行讀取和寫入操作。
Microsoft.NET Framework 提供了 Clipboard 類來包裝系統(tǒng)剪貼板上的主要操作。該 Clipboard 類作為 Windows 窗體基礎(chǔ)結(jié)構(gòu)的一部分,在 System.Windows.Forms 命名空間中進(jìn)行聲明。該類上的方法允許您在單個應(yīng)用程序的上下文中獲得并設(shè)置剪貼板的當(dāng)前內(nèi)容。
我的一位客戶要構(gòu)建一個不使用剪貼板的 ASP.NET 電子商務(wù)站點。然而,該團隊中的一名開發(fā)人員意識到,負(fù)責(zé)填寫后端表格的人員需要持續(xù)不斷地在計算機間傳送大量數(shù)據(jù)(大部分為純文本)。他們找到的最快方法是創(chuàng)建能夠跨網(wǎng)絡(luò)共享的臨時文本文件。盡管可以接受這個特定技巧,但整個過程都不太智能。特別是,文本首先會在 Microsoft Word 或 Microsoft Internet Explorer 中突出顯示,然后被復(fù)制到剪貼板,接著再粘貼到一個新的 Notepad 文檔中。最后,該文檔被拖放到網(wǎng)絡(luò)文件夾中。在具備了良好的意志和新的 DCOM 存儲器后,有悟性的開發(fā)人員會想到,一個定制的遠(yuǎn)程剪貼板查看器和管理器可以更快、更有效地完成工作。

圖 1 剪貼板查看器
剪貼板查看器(請參見圖 1)是一個舊的 Windows 附件,它不再出現(xiàn)在開始菜單中,但是依然可以作為 clipbrd.exe 從 System32 文件夾中使用。剪貼板查看器充當(dāng)所有連接到網(wǎng)絡(luò)的計算機上的剪貼板的管理器。如果當(dāng)前的安全設(shè)置允許,您就可以連接到 Windows 的一個遠(yuǎn)程實例,并監(jiān)視計算機的剪貼板。雖然查看器只是一個查看器(它顯示當(dāng)前內(nèi)容,并可讓您刪除內(nèi)容),但是它不提供向剪貼板中輸入新文本的用戶界面。此外,剪貼板查看器基于分布式技術(shù)(十幾年前的舊技術(shù))— 網(wǎng)絡(luò)動態(tài)數(shù)據(jù)交換(或縮寫為 NetDDE)。所以,我的客戶決定編寫一個自定義版本的剪貼板查看器,以作為實際的分布式應(yīng)用程序。因為他使用的是 .NET Framework,所以他在計劃時就想到了使用 .NET Remoting 來設(shè)計實用工具。
安裝遠(yuǎn)程組件
.NET Remoting 是一種機制,能夠?qū)崿F(xiàn)在不同 AppDomains 中運行的組件之間的通訊。所有基于 .NET Framework 的應(yīng)用程序至少由一個(主)AppDomain 構(gòu)成,但是更多的 AppDomains 可以通過編程方式創(chuàng)建。AppDomain 代表一個托管子進(jìn)程,該子進(jìn)程存在于由操作系統(tǒng)和 CPU 管理的物理進(jìn)程上下文中。公共語言運行庫 (CLR) 可確保不能從一個 AppDomain 訪問包含在另一個 AppDomain 中的任何數(shù)據(jù)。無論兩個應(yīng)用程序域是位于同一個應(yīng)用程序中、同一計算機的兩個截然不同的應(yīng)用程序中還是運行于物理分隔的計算機上,分離機制都是完全相同的。
從體系結(jié)構(gòu)上講,.NET Remoting 的角色取決于系統(tǒng)將調(diào)用上下文從客戶端封送到服務(wù)器、然后將結(jié)果發(fā)送回客戶端的能力。遠(yuǎn)程組件是一個具有公共方法的類,它可以直接或間接地來自 MarshalByRefObject。該類被編譯為程序集,它部署在服務(wù)器計算機上,并通過宿主應(yīng)用程序與客戶端進(jìn)行交互。宿主應(yīng)用程序負(fù)責(zé)偵聽傳入調(diào)用的特定端口、將它接收到的參數(shù)轉(zhuǎn)換為對象的本地調(diào)用,并將返回值封送回調(diào)用方。圖 2 顯示了訪問網(wǎng)絡(luò)計算機剪貼板的遠(yuǎn)程組件的體系結(jié)構(gòu)。

圖 2 訪問剪貼板的遠(yuǎn)程組件
客戶端(假設(shè)運行在 Machine1 上)發(fā)出一個對服務(wù)器端對象(假設(shè)駐留在 Machine2 上)的遠(yuǎn)程調(diào)用。該調(diào)用由偵聽協(xié)議端口的應(yīng)用程序宿主接收,然后再進(jìn)行處理。該調(diào)用上下文包括要調(diào)用的遠(yuǎn)程方法的名稱及其參數(shù)。通過圖 3 中所示的代碼,可以實現(xiàn)封裝剪貼板函數(shù)的組件。marshal-by-ref 類將公開一個 Copy 方法,以使客戶端能夠?qū)⑽谋緦懭脒h(yuǎn)程剪貼板,以及一個 Paste 方法,以將遠(yuǎn)程剪貼板的內(nèi)容粘貼到本地上下文中。圖 3中的代碼和基本模式應(yīng)該比較易于理解,特別是,如果您閱讀了我在 2002 年 10 月刊上發(fā)表的 .NET Remoting 介紹文章(請參閱 .NET Remoting: Design and Develop Seamless Distributed Applications for the Common Language Runtime),則更是如此。截至目前,一切都還不錯。然而,您可能已經(jīng)注意到,Copy 和 Paste 方法的主體都是空的。起初,我認(rèn)為這部分代碼的內(nèi)容無足輕重。但是,我大錯特錯了。我解決問題的方式只是對 Win32? 和 Visual Studio? 6.0 進(jìn)行了一些改善。
.NET 剪貼板 API
正如先前提到的那樣,基于 .NET Framework 的應(yīng)用程序使用 System.Windows.Forms 命名空間中的 Clipboard 類來管理剪貼板。該類(不一定要實例化)包含一對靜態(tài)方法 — GetDataObject 和 SetDataObject。GetDataObject 檢索當(dāng)前存儲在系統(tǒng)剪貼板中的數(shù)據(jù);SetDataObject 將指定的數(shù)據(jù)對象置于系統(tǒng)內(nèi)存中。
剪貼板支持多種數(shù)據(jù)格式,包括用戶定義的格式。Win32 剪貼板 API 定義一組預(yù)先定義的格式,并用助記鍵常量(一個整數(shù)值,例如 CF_TEXT 或 CF_BITMAP)對其進(jìn)行標(biāo)識。
由于該剪貼板是系統(tǒng)組件,因此表示它的 .NET Framework 類只為一組低級別的 API 函數(shù)提供包裝。可能會使您感到驚訝的是,剪貼板的 .NET Framework 實現(xiàn)不是基于原始 Win32 函數(shù)和消息的。.NET Framework 中的 Clipboard 類通過 OLE 剪貼板協(xié)議執(zhí)行操作。您是說 OLE 是的,沒錯!本專欄打算回顧一些早期技術(shù),它們是 Windows 發(fā)展過程中的重要里程碑。
大約在 10 年前,Microsoft 引入了 OLE 作為一種全包含組件技術(shù)。引用 Kraig Brockschmidt 在 Inside OLE(Microsoft Press,1995 年)一書中的話,OLE 被定義為一個“基于對象服務(wù)的統(tǒng)一環(huán)境”。與使用 Win32 不同,您無法只使用 OLE(或者是它的繼任者 COM)來編寫一個完整的應(yīng)用程序。但是,OLE 采用了某些現(xiàn)有的系統(tǒng)功能,并以一種更通用、更廣泛的方式來公開它們。那么,什么是 OLE 剪貼板協(xié)議呢?
從本質(zhì)上說,OLE 剪貼板是一組接口,旨在泛化基于 Windows 的應(yīng)用程序與剪貼板之間發(fā)生的數(shù)據(jù)交換。Platform SDK 只定義幾個基本的數(shù)據(jù)類型(多數(shù)為文本和位圖)。OLE 剪貼板協(xié)議通過提供對任意數(shù)據(jù)格式和存儲介質(zhì)的支持來擴展模型。只要應(yīng)用程序所需的數(shù)據(jù)格式未超出 Platform SDK 提供的范圍,那么對于基于 Windows 的應(yīng)用程序來說,OLE 剪貼板協(xié)議就不是必需的。基于 OLE 的協(xié)議比 Win32 剪貼板 API 的功能更強大,并且在 .NET Framework 中構(gòu)建剪貼板支持時,OLE 是一個相當(dāng)合理的選擇。
在 OLE 和 .NET Framework 中執(zhí)行剪貼板操作的關(guān)鍵接口是 IDataObject。這兩個接口具有不同的方法集,但都扮演著同樣的角色。IDataObject 提供一種獨立于格式的機制以傳輸數(shù)據(jù),并且由 Clipboard 類在拖放操作中使用。(在 .NET Framework 中,OLE IDataObject 接口被重命名為 IOleDataObject。)圖 4 列出了在 IDataObject 接口上定義的方法。
以下代碼片段顯示了一個基于 .NET Framework 的應(yīng)用程序如何將純文本復(fù)制到剪貼板:
Clipboard.SetDataObject(text);
SetDataObject 方法提取一個對象并將它作為 IDataObject 實例復(fù)制到剪貼板中。如果該參數(shù)是一個已實現(xiàn) IDataObject 接口的對象,則直接進(jìn)行復(fù)制。否則,該方法將對象打包到一個動態(tài)創(chuàng)建的DataObject 類實例中,如圖 5 所示。
DataObject 類可實現(xiàn) IDataObject 和 IOleDataObject 接口。剪貼板的 SetDataObject 方法接受普通的對象參數(shù):
public static void SetDataObject(object); public static void SetDataObject(object, bool);
這段代碼允許您將簡單數(shù)據(jù)(例如,文本和位圖)作為原生對象傳入,而將數(shù)據(jù)改寫的重?fù)?dān)轉(zhuǎn)嫁給內(nèi)置的基礎(chǔ)結(jié)構(gòu)。SetDataObject 方法還具有一個要求額外 Boolean 參數(shù)的重載。該參數(shù)指示,在當(dāng)前應(yīng)用程序終止之后,置于剪貼板中的數(shù)據(jù)是否應(yīng)該保持可用。如果您使用一個參數(shù)的重載,則剪貼板的內(nèi)容會在退出時刷新。
與將數(shù)據(jù)寫入剪貼板相比,從剪貼板讀取數(shù)據(jù)更明了些,這是因為數(shù)據(jù)推斷不能委托給 .NET Framework。以下代碼片段顯示了托管應(yīng)用程序如何從剪貼板中進(jìn)行讀取:
IDataObject data; data = Clipboard.GetDataObject();
GetDataObject 方法返回一個 IDataObject 數(shù)據(jù)包,應(yīng)用程序必須將其解包:
// 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)采用一個參數(shù)來識別數(shù)據(jù)類型(例如,純文本)。如果該數(shù)據(jù)對象的內(nèi)容與指定的類型相匹配,則該方法會返回真。請注意,無論是存儲對象的原生類型相匹配,還是對象的類型能夠轉(zhuǎn)換為所需類型,該方法都會返回真。例如,如果數(shù)據(jù)對象包含 HTML 文本,但是用戶要求純文本,那么 GetDataPresent 會返回真。
從基于 .NET Framework 的應(yīng)用程序中使用剪貼板 API 不費吹灰之力,但是如果您試圖從遠(yuǎn)程對象來使用它,就不那么簡單了。
構(gòu)建遠(yuǎn)程剪貼板處理程序
Windows 窗體應(yīng)用程序通常以單線程單元 (STA) 模式運行。通過將 [STAThread] 屬性添加到應(yīng)用程序的 Main 例程,C# 項目將這一模式清晰地呈現(xiàn)在您面前。在 Visual Basic .NET 中,該設(shè)置是隱式的。對于許多 GUI 應(yīng)用程序而言,STA 模式絕對是必要的,因為這些應(yīng)用程序依賴于由并不始終支持純多線程環(huán)境的操作系統(tǒng)所公開的服務(wù)。這是典型的 OLE 和 COM 服務(wù),例如剪貼板和拖放操作。
事實上,您不能在來自 MTA 池的線程中使用剪貼板對象。請試驗下面的小型控制臺應(yīng)用程序:
using System.Windows.Forms; class Test { [MTAThread] static void Main() { Clipboard.SetDataObject("MSDNMag"); } }
這段代碼會拋出一個線程狀態(tài)異常,如圖 6 所示。

圖 6 線程狀態(tài)異常
能夠在基于 .NET Framework 的應(yīng)用程序中進(jìn)行 OLE 調(diào)用之前,程序員必須確保當(dāng)前線程以 STA 模式運行。實際上,托管對象負(fù)責(zé)以一種線程安全的方式來公開它們的共享數(shù)據(jù)。.NET Framework 支持線程單元只為獲得向后兼容性。相反,COM 組件使用線程單元。出于該原因,CLR 需要在與 COM 對象發(fā)生任何交互之前先創(chuàng)建一個線程單元。STAThread 和 MTAThread 屬性都是聲明性編程接口,用于為應(yīng)用程序選擇線程模型。在上面的代碼片段中,MTAThread 屬性設(shè)置了控制臺應(yīng)用程序,以創(chuàng)建一個托管線程并對其進(jìn)行配置以輸入一個 MTA 單元。只要應(yīng)用程序調(diào)入 OLE/COM 的內(nèi)容,就可以檢測到單元沖突并引發(fā)一個異常。通常,控制臺和 Windows 窗體應(yīng)用程序以 STA 模式運行(除非指定其他模式)。
當(dāng)我第一次構(gòu)建 marshal-by-ref 對象以將文本復(fù)制到遠(yuǎn)程剪貼板時,我保留了默認(rèn)設(shè)置。但是,只要執(zhí)行流到達(dá) Clipboard 類,就會引發(fā)線程異常。請記住,遠(yuǎn)程調(diào)用始終是通過 MTA 線程解決的。
在調(diào)入 Win32 OLE 方法之前,Clipboard 類會根據(jù)當(dāng)前線程的單元模式執(zhí)行預(yù)備檢查。這通過發(fā)出一個對 Application.OleRequired 方法的調(diào)用來完成。該方法檢驗 OLE 是否在當(dāng)前線程上進(jìn)行初始化,或者親自進(jìn)行初始化(如果需要)。該方法從列出三種可行值(TA、STA 和 Unknown)的 ApartmentState 枚舉中返回一個值。您可以通過下面的代碼檢查當(dāng)前的線程模型:
Console.WriteLine(Application.OleRequired().ToString());
調(diào)入遠(yuǎn)程對象的客戶端應(yīng)用程序是一個單線程的 Windows 窗體應(yīng)用程序。.NET Remoting 宿主是一個顯式標(biāo)記為 STA 的控制臺應(yīng)用程序。但是,用于遠(yuǎn)程調(diào)用的線程的單元狀態(tài)是 MTA。您在圖 6 中看到的錯誤消息其實是以前的結(jié)論。
對于如何解決 .NET Remoting 文檔和可用參考資料(包括出色的 http://www.dotnetremoting.cc)中的問題,我還沒有找到理想的解決方法。所以我采用了 Kraig Brockschmidt 在有關(guān) OLE 剪貼板的章節(jié)中給出的建議。Kraig 指出,只有當(dāng) OLE 剪貼板能夠為您帶來附加值時,才應(yīng)該使用它(如 .NET Framework 所做的那樣)。在只需要交換文本和位圖時,您可以堅持使用 Win32 剪貼板 API。作為回報,這樣做會減少線程之爭。圖 7 顯示了一個 Win32 DLL,它導(dǎo)出一對公共函數(shù)以將純文本復(fù)制并粘貼到剪貼板。
當(dāng)編碼 Win32 方式時,您必須首先打開并清空剪貼板。您需要一個窗口句柄來打開剪貼板,這是因為剪貼板只能屬于一個窗口對象。OpenClipboard 是要調(diào)用的 API 函數(shù)。EmptyClipboard 函數(shù)會釋放存儲在剪貼板中的全局?jǐn)?shù)據(jù)的所有句柄。之后,當(dāng)前讓剪貼板打開的窗口便成為新的所有者。要將數(shù)據(jù)復(fù)制到剪貼板,您必須分配一塊最好以 GHND 標(biāo)志標(biāo)記的共用內(nèi)存,該標(biāo)志表示其可移動,并可初始化為零。復(fù)制到剪貼板或從中讀取的任何數(shù)據(jù),都被打包到共用內(nèi)存的句柄中。在 Win32 級別上,可以打包到存儲介質(zhì)中的數(shù)據(jù)格式種類限制為幾種,例如 HTML、純文本或獨立于設(shè)備的位圖。如果您使用 OLE,則可以使用更多格式和存儲介質(zhì)。
雖然將 Win32 DLL 和 P/Invoke 平臺用于 Win32 交互操作會限制剪貼板只能使用幾種格式,但是不需要更改線程模型,并且也可以通過遠(yuǎn)程使用。圖 8 顯示了遠(yuǎn)程剪貼板處理程序?qū)ο蟮淖罱K代碼。兩個 DLL 公共函數(shù)映射到 marshal-by-ref 類的靜態(tài)外部成員。CopyToClipboard 的簽名不難轉(zhuǎn)換為 CLR 類型系統(tǒng)。利用字符串更改 LPCTSTR 并完成操作。導(dǎo)入 PasteFromClipboard 函數(shù)需要一點技巧。在圖 7中,通過引用聲明該函數(shù)接受一個字符串,并返回一個布爾值。
將類似簽名轉(zhuǎn)換為 .NET 代碼的有效方式涉及到 StringBuilder 對象的使用:
[DllImport("clip32.dll")] private static extern bool PasteFromClipboard(StringBuilder text);
您首先將對字符串的引用替換為初始化的 StringBuilder 對象。然后,使用 StringBuilder 類上的 ToString 方法來獲取該字符串:
StringBuilder buf = new StringBuilder(""); PasteFromClipboard(buf); return buf.ToString();
應(yīng)當(dāng)注意的是,StringBuilder 對象與 String 對象不同,前者是可增長的對象,而后者是傳統(tǒng)的字符串,它在本質(zhì)上不會改變。換言之,在您串聯(lián)兩個字符串時,.NET Framework 會創(chuàng)建一個新的字符串,其大小為二者之和。在您將字符串添加到 StringBuilder 對象時,只是將輸入文本追加到現(xiàn)有緩沖區(qū)而已。
遠(yuǎn)程剪貼板客戶端
遠(yuǎn)程剪貼板客戶端由兩個元素組成,即客戶端和遠(yuǎn)程組件的主服務(wù)器。要部署該客戶端,您必須將主服務(wù)器(請參見圖 9)和帶有遠(yuǎn)程組件的程序集一起復(fù)制到要到達(dá)的任何計算機上。您必須將客戶端應(yīng)用程序復(fù)制到您希望在網(wǎng)絡(luò)中從其進(jìn)行復(fù)制或粘貼的所有計算機上。

圖 9 遠(yuǎn)程剪貼板應(yīng)用程序
.NET Remoting 基礎(chǔ)結(jié)構(gòu)不會自動啟動服務(wù)器端主機,來接收對遠(yuǎn)程組件的傳入調(diào)用。用戶負(fù)責(zé)啟動并運行這種 stub 程序。您可以對該任務(wù)使用 Microsoft Internet 信息服務(wù) (IIS),或編寫自己的應(yīng)用程序。使用 IIS 會受到一些限制(需要 HTTP 通道),但是該方法可讓您不必管理遠(yuǎn)程處理宿主。或者,您可以編寫自定義應(yīng)用程序,該應(yīng)用程序只需注冊一條服務(wù)器通道(TCP、HTTP 或自定義類型),并通過服務(wù)器 URI 將遠(yuǎn)程類型與已知類型相關(guān)聯(lián)。
在圖 10 中,宿主是為端口 2222 創(chuàng)建 TCP 通道并將 MsdnMag.ClipboardHandler 類型(請參見圖 8)標(biāo)記為已知的控制臺應(yīng)用程序。用于調(diào)用對象的 URI 是 ClipboardHandler。您必須手動啟動和終止控制臺應(yīng)用程序。您也可以決定將其編寫為 GUI 應(yīng)用程序,并提供一個用戶界面來暫停或終止端口監(jiān)視。要暫停監(jiān)視,您可以取消注冊通道。如果您不希望編寫控制臺應(yīng)用程序,則可以選擇創(chuàng)建一個 Windows 服務(wù),該服務(wù)提供相同的功能,而不需要手動處理啟動/停止操作。
.NET Remoting 組件的客戶端必須完成一項基本任務(wù),即,使遠(yuǎn)程類型可由其余的應(yīng)用程序識別。該任務(wù)通過使用 RemotingConfiguration 類的其中一個靜態(tài)成員(RegisterWellKnownClientType 方法)來執(zhí)行。以下代碼片段顯示了如何將類型注冊為已知:
RemotingConfiguration.RegisterWellKnownClientType( typeof(MsdnMag.ClipboardHandler), "tcp://expo-two:2222/ClipboardHandler");
RegisterWellKnownClientType 方法使用兩個參數(shù)。第一個是待定的類型。第二個是一個 URI,它包括用于封送的傳輸協(xié)議、服務(wù)器名稱或 IP 地址、要使用的端口以及遠(yuǎn)程對象的昵稱,如之前的代碼所示。當(dāng)然,該端口必須與遠(yuǎn)程主機在其上進(jìn)行偵聽的端口相匹配。雖然已知類型的概念易于理解,但不能在本地定義遠(yuǎn)程類型,并且對任何方法或?qū)傩缘娜魏我枚急仨氁圆煌姆绞竭M(jìn)行處理。在每個調(diào)用前面,編譯器都必須生成一些普通的代碼,以將調(diào)用封送到遠(yuǎn)程服務(wù)器并返回。出于這個原因,必須識別并標(biāo)記源代碼中對遠(yuǎn)程對象的所有引用,以便實時 (JIT) 編譯器可以為其正確生成動態(tài)代碼。
RegisterWellKnownClientType 方法的內(nèi)部實現(xiàn)并不復(fù)雜。它可緩存類型信息,并將其添加到已知類型的全局哈希表。它將類型用作密鑰,而將 URI 字符串用作成對的值。已知類型的編程接口決不允許您取消注冊類型。如果您試圖將一個已知類型重定向到另一個 URI,則會引發(fā)一個異常。
正如您看到的那樣,RegisterWellKnownClientType(以及類似方法,如 RegisterActivatedClientType)通過在類型和服務(wù)器 URI 之間建立一對一的關(guān)系來運行。如果應(yīng)用程序需要從不同的服務(wù)器調(diào)用相同的類型,應(yīng)該怎么做呢?好,這就是在構(gòu)建遠(yuǎn)程剪貼板處理程序時要面對的下一個問題。
如圖 10所示,客戶端應(yīng)用程序必須多次注冊相同的遠(yuǎn)程類型,為每臺連接的服務(wù)器注冊一次。遠(yuǎn)程剪貼板客戶端必須能夠?qū)W(wǎng)絡(luò)上可用的所有計算機的剪貼板進(jìn)行讀取和寫入操作。這些計算機都會運行同一宿主和同一類型的實例,即圖 8中的 MsdnMag.ClipboardHandler 類。如何多次將同一類型注冊到不同的 URI 您有兩個選擇。第一個要求跳過內(nèi)置的配置機制。您不用將遠(yuǎn)程類型標(biāo)記為已知,而只需使用 Activator.GetObject 方法的一個重載來創(chuàng)建并獲取對象的遠(yuǎn)程實例:
string uri = @"tcp:\\expo-two:2222\ClipboardHandler"; object o = Activator.GetObject(typeof(MsdnMag.ClipboardHandler), uri); MsdnMag.ClipboardHandler clip; clip = (MsdnMag.ClipboardHandler) o;
GetObject 方法為指定類型和 URL 所指示的對象獲取或創(chuàng)建代理。該解決方案為您提供了極大的靈活性,因為它并不依賴于服務(wù)器的數(shù)量及其位置。
第二個選擇是,如果您明確知道客戶端始終與之協(xié)同工作的服務(wù)器,則可使用更為嚴(yán)格的方法,將傳統(tǒng)的已知類型方法擴展到多臺服務(wù)器的情況。其思想是通過派生幾乎完全相同的新類來重命名遠(yuǎn)程類型。在計算機名之后的命名空間中將新類設(shè)為根。(盡管這是任意的。)
圖 11 中的代碼顯示了如何使用繼承來重命名 MsdnMag.ClipboardHandler 類。如果重新編譯,則 ExpoTwo 和 ExpoStar 命名空間中的新類將不會添加額外的代碼,并且不需要系統(tǒng)開銷。要支持新服務(wù)器,您必須添加新的命名空間聲明。應(yīng)當(dāng)注意的是,該解決方案缺乏靈活性,因為它對服務(wù)器的名稱進(jìn)行硬編碼,并且任何更改(例如,將新服務(wù)器添加到列表)都要求重新編譯。以下代碼說明了來自客戶端的遠(yuǎn)程調(diào)用:
ExpoTwo.RemoteClipboardHandler rc; rc = new ExpoTwo.RemoteClipboardHandler(); rc.Copy(TextToCopy1.Text);
在調(diào)用已知類型上的方法時,.NET Remoting 基礎(chǔ)結(jié)構(gòu)會驗證宿主應(yīng)用程序是否已啟動并處于運行狀態(tài)。如果不是,則會引發(fā)一個套接字異常。引發(fā)的特定異常是 SocketException;該類屬于 System.Net.Sockets 命名空間。客戶端應(yīng)用程序的示例項目引用了包含遠(yuǎn)程對象的組件。
盡管其功能不會引發(fā)異常,但該方法或許不是實際情況中的最佳選擇。版本控制、類依賴項甚至大小都是確定備選方法的理由。例如,您可以通過為所有公共方法甚至接口定義帶有空實現(xiàn)的基類,來避免鏈接原始程序集。盡管在后一種情況中,您不能在客戶端上只使用 new operator 來獲取遠(yuǎn)程對象的實例。實際上,按照設(shè)計,您無法在接口上調(diào)用 new。您可以使用 Activator.GetObject 來檢索在指定 URI 處實現(xiàn)給定接口的對象的實例。有關(guān)該特定點的詳細(xì)信息,以及與 .NET Remoting 相關(guān)的工作的有價值資源,請參閱 http://www.ingorammer.com/RemotingFAQ。
小結(jié)
圖 12 中所示的客戶端應(yīng)用程序提供了在特定計算機上復(fù)制和粘貼純文本的按鈕。通過使用低級別 API 調(diào)用的 Win32 DLL 來完成系統(tǒng)剪貼板上的物理操作。對于跨網(wǎng)絡(luò)中的計算機共享剪貼板這一問題,http://www.codeproject.com/dotnet/clipsend.asp 上的文章提供了更為有用的方法。

圖 12 復(fù)制和粘貼到特定計算機
對于我的客戶端用途而言,Win32 基本格式就足夠了。希望將來沒有這些限制。我的解決方案將模擬 ASP.NET 會話的分布式體系結(jié)構(gòu),引入充當(dāng)宿主的 Windows 服務(wù),并添加在 STA 線程上以遠(yuǎn)程方式操縱剪貼板的 marshal-by-ref 類。最后,我會將一對重載添加到現(xiàn)有 Clipboard 類的方法中。
請將給 Dino 的問題和意見發(fā)送至 cutting@microsoft.com。
Dino Esposito 是一位講師兼顧問,現(xiàn)居住在意大利的羅馬。他著有 Building Web Solutions with ASP.NET and ADO.NET 和 Applied XML Programming for .NET,這兩本書均出版自 Microsoft Press。他的大部分時間都用于講授有關(guān) ASP.NET 的課程以及會議演講。Dino 最近為 Microsoft Press 出版了 Programming ASP.NET 一書。您可以通過 dinoe@wintellect.com 與 Dino 取得聯(lián)系。