C#Excel加载项-跨域单例异常

本文关键字:单例 异常 加载项 C#Excel | 更新日期: 2023-09-27 18:25:30

我正在开发一个excel插件,在这个插件中有几个AppDomain。我需要在每个AppDomain中访问一些共享数据,所以我决定使用跨AppDomain的单例。我遵循了这个线程中描述的内容:

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

因为这是一个excel插件,所以在创建包含singleton的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 incnnu。(HRESULT:0x80020006(DISP_E_UNKNOWNNAME)异常)

或英语:未知名称。(HRESULT:0x80020006(DISP_E_UNKNOWNNAME)异常)

当我保留内置类型时,封送可以正常工作,但考虑到我要访问的对象(Microsoft.Office.Interop.Excel.Application)是COM对象,我担心这就是问题所在。

我对Remoting和Marshaling很陌生。有什么想法吗?它是否与COM对象的序列化有关?

非常感谢!Sean

C#Excel加载项-跨域单例异常

您当然不应该到处传递应用程序对象,这会造成无尽的麻烦。

我建议您编写一个小助手,可以从每个AppDomain调用它来获得正确的应用程序对象。这样做有一个小障碍,因为通常的CreateObject方法不会总是为您所处的流程获得Excel应用程序实例。Andrew Whitechapel在这里有一个解释和正确的代码:http://blogs.officezealot.com/whitechapel/archive/2005/04/10/4514.aspx.

最后,在其他语言环境中调用ExcelCOM对象时,应该注意区域设置问题。有时调用需要本地化,或者需要切换线程的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.

在C#中,远程处理对象的工作方式有两种。对象从MarshalByRefObject继承,或者它需要是Serializable。由于您显然不想序列化Excel应用程序对象(即使可以,它也会很大,并且不会引用另一侧的Excel实时副本),因此唯一的选项是MarshalByRef。不幸的是,您也不能控制Application对象的源代码,所以我认为这种操作方法是不可行的。

您可能应该做的是使用MarshalByRef对象从加载项的主AppDomain公开本机.NET API,然后在该对象中对Excel应用程序对象进行所需的调用。

因此,您得到的体系结构如下所示:

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

特别关注使通过远程处理(我放在这里!!)公开的API 100%托管代码,而完全不公开或依赖Excel。