无法通过AppDomains传递GCHandle:没有代表的解决方案?

我在c ++中有基础库,客户端应用程序在C#中。 有c ++ / cli接口可以从C#访问c ++ api。 一切都运行良好,直到多个应用程序域没有像NUnit或WCF托管一样发挥,即有一个应用程序域。

我已经在cli中的gcroot中存储了托管对象以进行回调。 我已经读过这是应用程序域问题的根本原因(“无法通过AppDomains传递GCHandle”),因为它们没有应用程序域信息( http://lambert.geek.nz/2007/05/29/unmanaged -appdomain-callback / )。 有人建议使用委托,但我的底层c ++层期望对象不是函数指针( http://www.lenholgate.com/blog/2009/07/error-cannot-pass-a-gchandle-across-appdomains.html )。 我也尝试过IntPtr,但在这种情况下,我无法在回调期间将其强制转换为我的托管对象。

UPDATE

让我再详细说明一下我的问题。

我在C#中有“Receiver”类 ,它作为输入参数传递给api之一。 此接收器对象用于回调。 在C ++ / CLI中,我创建了一个Native / unmanaged类“ObjectBinder” ,它与托管的Receiver类具有相同的副本(具有相同的方法)。 它保存了gcroot中托管接收者对象的引用 。 当我们从C#调用api时,它来到CLI层, app域是“client exe” 。 我们在gcroot的ObjectBinder中存储参数“managed receiver object”,并将本机ObjectBinder对象的引用传递给C ++。 现在后端代码(c ++和c)向c ++层发送一个asyn回调新线程 ),该层使用ObjectBinder对象向CLI发送回调用。 现在我们在ObjectBinder对象的CLI层中。 但是App域已被更改(在WCF或NUNIT或任何其他创建它自己的App域的服务的情况下,在编译时不知道) 。 现在我想访问存储在gcroot中的托管Receiver对象,以便将回调发送回C#,但它给出了APP DOMAIN错误。

我也试过IntPtrIUnknown *而不是gcrootMarshal :: GetIUnknownForObjectMarshal :: GetObjectForIUnknown但是得到相同的错误。

即使从MarshalByRefObjectContextBoundObject派生,也不能简单地使用GCHandle.ToIntPtr / GCHandle.FromIntPtr .NET应用程序域之间的托管对象。

一种选择是使用COM和全局接口表(GIT) 。 COM Marshaller和.NET运行时将一起编组调用,但您需要坚持使用托管对象实现的COM接口。 这适用于跨不同domians和不同COM公寓线程的调用。

另一种选择是使用Marshal.GetObjectForIUnknown创建一个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); } } } } 

没有委托的这个问题的一种可能的解决方法是从ObjectBinder类调用CrossAppDomainSingleton。 CrossAppDomainSingleton可以保存对Receiver实例的引用。 此解决方案会将您的呼叫分派给专用的应用域。

如果你有多个Receiver实例,这仍然可以使用单例中的映射逻辑并在回调中传递某种id。

你可以在这里找到一个实现。