[.NET] 使用 .NET Framework 開發 ActiveX Control (2) - 使用 C# 開發 ActiveX 控制項

本文將會開始以 C# 實作控制項,讓你可以有開發控制項的經驗。

本文將會開始以 C# 實作控制項,讓你可以有開發控制項的經驗。

準備工作

在我們開始用開發ActiveX控制項前,必須要先做一些準備工作,以便在開發完成時順利測試控制項,最重要的是Internet Explorer的安全性設定,在開發時期基本上我們不會進行元件的安全設定(例如簽章),為了要讓測試工作可以順利,我們要對IE的安全性做一些設定。

首先,我們要先把網站加入受信任的網站,我們可以由控制項的網際網路選項(或是Internet選項)中,在『安全性』頁籤中,選取『信任的網站』,然後按『網站』按鈕:

clip_image001_thumb1

將本機的網址(本文為http://localhost),加入信任網站中:

clip_image002_thumb1

加入完成後,一樣在『安全性』頁籤的『信任的網站』,按下『自訂等級』按鈕,會顯示安全等級的細部設定視窗:

clip_image003_thumb1

 

請依下表設定安全層級:

 

選項

下載未簽署的ActiveX控制項

提示(Prompt)

允許程式碼片段

提示(Prompt)

啟始不標示為安全的ActiveX控制項

提示(Prompt)

設定好以後,我們就可以來開始開發我們的控制項了。

 

使用C#開發ActiveX控制項-基本功能

本文將使用C#以及Visual Studio 2008作為開發環境,但這個方法可以在Visual Studio 2005以後的開發工具使用(但我只試過VS2008)。同時,我不會在這裡說明細部的操作程序,因此我假定你對Visual Studio的操作已經有基本的瞭解。

首先,先建立一個空白的方案:

clip_image005_thumb1

然後,在方案中新增一個C#的類別庫專案(我的專案命名是MyControl),這個專案會產生DLL檔案,也是作為控制項的專案之一。您也可以用Windows Forms控制項專案來做,但因為我們不一定會需要使用者介面,因此由類別庫專案來開發會比較簡單。

當專案產生後,請將專案內容打開,並在『建置』頁籤中,將『註冊COM Interop』核取起來後存檔。然後把預設的Class1.cs檔案更名為MyDateControl.cs(若Visual Studio提示更改類別名稱,請接受它),接著將它打開,加入下列程式碼:

using System.Runtime.InteropServices;

namespace MyControl

{

[Guid("C90E96C1-8534-4243-9530-960D9AF982CB")]

[ComVisible(true)]

public class MyDateControl

{

public DateTime Today { get { return DateTime.Today; } }

public string GetTodayDateString() {

return DateTime.Today.ToString("yyyy/MM/dd HH:mm:ss");

}

}

}

這段程式碼會把MyDateControl類別登錄到COM Registry Database中,設定它的GUID,並且開放一個屬性與方法。加入完成後請建置它,此時組件會被登錄,要檢查它是否被登錄,你可以開啟Visual Studio 2008命令提示字元:

clip_image006_thumb1

並輸入oleview指令:

clip_image008_thumb1

此時會出現OLE View應用程式視窗,請由左邊展開Type Libraries,你可以在節點中找到MyControl這個節點,表示組件已經登錄到COM Registry Database內:

clip_image010_thumb1

現在,我們再於方案中新增一個ASP.NET網站或應用程式專案(作法我就不贅述),或是打開一個既有的Web應用程式專案,然後在根目錄中加入一個新的HTML網頁,並加入下列HTML碼:

<html>

<head>

<title></title>

<script language="javascript" type="text/javascript">

function displayDateFromProperty() {

alert(document.getElementById("myControl").Today);

}

function displayDate() {

alert(document.getElementById("myControl").GetTodayDateString());

}

</script>

</head>

<body>

<input type="button" value="Display Property" onclick="displayDateFromProperty()" /><input type="button" value="Invoke Method" onclick="displayDate()" /><br />

<object id="myControl" classid="clsid:C90E96C1-8534-4243-9530-960D9AF982CB" width="0px" height="0px" />

</body>

</html>

此段HTML碼是一個簡單的網頁,並在網頁內放了一個ActiveX控制項,這個控制項的CLSID要和類別所設定的GUID一致,否則IE會無法載入此控制項。在這個網頁中內含了兩個JavaScript指令碼,它會呼叫在控制項內開放的屬性和方法。

 

NOTE

classid=”clsid:{GUID}”內的GUID值必須要和類別的GUID一致。

 

現在,我們可以來測試這個控制項了,請用瀏覽器瀏覽這個網頁,你應該會看到這個畫面:

clip_image012_thumb1

當你按下Display Property和Invoke Method時,應該都會看到顯示今天日期的訊息對話盒,但第一次按時,應該會看到這個訊息:

clip_image013_thumb1

這表示你目前的控制項是不安全的,要按下『是』,才可以順利執行:

clip_image014_thumb1

 

NOTE

我們會在後面告訴你怎麼讓這個交互作用警告的對話盒不再出現。

 

使用C#開發ActiveX控制項-產生使用者介面

在完成了上面的控制項後,你應該會對用C#開發ActiveX控制項有一點感覺了,我們再來寫一個具有使用者介面的控制項,基本上作法差不多,但這次要新增的是Windows Forms使用者控制項(我的控制項命名為MyDateControlUI)。

clip_image016_thumb2

新增完成時,Visual Studio會開啟Designer視窗,請在上面放兩個按鈕,如同前一個範例一樣,其中一個是顯示今日的日期(cmdDisplayToday),但另一個會讀取本機上的登錄資料庫(cmdGetOSVer),以取得目前的作業系統資訊:

clip_image018_thumb1

畫面設定完成後,請開啟控制項的程式碼,加入對COM Interoperability以及Microsoft.Win32的命名空間參考:

using System.Runtime.InteropServices;

using Microsoft.Win32;

請為按鈕填入下列程式碼(分別是cmdDisplayToday按鈕以及cmdGetOSVer按鈕的Click事件常式):

private void cmdDisplayToday_Click(object sender, EventArgs e)

{

MessageBox.Show(DateTime.Today.ToString("yyyy/MM/dd"), "Today");

}

private void cmdGetOSVer_Click(object sender, EventArgs e)

{

RegistryKey key = Registry.LocalMachine.OpenSubKey(

@"SOFTWARE\Microsoft\Windows NT\CurrentVersion");

MessageBox.Show(key.GetValue("BuildLabEx").ToString(), "OS Build Ver");

key = null;

}

然後依照上一步的程序,在類別上加入ComVisible和GUID的設定:

namespace MyControl

{

[Guid("1C267377-9034-4919-A2F8-C1D8961DDDB8")]

[ComVisible(true)]

public partial class MyDateControlUI : UserControl

{

public MyDateControlUI()

{

InitializeComponent();

}

...

}

}

接著,我們修改上一程序所用的HTML網頁,先將原本的<body></body>內的指令移除或註解起來,再加入下面的指令(CLSID值一樣要和類別相同),這裡用的雖然也是<object>標籤,但有設定寬度和高度,以配合控制項。

<object id="myControl" classid="clsid:1C267377-9034-4919-A2F8-C1D8961DDDB8" width="325px" height="97px" />

現在,我們再以瀏覽器來瀏覽此網頁,你看到的應該會是這樣:

clip_image020_thumb1

同時,按下Display Today會得到今天的日期,而按下Get OS Version,會出現作業系統的Build版本資訊,此版本資訊是來自於登錄資料庫,因此可證明ActiveX控制項具有與作業系統互動的能力(Flash和Silverlight in-browser application就沒辦法做到):

clip_image021_thumb1

 

宣告控制項是安全的

我們一開始設計的MyControl控制項在執行時都會出現潛在安全性問題的提示訊息,這是因為Internet Explorer內建的安全性機制使然,就算把提示的設定改為啟用(Enabled),仍然會出現這個訊息,要將此訊息關閉,你必須要在你的控制項實作一個特別的介面,向IE宣告你的控制項與JavaScript互動時是安全的。

 

NOTE

只要你的控制項會和JavaScript互動,就必須要實作這個介面。

 

這個介面是IObjectSafety,它是COM原生函式庫內的一個介面,當IE在載入你的控制項時,會在控制項內搜尋是否有這個介面的存在(即控制項是否有實作此介面),如果沒有找到的話,就會顯示潛在安全性問題的訊息,因此我們需要實作這個介面。請在專案內加入一個類別檔案,命名為IObjectSafety.cs檔,並將下列程式加入:

/// <summary>

/// 針對 ActiveX Control 的 IObjectSafety 介面引用。

/// </summary>

[ComImport]

[Guid("CB5BDC81-93C1-11cF-8F20-00805F2CD064")]

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]

public interface IObjectSafety

{

/// <summary>

/// 取得物件的安全選項。

/// </summary>

/// <param name="riid">介面代碼。</param>

/// <param name="pdwSupportedOptions">支援的安全性選項。</param>

/// <param name="pdwEnabledOptions">啟用選項。</param>

void GetInterfaceSafetyOptions(int riid, out int pdwSupportedOptions, out int pdwEnabledOptions);

/// <summary>

/// 設定物件的安全選項。

/// </summary>

/// <param name="riid">介面代碼。</param>

/// <param name="pdwSupportedOptions">支援的安全性選項。</param>

/// <param name="pdwEnabledOptions">啟用選項。</param>

void SetInterfaceSafetyOptions(int riid, int pdwSupportedOptions, int pdwEnabledOptions);

}

/// <summary>

/// IObjectSafety 使用的列舉值。

/// </summary>

public class IObjectSafetyEnums

{

/// <summary>

/// 對未受信任的呼叫者宣布此介面為安全。

/// </summary>

public const int INTERFACESAFE_FOR_UNTRUSTED_CALLER = 0x0000001;

/// <summary>

/// 對未受信任的資料宣布此介面為安全。

/// </summary>

public const int INTERFACESAFE_FOR_UNTRUSTED_DATA = 0x0000002;

}

這個介面有兩個方法:

方法

功能

GetInterfaceSafetyOptions

由COM用戶端取得此元件的安全選項。

SetInterfaceSafetyOptions

由COM用戶端設定此元件的安全選項。

 

如果控制項內沒有特別要針對不同的介面做設定,那麼我們可以直接以下列程式實作:

public void GetInterfaceSafetyOptions(int riid, out int pdwSupportedOptions, out int pdwEnabledOptions)

{

pdwSupportedOptions = IObjectSafetyEnums.INTERFACESAFE_FOR_UNTRUSTED_CALLER | IObjectSafetyEnums.INTERFACESAFE_FOR_UNTRUSTED_DATA;

pdwEnabledOptions = IObjectSafetyEnums.INTERFACESAFE_FOR_UNTRUSTED_CALLER | IObjectSafetyEnums.INTERFACESAFE_FOR_UNTRUSTED_DATA;

}

public void SetInterfaceSafetyOptions(int riid, int pdwSupportedOptions, int pdwEnabledOptions)

{

}

如同上面的說明,請在MyControl程式中指示要實作IObjectSafety,並且加入上面的實作,完成的類別程式碼應該會像這樣:

[Guid("C90E96C1-8534-4243-9530-960D9AF982CB")]

[ComVisible(true)]

public class MyDateControl : IObjectSafety

{

public DateTime Today { get { return DateTime.Today; } }

public string GetTodayDateString() { return DateTime.Today.ToString("yyyy/MM/dd HH:mm:ss"); }

public void GetInterfaceSafetyOptions(int riid, out int pdwSupportedOptions, out int pdwEnabledOptions)

{

pdwSupportedOptions = IObjectSafetyEnums.INTERFACESAFE_FOR_UNTRUSTED_CALLER | IObjectSafetyEnums.INTERFACESAFE_FOR_UNTRUSTED_DATA;

pdwEnabledOptions = IObjectSafetyEnums.INTERFACESAFE_FOR_UNTRUSTED_CALLER | IObjectSafetyEnums.INTERFACESAFE_FOR_UNTRUSTED_DATA;

}

public void SetInterfaceSafetyOptions(int riid, int pdwSupportedOptions, int pdwEnabledOptions)

{

}

}

完成後請編譯此組件,然後修改HTML網頁使用MyControl控制項,然後以瀏覽器瀏覽該網頁,並按下網頁中的按鈕,你會發現潛在安全性的訊息不再出現。

(To be continued…)