2012-02-23 111 views
0

我正在開發一個excel插件,在這個插件中有幾個AppDomain。我需要訪問每個AppDomain中的一些共享數據,所以我決定使用跨AppDomain單例。我跟着什麼,在這個線程被描述:C#Excel插件 - 跨域單例例外

http://www.dolittle.com/blogs/einar/archive/2007/05/18/cross-appdomain-singleton.aspx

因爲這是一個Excel插件,我不得不創建包含單,以便使用正確的基本目錄的AppDomain時修改了一點搜索時爲組裝。下面是我修改後的版本:

public class CrossAppDomainSingleton<T> : MarshalByRefObject where T : new() 
{ 
    private static readonly string AppDomainName = "Singleton AppDomain"; 
    private static T _instance; 

    private static AppDomain GetAppDomain(string friendlyName) 
    { 
     IntPtr enumHandle = IntPtr.Zero; 
     mscoree.CorRuntimeHostClass host = new mscoree.CorRuntimeHostClass(); 
     try 
     { 
      host.EnumDomains(out enumHandle); 

      object domain = null; 
      while (true) 
      { 
       host.NextDomain(enumHandle, out domain); 
       if (domain == null) 
       { 
        break; 
       } 
       AppDomain appDomain = (AppDomain)domain; 
       if (appDomain.FriendlyName.Equals(friendlyName)) 
       { 
        return appDomain; 
       } 
      } 
     } 
     finally 
     { 
      host.CloseEnum(enumHandle); 
      Marshal.ReleaseComObject(host); 
      host = null; 
     } 
     return null; 
    } 


    public static T Instance 
    { 
     get 
     { 
      if (null == _instance) 
      { 
       AppDomain appDomain = GetAppDomain(AppDomainName); 
       if (null == appDomain) 
       { 
        string baseDir = AppDomain.CurrentDomain.BaseDirectory; 
        appDomain = AppDomain.CreateDomain(AppDomainName, null, baseDir, null, false); 
       } 
       Type type = typeof(T); 
       T instance = (T)appDomain.GetData(type.FullName); 
       if (null == instance) 
       { 
        instance = (T)appDomain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName); 
        appDomain.SetData(type.FullName, instance); 
       } 
       _instance = instance; 
      } 

      return _instance; 
     } 
    } 
} 

這是我實現CrossAppDomainSingleton的:

public class RealGlobal : CrossAppDomainSingleton<RealGlobal> 
{ 
    //ExcelApp Value Shared 
    private Microsoft.Office.Interop.Excel.Application s_excelApp = null; 

    public Microsoft.Office.Interop.Excel.Application GetExcelApp() 
    { 
     return s_excelApp; 
    } 

    public void SetExcelApp(Microsoft.Office.Interop.Excel.Application app) 
    { 
     s_excelApp = app; 
    } 
} 

有一次,我嘗試使用任何get或set方法(我試過一個屬性也,但沒有得到進一步的) ,我係統地得到一個例外:

Nom inconnu。 (來自HRESULT的異常:0x80020006(DISP_E_UNKNOWNNAME))

或英文: 未知名稱。 (從HRESULT異常:0x80020006(DISP_E_UNKNOWNNAME))

編組工作正常,當我保持內置類型,但鑑於我想訪問的對象(Microsoft.Office.Interop.Excel.Application)是一個COM對象,我擔心這是問題所在。我很新的Remoting和編組。有任何想法嗎?它是否與COM對象的序列化有關?

非常感謝提前! Sean

回答

2

你當然不應該傳遞那個Application對象,它會造成無盡的麻煩。

我建議你寫一個小助手,你可以從每個AppDomain調用來獲得正確的Application對象。這樣做有一個小小的障礙,因爲通常的CreateObject方法並不總是爲您所處理的進程獲得Excel應用程序實例。Andrew Whitechapel在此有一個解釋和正確的代碼:http://blogs.officezealot.com/whitechapel/archive/2005/04/10/4514.aspx

最後,在其他語言環境中調用Excel COM對象時,應該注意區域設置問題。有時調用需要本地化,或者您需要切換線程的UI語言。一些信息在這裏:http://msdn.microsoft.com/en-us/library/aa168494(v=office.11).aspx和他們在VSTO在這裏做的一些信息:http://blogs.msdn.com/b/eric_carter/archive/2005/06/15/429515.aspx

+0

+1,因爲這解決了我的問題,無需幕後重構。 officezealot文章非常有用,並且文化在excel中對我們來說總是美國英語,所以不用擔心這一點。謝謝!!! – 2012-02-24 13:27:04

0

在C#中,remoting對象有兩種工作方式。對象從MarshalByRefObject繼承,或者它需要是Serializable。由於您顯然不想序列化Excel應用程序對象(即使可以,它會很大,並且不會引用另一側的Excel活動副本),唯一的選擇是MarshalByRef。不幸的是,你不能控制應用程序對象的源代碼,所以我認爲這個操作方法是不可行的。

你應該做的是從插件的主要AppDomain中暴露本地.NET API,使用MarshalByRef對象,然後該對象內讓您需要將Excel應用程序對象的調用。

所以你最終的架構是這樣的:

[Excel Application] <--> [.NET Object : MarshalByRef] !! <-- Remoting Boundary --> [Other AppDomains] 

特別注重使被在遠程暴露(這裏我把!)100%的託管代碼,沒有暴露的或依賴於API所有的Excel。

+0

不錯的主意。這需要進行相當大的重構,因爲Excel對象在多個項目中存儲和使用,但是它是最有前途的解決方案,無需編寫一些C++和p /調用 – 2012-02-24 09:22:38