■■■■前言
目前的基于.NET平臺的軟件研發中仍然存在大量的對COM及ActiveX控件的調用。使用C#調用ActiveX控件時一般是使用vs.net工具自動生成的互操作性程序集。這種方法操作簡單,能保證一定的性能。但會產生額外的程序文件,不利于應用軟件的簡潔部署,還容易產生和ActiveX控件版本相關的錯誤。本文就提出使用反射技術動態調用ActiveX控件的方式來解決這些問題。
■■■■問題描述:
目前的基于.NET平臺的軟件研發中仍然存在大量的對COM及ActiveX控件的調用。使用C#調用ActiveX控件時一般是使用vs.net工具的添加COM引用時自動生成的互操作性程序集。這種方法操作簡單,能保證一定的性能。但會產生額外的程序文件,不利于應用軟件的簡潔部署。而且當開發環境和運行環境使用的ActiveX控件的版本不一致[袁永福原創]時還容易出錯。
筆者長期從事基于.NET平臺的通用產品類軟件研發。[袁永福原創]產品類軟件要求部署簡潔,為此筆者都會將多個工程編譯生成的多個.NET程序集文件合并成一個.NET程序集文件來做到簡潔部署。
實踐中發現自動生成的互操作性程序集無法合并。另外產品類軟件應該能適應各種復雜的開發和生產環境,甚至ActiveX控件的CLSID 都有可能變化。
例如,對于COM類庫“HebcaFormSealLib”,VS.NET會自動生成程序集文件“AxInterop.HebcaFormSealLib.dll”、“Interop.HebcaFormSealLib.dll”。這些程序集文件無法進行程序集合并,而且對于32位或64位的工程項目類型敏感,容易導致錯誤。
■■■■技術改進:
因此筆者不采用這種自動生成的互操作性程序集。轉而采用自定義的反射來調用ActiveX控件。
后期綁定ActiveX 控件主要知識點為System.Windows.Forms.AxHost類型和Type.InvokeMember方法。
AxHost類型是從System.Windows.Forms.Control類型[袁永福原創]派生出來的,專門用于承載ActiveX控件。它是一個抽象類,有一個受保護的構造函數,函數參數是一個guid格式的ActiveX控件的CLSID字符串。還有一個GetOcx內部方法用于創建ActiveX控件的對象實例,它是一個COM對象引用。
創建了這個COM對象引用后就可以調用Type.InvokerMember方法來動態的調用指定名稱的方法和屬性。
■■■■范例:
筆者最近在使用某電子簽名的ActiveX控件來實現文檔簽名的功能。筆者寫出以下接口代碼
[System.Runtime.InteropServices.ComVisible(false)]
public class DCHebeiCAControl : System.Windows.Forms.AxHost
{
public DCHebeiCAControl()
: base("{e4ee564c-0845-4404-91ee-0c206113333f}")
{ }
public object _ocx = null;
protected override void AttachInterfaces()
{
this._ocx = base.GetOcx();
}
private void CheckOCX()
{
if (this._ocx == null)
{
throw new System.NullReferenceException("_ocx");
}
}
public VersionType GetBaseVersionType()
{
this.CheckOCX();
VersionType result = (VersionType)this._ocx.GetType().InvokeMember(
"GetBaseVersionType",
BindingFlags.InvokeMethod,
null,
this._ocx,
new object[] { });
return result;
}
public string GetCert(string sealSN)
{
this.CheckOCX();
string result = (string)this._ocx.GetType().InvokeMember(
"GetCert",
BindingFlags.InvokeMethod,
null,
this._ocx,
new object[] { sealSN });
return result;
}
public string GetClientDetailVersionInfo()
{
this.CheckOCX();
string result = (string)this._ocx.GetType().InvokeMember(
"GetClientDetailVersionInfo",
BindingFlags.InvokeMethod,
null,
this._ocx,
new object[] { });
return result;
}
public int GetClientVersion()
{
this.CheckOCX();
int result = (int)this._ocx.GetType().InvokeMember(
"GetClientVersion",
BindingFlags.InvokeMethod,
null,
this._ocx,
new object[] { });
return result;
}
public string GetClientVersionInfo()
{
this.CheckOCX();
string result = (string)this._ocx.GetType().InvokeMember(
"GetClientVersionInfo",
BindingFlags.InvokeMethod,
null,
this._ocx,
new object[] { });
return result;
}
public object GetConfig(string argName)
{
this.CheckOCX();
object result = (object)this._ocx.GetType().InvokeMember(
"GetConfig",
BindingFlags.InvokeMethod,
null,
this._ocx,
new object[] { argName });
return result;
}
// ----------- 封裝其他接口 -----------------------------
}//classDCHebeiCAControl
上述代碼中各個功能函數內部代碼結構簡單[袁永福原創],之間有很大的相似性,因此完全可以編寫一個代碼生成器來自動生成上述代碼。
完成自定義的控件后,筆者再創建一個WinForm 窗體,在其Load事件中創建控件并添加到窗體上,其代碼如下
這樣無需使用自動生成的COM接口程序集即可調用ActiveX控件,大幅提高程序
private DCHebeiCAControl _Control = null;
private void frmTest_Load(object sender, EventArgs e)
{
this._Control = new DCHebeiCAControl();
this._Control.Size = new Size(200, 200);
this._Control.Location = new Point(0, 0);
this.Controls.Add(this._Control);
}
的通用性,而且對于32位和64位的項目類型不敏感。方便部署和更新。
不過這樣由于采用后期綁定而帶來一定的性能[袁永福原創]問題,因此對于性能敏感而又頻繁調用ActiveX控件的場景下需要謹慎采用這種模式。
■■■■小結:
在.net開發中調用舊的ActiveX控件是很多開發場景中不得不做的事情。在本文中,筆者介紹了在C#中調用ActiveX控件的標準模式,并提出了一種[袁永福原創]改良模式來提高程序的通用性。為操作ActiveX控件的.NET程序開發提供了一種新的技術手段。