CSharp Tips:讓DotNet實現的COM對象支持IObjectSafety接口

當我們實現的COM對象,或者ActiveX控件在瀏覽器中調用的時候,往往會出現警告框,提示不安全的控件正在運行。這是因爲瀏覽器安全策略所限定的,瀏覽器認爲只有“安全的對象”才能夠被執行。
所謂安全的對象就是指那些不訪問本地資源的對象,例如不會去讀註冊表,不會寫文件等等。一個滿足條件的對象通過支持IObjectSafety接口告訴瀏覽器,自己是合法的。
下面就簡單的介紹一下怎麼在C#中實現對於IObjectSafety接口的支持。
 
思路
C/C++d的程序可以直接在SDK中找到IObjectSafety的定義,所以需要支持的話非常容易。C#比較麻煩,因爲我們沒有辦法獲得IObjectSafety的定義,不過沒有問題,我們可以按照IObjectSafety在SDK中的定義,在C#的工程中重新定義該接口。
如果大家瞭解COM機制一定會知道,所謂藉口的定義之出現在類型庫中,與實現無關。而判斷一個接口唯一性就是定義接口時指定的UUID。此外自己重新定義時需要保證接口中沒有函數的參數與返回值必須與原定義一致即可。
我們的做法就是,找到ObjSafe.idl,然後複製其中的UUID,利用這個UUID在C#中定一個interface IObjectSafety,並且申明其中的兩個函數;定義完成之後,讓需要檢查安全接口的組件繼承該接口,並在該組件內部實現IObjectSafety的兩個函數,按照要求做適當的返回,那麼用這個組件包裝的COM對象在IE中調用就被認爲是安全的了。
 
第一次嘗試
按照上面的思路,我們開始進行嘗試
idl中的接口定義
[
 object,
 uuid(CB5BDC81-93C1-11cf-8F20-00805F2CD064),
 pointer_default(unique)
]
interface IObjectSafety : IUnknown
{
 HRESULT GetInterfaceSafetyOptions(
  [in]  REFIID riid,     // Interface that we want options for
  [out] DWORD * pdwSupportedOptions, // Options meaningful on this interface
  [out] DWORD * pdwEnabledOptions);  // current option values on this interface
 
 HRESULT SetInterfaceSafetyOptions(
  [in]  REFIID riid,     // Interface to set options for
  [in]  DWORD  dwOptionSetMask,  // Options to change
  [in]  DWORD  dwEnabledOptions);  // New option values
}
IObjectSafety接口定義
    因爲接口中存在指針,所以直接採用Int32的整型形式,用到了unsafe code。
 [Guid("CB5BDC81-93C1-11cf-8F20-00805F2CD064")]
 public interface IObjectSafety
 {
  // methods
  unsafe void GetInterfacceSafyOptions(
   System.Int32 riid,
   System.Int32* pdwSupportedOptions,
   System.Int32* pdwEnabledOptions);
  void SetInterfaceSafetyOptions(
   System.Int32 riid,
   System.Int32 dwOptionsSetMask,
   System.Int32 dwEnabledOptions);
 }
繼承
 public class MyControl : System.Windows.Forms.UserControl,IObjectSafety
實現
  // implement functions of IObjectSafety
  public unsafe void GetInterfacceSafyOptions(System.Int32 riid,System.Int32* pdwSupportedOptions,System.Int32* pdwEnabledOptions)
  {
        ...
  }
  public void SetInterfaceSafetyOptions(System.Int32 riid,System.Int32 dwOptionsSetMask,System.Int32 dwEnabledOptions)
  {
        ...    
  }
    一切正常編譯通過,但是通過IE調用測試頁面,在裝載頁面的時候卻產生了一個關閉應用程序系統異常,仔細察看內容Error Report是非法的內存地址訪問。無語中...
 
第二次嘗試
    由於是非法的內存地址訪問,很自然的聯想到是接口定義的問題,因爲存在unsafe code,查查文檔發現根本無需使用unsafe code這麼誇張,可以通過out這個參數修飾符解決。
    修改定義和實現如下
IObjectSafety接口定義
 [Guid("CB5BDC81-93C1-11cf-8F20-00805F2CD064")]
 public interface IObjectSafety
 {
  // methods
  void GetInterfacceSafyOptions(
   System.Int32 riid,
   out System.Int32 pdwSupportedOptions,
   out System.Int32 pdwEnabledOptions);
  void SetInterfaceSafetyOptions(
   System.Int32 riid,
   System.Int32 dwOptionsSetMask,
   System.Int32 dwEnabledOptions);
 }
實現
  // implement functions of IObjectSafety
  public unsafe void GetInterfacceSafyOptions(System.Int32 riid,out System.Int32 pdwSupportedOptions,out System.Int32 pdwEnabledOptions)
  {
        ...
  }
  public void SetInterfaceSafetyOptions(System.Int32 riid,System.Int32 dwOptionsSetMask,System.Int32 dwEnabledOptions)
  {
        ...    
  }
    編譯通過,不錯;IE調用測試頁面,同樣的錯誤!鬱悶,無鬥志,回家。
 
第三次嘗試
    睡了一覺,飽餐戰飯,繼續思考。
    自己比較了編譯器生成的類型庫,發現一些很奇怪的現象,在類型庫中IObjectSafety居然被定義了兩次interface IObjectSafety : IUnknown,以及dispinterface IObjectSafety : IDispatch。而偏偏MyControl是從dispinterface IObjectSafety上繼承的。這就與正確的IObjectSafety的接口說明相違背,問題應該出在這裏。
    MSDN,查文檔。System.Runtime.InteropService下有很多關於描述接口的屬性,從中可以找到產生問題的原因。有一個屬性InterfaceTypeAttribute,就是用來說明定義的接口是從IUnknown繼承還是IDispatch繼承,缺省情況下是Dual的,所以是兩份。
    再次定義如下:
 [Guid("CB5BDC81-93C1-11CF-8F20-00805F2CD064"),InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
 public interface IObjectSafety
 {
  // methods
  void GetInterfacceSafyOptions(
   System.Int32 riid,
   out System.Int32 pdwSupportedOptions,
   out System.Int32 pdwEnabledOptions);
  void SetInterfaceSafetyOptions(
   System.Int32 riid,
   System.Int32 dwOptionsSetMask,
   System.Int32 dwEnabledOptions);
 }
    其餘代碼不變,重新編譯,通過;察看導出類型庫,果然少了很多垃圾;調用測試頁面,正確。激動中...
 
怎樣讓IE認爲你的對象安全
    實現了這個接口,剩下的事情就很簡單了。前面提到過如果按照正規的途徑你需要確保你的代碼沒有訪問系統的本地自然,然後按照文檔要求,當該對象被不同的接口調用查詢的時候,做不同的反饋。具體實現可以在MSDN的Sample中找到。
    當然我們可以寫一個對象讀寫本地文件,但是支持IObjectSafety接口,並且始終聲明自己是合法的,這樣來欺騙瀏覽器,那麼代碼就很簡單了,如下:
  // implement functions of IObjectSafety
  public void GetInterfacceSafyOptions(System.Int32 riid,out System.Int32 pdwSupportedOptions,out System.Int32 pdwEnabledOptions)
  {
   pdwSupportedOptions = CLsObjectSafety.INTERFACESAFE_FOR_UNTRUSTED_CALLER;
   pdwEnabledOptions = CLsObjectSafety.INTERFACESAFE_FOR_UNTRUSTED_DATA;
  }
  public void SetInterfaceSafetyOptions(System.Int32 riid,System.Int32 dwOptionsSetMask,System.Int32 dwEnabledOptions)
  {
  }
    只要這麼些,就不會再有討厭的對話框彈出了。
 
    如果你的組件是在客戶端安裝,在瀏覽器中調用,那麼所有的工作已經完成;所以是希望通過Codebase的方式下載發佈,你還需要去搞一個數字簽字,已經不是本文討論的範圍了,就到這裏,結束了。
 
 
參考文檔
1、HOWTO: Implement IObjectSafety in Visual Basic Controls(http://support.microsoft.com/default.aspx?scid=kb;EN-US;q182598)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章