C# Excel Addin - 跨域单例异常

发布于 2025-01-08 17:34:34 字数 3194 浏览 0 评论 0原文

我正在开发一个 Excel 插件,在这个插件中有几个应用程序域。我需要跨每个 AppDomain 访问一些共享数据,因此我决定使用跨 AppDomain 单例。我遵循了该线程中的描述:

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

因为这是一个excel addin,在创建包含单例的 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 对象,我担心这个是问题所在。

我对远程处理和编组非常陌生。有什么想法吗?它与 COM 对象的序列化有关系吗?

非常感谢! 肖恩

I am developing an excel addin and in this addin there are several AppDomains. I need to have access to some shared data across each AppDomain so I decided to use a cross-AppDomain singleton. I followed what was described in this thread :

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

Because this is an excel addin, I had to modify it a little when creating the AppDomain that contains the singleton so that the correct base directory is used when searching for assemblies. Below is my modified version:

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;
        }
    }
}

Here is my implementation of the 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;
    }
}

Once I try to use either the get or set method (I tried a property also but got no further), I systematically get an exception:

Nom inconnu. (Exception from HRESULT: 0x80020006 (DISP_E_UNKNOWNNAME))

or in English:
Unknown Name. (Exception from HRESULT: 0x80020006 (DISP_E_UNKNOWNNAME))

Marshalling works fine when I keep built-in types, but given that the object I want to access (Microsoft.Office.Interop.Excel.Application) is a COM object, I fear that this is the problem.

I'm very new to Remoting and Marshalling. Any ideas? Does it have something to do with the serialization of a COM object?

Many thanks in advance!
Sean

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

蓝海 2025-01-15 17:34:34

您当然不应该传递该 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

You certainly should not be passing that Application object around, it will cause endless trouble.

I suggest you write a small helper that you can call from each AppDomain to get the right Application object. There is a small snag in doing this, since the usual CreateObject approach will not always get the Excel Application instance for the process you are in. Andrew Whitechapel has an explanation and the right code here: http://blogs.officezealot.com/whitechapel/archive/2005/04/10/4514.aspx.

Finally, you should take some care with locale issues when calling the Excel COM object in other language environments. Sometimes calls need to be localised, or you need to swith the thread's UI language. Some info here: http://msdn.microsoft.com/en-us/library/aa168494(v=office.11).aspx and some info on what they do in VSTO here: http://blogs.msdn.com/b/eric_carter/archive/2005/06/15/429515.aspx.

半山落雨半山空 2025-01-15 17:34:34

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

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

因此,您生成的架构如下所示:

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

特别注重使通过远程处理公开的 API(我放置的位置!!)100% 托管代码,完全不公开或依赖于 Excel。

In C# remoting objects work one of two ways. Either the object inherits from MarshalByRefObject or it needs to be Serializable. Since you clearly don't want to serialize the Excel Application object (even if you could, it would be huge and wouldn't refer to the live copy of Excel on the other side), the only option is MarshalByRef. Unfortunately, you don't control the source code of the Application object either, so I think that this operall approach is a non-starter.

What you should probably do is expose a native .NET API from the addin's main AppDomain, using a MarshalByRef object, and then within that object make the calls you need to the Excel Application object.

So your resulting architecture looks like this:

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

With particular focus on making the API that is exposed over remoting (where I put !!) 100% managed code, with no exposure of or dependency on Excel at all.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文