我正在开发一个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
您当然不应该到处传递应用程序对象,这会造成无尽的麻烦。
我建议您编写一个小助手,可以从每个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。