在我成為 C/ 開發人員之後尤其是在 Microsoft 推出之前我經常指責采用 Visual Basic 進行編程的同事選擇使用那樣一種弱類型化的語言
有那麼一段時間進行靜態類型化和強類型化編程是獲得良好的體驗的明顯選擇但是事物總是要發展變化的當今的 開發人員社區(看起來幾乎所有前 C/C++ 開發人員都已經轉移到這裡)經常發現他們明確需要一個更加動態的編程模型上個月我介紹了 Microsoft 在 C# 和 Visual Studio 中提供的一些動態編程功能這個月我將深入探討一些相關方案首先要介紹 C# 最吸引人的原因之一可以在 NET Framework 中輕松實現 COM 對象編程
輕松訪問 COM 對象
如果一個對象的結構和行為不是由完全靜態定義的類型(編譯器全面了解該類型)描述的話該對象就是動態的不可否認動態一詞在這種情況下聽起來太寬泛了因此讓我們看一個簡單的示例在 Script 等腳本語言中以下代碼能夠成功運行
Set word = CreateObject(WordApplication)
CreateObject 函數假設它獲得的 string 參數是某個已注冊 COM 對象的 progID它創建該組件的一個實例並返回該實例的 IDispatch 自動化接口IDispatch 接口的細節在腳本語言的任何層級都絕對看不到重要的是您可以編寫如下代碼
Set word = CreateObject(WordApplication)
wordVisible = True
Set doc = wordDocumentsAdd()
Set selection = wordSelection
selectionTypeText Hello world
selectionTypeParagraph()
docSaveAs(fileName)
在這段代碼中
您首先創建對組件的引用
以便自動執行底層 Microsoft Word 應用程序的行為
接著
您顯示 Word 主窗口
添加一個新文檔
在其中輸入一些文字
然後將文檔保存到某個位置
這段代碼清晰易懂
而且更重要的是
能夠正常運行
但它能正常運行要歸功於 Script 提供的特殊功能
後期綁定
後期綁定意味著直到執行流程遇到給定對象之前
該對象的類型都是未知的
當執行流程需要執行給定對象時
運行時環境才會開始確保要調用的該對象成員確實存在
然後再進行調用
在代碼真正執行之前
不會對其進行任何提前檢查
您可能知道
像 這樣的腳本語言並沒有編譯器
但是
Visual Basic(包括 CLR 版本)多年來一直有一項類似的功能
我承認
我經常會羨慕我的 Visual Basic 同事能夠更輕松地使用 COM 對象
而需要進行互操作的應用程序(例如 Office)經常采用這種有價值的構造塊
事實上
在有些情況下
即使整個應用程序是用 編寫的
我的團隊也會用 Visual Basic 編寫一部分互操作代碼
這有點令人意外?多語言編程不是一種新的前沿技術嗎?
在 Visual Basic 中
CreateObject 函數的存在是為了解決頑固的兼容性問題
重點在於基於 的語言在設計時考慮的是前期綁定NET Framework 能夠處理 COM 互操作性方案
但是這種方案從來不能由編程語言通過關鍵字和工具來提供支持
這種情況直到 C#
才有所改觀
C#
(和 Visual Basic)擁有動態查詢功能
這表明後期綁定現在對於
NET Framework 開發人員來說已經切實可行了
借助動態查詢功能
您可以繞過靜態類型檢查
在代碼中直接訪問方法
屬性
索引生成器屬性和字段
而留待運行時進行解析
還通過識別成員聲明中的默認值來實現可選參數
這意味著
在調用擁有可選參數的成員時
可以省略可選參數
而且
既可以按名稱也可以按位置來傳遞參數
最後
C#
中改進的 COM 綁定功能意味著以前是靜態且強類型化的語言現在也支持腳本語言的一些常見功能
在您了解如何利用新的動態關鍵字
實現與 COM 對象的流暢操作之前
讓我們稍稍深入了解一下動態類型查詢的內部機制
動態語言運行時
當您在 Visual Studio
中將某個變量聲明為動態時
其默認配置中根本不會有 IntelliSense
有趣的是
如果您安裝一個類似 ReSharper
(/resharper) 的附加工具
就可以通過 IntelliSense 獲得一些有關動態對象的不完全信息
圖
顯示了帶有和不帶 ReSharper 的代碼編輯器
該工具僅僅列出該動態類型上看起來已經定義的成員
在最低限度下
動態對象是 System
Object 的實例
圖 在帶有和不帶 ReSharper 的情況下Visual Studio 中的動態對象的 IntelliSense
讓我們看看當編譯器遇到以下代碼時會發生什麼情況(這段代碼設計得極其簡單目的是簡化對實現細節的理解)
class Program
{
static void Main(string[] args)
{
dynamic x = ;
ConsoleWriteLine(x)
}
}
在第二行中編譯器不會嘗試解析符號 WriteLine也不會像傳統的靜態類型檢查器一樣發出警報或錯誤只要遇到 dynamic 關鍵字C# 的表現就會變得像是解釋性語言結果編譯器會生成一些臨時代碼用來解釋涉及動態變量或參數的表達式解釋器基於動態語言運行時 (DLR)是 機制中的一個全新組件若要使用更具體的術語編譯器必須使用 DLR 所支持的抽象語法來生成表達式樹並將其傳遞給 DLR 庫進行處理在 DLR 中由編譯器提供的表達式被封裝在動態更新的站點對象中站點對象負責實時將方法綁定到對象圖 顯示了真實代碼的充分簡化版本該真實代碼是由前述的簡單程序生成的
圖
中的代碼已經過編輯和簡化以方便閱讀
但它顯示了實際情況的要點
動態變量映射到 System
Object 實例
然後就會在 DLR 中為程序創建一個站點
該站點負責管理 WriteLine 方法及其參數與目標對象之間的綁定
該綁定維持在類型 Program 的上下文中
為了對動態變量調用方法 Console
WriteLine
您將調用該站點
並傳遞目標對象(本例中為 Console 類型)及其參數(本例中為動態變量)
該站點將在內部檢查目標對象是否真的擁有成員 WriteLine
並且該成員能夠接受類似於變量 x 中目前存儲的對象這樣的參數
如果有任何問題
運行時就會引發 RuntimeBinderException
圖
動態變量的真正實現
internal class Program
{
private static void Main(string[] args)
{
object x =
;
if (MainSiteContainer
site
== null)
{
MainSiteContainer
site
= CallSite<
Action<CallSite
Type
object》
Create(Binder
InvokeMember(
WriteLine
null
typeof(Program)
new CSharpArgumentInfo[] {
CSharpArgumentInfo
Create(…)
}))
}
MainSiteContainer
site
Target
Invoke(
site
typeof(Console)
x)
}
private static class MainSiteContainer
{
public static CallSite<Action<CallSite
Type
object》 site
;
}
}
使用 COM 對象
現在
新的
能夠在基於 的應用程序中簡單輕松地使用 COM 對象
讓我們看看如何在 C# 中創建一個 Word 文檔
並且對您在
NET
和
NET
中需要的代碼進行比較
示例應用程序將基於給定的模板創建一個新的 Word 文檔
填入一些內容
並將其保存到一個指定位置
模板包含一些書簽
用於容納一些常用信息
無論您面向的是
NET Framework
還是
NET Framework
通過編程來創建 Word 文檔的第一步都是添加 Microsoft Word 對象庫(請參見圖
)
圖
引用 Word 對象庫
在 Visual Studio
和
NET Framework
之前
若要完成此操作
您需要類似圖
所示的代碼
圖
在 C#
中創建新的 Word 文檔
public static class WordDocument
{
public const String TemplateName = @
Sample
dotx
;
public const String CurrentDateBookmark =
CurrentDate
;
public const String SignatureBookmark =
Signature
;
public static void Create(String file
DateTime now
String author)
{
// Must be an Object because it is passed as a ref
Object missingValue = Missing
Value;
// Run Word and make it visible for demo purposes
var wordApp = new Application { Visible = true };
// Create a new document
Object template = TemplateName;
var doc = wordApp
Documents
Add(ref template
ref missingValue
ref missingValue
ref missingValue)
doc
Activate()
// Fill up placeholders in the document
Object bookmark_CurrentDate = CurrentDateBookmark;
Object bookmark_Signature = SignatureBookmark;
doc
Bookmarks
get_Item(ref bookmark_CurrentDate)
Range
Select()
wordApp
Selection
TypeText(current
ToString())
doc
Bookmarks
get_Item(ref bookmark_Signature)
Range
Select()
wordApp
Selection
TypeText(author)
// Save the document
Object documentName = file;
doc
SaveAs(ref documentName
ref missingValue
ref missingValue
ref missingValue
ref missingValue
ref missingValue
ref missingValue
ref missingValue
ref missingValue
ref missingValue
ref missingValue
ref missingValue
ref missingValue
ref missingValue
ref missingValue
ref missingValue)
doc
Close(ref missingValue
ref missingValue
ref missingValue)
wordApp
Quit(ref missingValue
ref missingValue
ref missingValue)
}
}
為了與 COM 自動化接口交互
您經常需要 Variant 類型
當您在基於 的應用程序中與 COM 自動化對象交互時
您需要將 Variants 表示成普通對象
其直接後果是您不能使用字符串來指示 Word 文檔所用的模板文件的名稱
因為必須通過引用來傳遞 Variant 參數
您不得不求助於 Object
如下所示
Object template = TemplateName;
var doc = wordApp
Documents
Add(ref template
ref missingValue
ref missingValue
ref missingValue)
要考慮的第二個方面是 Visual Basic 和腳本語言遠不如
嚴格
例如
這些語言不會強制要求您指定 COM 對象聲明上某個方法的所有參數
Documents 集合的 Add 方法需要四個參數
而除非您的語言支持可選參數
否則就不能忽略這些參數
正如前文所述
C#
支持可選參數
這意味著
盡管直接用 C#
來重新編譯圖
中的代碼就能正常使用
您可能仍會重寫這段代碼
刪除所有用來傳遞缺少的值的 ref 參數
如下所示
Object template = TemplateName;
var doc = wordApp
Documents
Add(template)
借助 C#
中新的
省略 ref
支持
圖
中的代碼變得更加簡單
而且更重要的是
它變得更容易閱讀
且語法上與腳本代碼更像
圖
包含編輯過的版本
該版本能夠用 C#
進行正確編譯
並且與圖
中的代碼效果相同
圖
在 C#
中創建新的 Word 文檔
public static class WordDocument
{
public const String TemplateName = @
Sample
dotx
;
public const String CurrentDateBookmark =
CurrentDate
;
public const String SignatureBookmark =
Signature
;
public static void Create(string file
DateTime now
String author)
{
// Run Word and make it visible for demo purposes
dynamic wordApp = new Application { Visible = true };
// Create a new document
var doc = wordApp
Documents
Add(TemplateName)
templatedDocument
Activate()
// Fill the bookmarks in the document
doc
Bookmarks[CurrentDateBookmark]
Range
Select()
wordApp
Selection
TypeText(current
ToString())
doc
Bookmarks[SignatureBookmark]
Range
Select()
wordApp
Selection
TypeText(author)
// Save the document
doc
SaveAs(fileName)
// Clean up
templatedDocument
Close()
wordApp
Quit()
}
}
圖
中的代碼允許您使用普通 類型來調用 COM 對象
而且
可選參數使得它更加簡單
中引入的動態關鍵字和其他 COM 互操作功能不會使代碼的運行速度明顯加快
但能讓您像編寫腳本一樣編寫 C# 代碼
對於 COM 對象來說
這種成果可能與性能的提升一樣重要
無 PIA 部署
從
NET Framework 推出以來
您就可以將 COM 對象包裝到托管類中
然後從基於
NET 的應用程序中使用
為了實現此目的
您需要使用由 COM 對象的供應商提供的主互操作程序集 (PIA)
PIA 必不可少
必須與客戶端應用程序一起部署
但是
很多時候
PIA 都太大了
並且會包含整個 COM API
因此將它們打包到安裝程序中不是什麼令人愉快的經驗
Visual Studio
提供了無 PIA 選項
無 PIA
是指編譯器能夠嵌入您在當前程序集中從 PIA 獲取的必要定義
因此
只有真正需要的定義才會進入最終的程序集
而不需要將供應商的 PIA 整個打包到安裝程序中
圖
顯示了
屬性
框中的選項
該選項在 Visual Studio
中實現了無 PIA
圖 在 Visual Studio 中啟用無 PIA 選項
無 PIA 功能基於 C# 的一項稱為類型等效性的功能簡而言之類型等效性就是兩個截然不同的類型可在運行時被當作是等效的並且可以互換使用類型等效性的典型示例是不同程序集中定義的兩個同名的接口它們是不同的類型但只要存在相同的方法它們就可以互換使用
總之使用 COM 對象仍然代價不低但是 C# 中的 COM 互操作支持使您編寫的代碼簡單得多從基於 NET Framework 框架的應用程序處理 COM 對象可使您與傳統的應用程序和關鍵業務方案建立聯系如果不這樣做您的控制力就會大大降低COM 在 NET Frameworok 中是相當棘手的問題但動態功能使這個問題變得不那麼困難
From:http://tw.wingwit.com/Article/program/net/201311/11486.html