You cannot marshal a managed object between .NET application domains simply with GCHandle.ToIntPtr
/GCHandle.FromIntPtr
, even if you derive from MarshalByRefObject
or ContextBoundObject
.
One option to do that is to use COM and Global Interface Table (GIT). The COM Marshaller and .NET runtime will marshal the calls together, but you'd need to stick with a COM interface implemented by the managed object. This will work for calls across different domians and different COM apartment threads.
Another option is to create a COM-callable wrapper (CCW) with Marshal.GetIUnknownForObject
, then use Marshal.GetObjectForIUnknown
from another domain. You'll get back a managed proxy object if you derived from MarshalByRefObject
, or an unmanaged RCW proxy otherwise. This will work if you call your managed object on the same thread (albeit, from another app domain).
Here is an example which illustrates the original problem (as I understood it) and these two possible solutions. I use a late-bound InterfaceIsIDispatch
interface here to avoid having to register the type library (no need to do RegAsm, in case you also want to marshal cross-apartments, in addition to cross-domains).
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);
}
}
}
}
MarshalByRefObject
and see if that helps. – IdunB
fromMarshalByRefObject
and accessing it viaMarshal::GetIUnknownForObject()
not helped? Also, are you doing this on the same thread (despite a different appdomain)? – Idun