c# – 无法通过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/C++LI中,我创建了一个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错误.

我也尝试过使用Marshal :: GetIUnkNownForObject和Marshal :: GetobjectForIUnkNown的IntPtr和IUnkNown *而不是gcroot但是得到了相同的错误.

解决方法

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

这样做的一个选择是使用COM和Global Interface Table (GIT).COM Marshaller和.NET运行时将一起编组调用,但您需要坚持使用托管对象实现的COM接口.这适用于跨不同domians和不同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,MarshalAs(UnmanagedType.LPStruct)] Guid riid);
            }
        }
    }
}

相关文章

在要实现单例模式的类当中添加如下代码:实例化的时候:frmC...
1、如果制作圆角窗体,窗体先继承DOTNETBAR的:public parti...
根据网上资料,自己很粗略的实现了一个winform搜索提示,但是...
近期在做DSOFramer这个控件,打算自己弄一个自定义控件来封装...
今天玩了一把WMI,查询了一下电脑的硬件信息,感觉很多代码都...
最近在研究WinWordControl这个控件,因为上级要求在系统里,...