您不能仅使用 GCHandle.ToIntPtr/GCHandle.FromIntPtr 在 .NET 应用程序域之间编组托管对象,即使您派生自 MarshalByRefObject 或 ContextBoundObject。
一种选择是使用 COM 和Global Interface Table (GIT)。 COM Marshaller 和 .NET 运行时会将调用编组在一起,但您需要坚持使用由托管对象实现的 COM 接口。这适用于跨不同域和不同 COM 单元线程的调用。
另一种选择是使用Marshal.GetIUnknownForObject 创建一个COM 可调用包装器(CCW),然后使用来自另一个域的Marshal.GetObjectForIUnknown。如果您从 MarshalByRefObject 派生,您将返回一个托管代理对象,否则将返回一个非托管 RCW 代理。如果您在同一个线程(尽管来自另一个应用程序域)上调用您的托管对象,这将起作用。
这是一个示例,说明了原始问题(据我所知)和这两种可能的解决方案。我在这里使用了一个后期绑定的InterfaceIsIDispatch 接口,以避免必须注册类型库(无需执行 RegAsm,以防您还想编组跨域,除了跨域)。
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace ConsoleApplication
{
public class Program
{
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] // late binding only
public interface ITest
{
void Report(string step);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(ITest))]
public class ComObject: MarshalByRefObject, ITest
{
public void Report(string step)
{
Program.Report(step);
}
}
public static void Main(string[] args)
{
var obj = new ComObject();
obj.Report("Object created.");
System.AppDomain domain = System.AppDomain.CreateDomain("New domain");
// via GCHandle
var gcHandle = GCHandle.Alloc(obj);
domain.SetData("gcCookie", GCHandle.ToIntPtr(gcHandle));
// via COM GIT
var git = (ComExt.IGlobalInterfaceTable)(Activator.CreateInstance(Type.GetTypeFromCLSID(ComExt.CLSID_StdGlobalInterfaceTable)));
var comCookie = git.RegisterInterfaceInGlobal(obj, ComExt.IID_IUnknown);
domain.SetData("comCookie", comCookie);
// via COM CCW
var unkCookie = Marshal.GetIUnknownForObject(obj);
domain.SetData("unkCookie", unkCookie);
// invoke in another domain
domain.DoCallBack(() =>
{
Program.Report("Another domain");
// trying GCHandle - fails
var gcCookie2 = (IntPtr)(System.AppDomain.CurrentDomain.GetData("gcCookie"));
var gcHandle2 = GCHandle.FromIntPtr(gcCookie2);
try
{
var gcObj2 = (ComObject)(gcHandle2.Target);
gcObj2.Report("via GCHandle");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
// trying COM GIT - works
var comCookie2 = (uint)(System.AppDomain.CurrentDomain.GetData("comCookie"));
var git2 = (ComExt.IGlobalInterfaceTable)(Activator.CreateInstance(Type.GetTypeFromCLSID(ComExt.CLSID_StdGlobalInterfaceTable)));
var obj2 = (ITest)git2.GetInterfaceFromGlobal(comCookie2, ComExt.IID_IUnknown);
obj2.Report("via GIT");
// trying COM CCW
var unkCookie2 = (IntPtr)(System.AppDomain.CurrentDomain.GetData("unkCookie"));
// this casting works because we derived from MarshalByRefObject
var unkObj2 = (ComObject)Marshal.GetObjectForIUnknown(unkCookie2);
obj2.Report("via CCW");
});
Console.ReadLine();
}
static void Report(string step)
{
Console.WriteLine(new
{
step,
ctx = Thread.CurrentContext.GetHashCode(),
threadId = Thread.CurrentThread.ManagedThreadId,
domain = Thread.GetDomain().FriendlyName,
});
}
public static class ComExt
{
static public readonly Guid CLSID_StdGlobalInterfaceTable = new Guid("00000323-0000-0000-c000-000000000046");
static public readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046");
[ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00000146-0000-0000-C000-000000000046")]
public interface IGlobalInterfaceTable
{
uint RegisterInterfaceInGlobal(
[MarshalAs(UnmanagedType.IUnknown)] object pUnk,
[In, MarshalAs(UnmanagedType.LPStruct)] Guid riid);
void RevokeInterfaceFromGlobal(uint dwCookie);
[return: MarshalAs(UnmanagedType.IUnknown)]
object GetInterfaceFromGlobal(
uint dwCookie,
[In, MarshalAs(UnmanagedType.LPStruct)] Guid riid);
}
}
}
}