无法跨 AppDomains 传递 GCHandle:没有委托的解决方案? [英] Cannot pass a GCHandle across AppDomains: solution without delegates?

查看:21
本文介绍了无法跨 AppDomains 传递 GCHandle:没有委托的解决方案?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有 C++ 基础库,客户端应用程序是 C#.有 c++/cli 接口可以从 C# 访问 c++ api.在没有像 NUnit 或 WCF 托管这样的多个应用程序域发挥作用(即使用一个应用程序域)之前,每件事都可以正常工作.

I have base library in c++ and client application is in C#. There is c++/cli interface to access c++ api's from C#. Every thing works fine until more than one app domain not come into play like NUnit or WCF hosting i.e. with one app domain.

我已将托管对象存储在 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 但在这种情况下,我无法在回调期间将其转换为我的托管对象.

I have stored managed object in gcroot in cli for callback. I have read that this is the root cause of app domain issue ("Cannot pass a GCHandle across AppDomains") because they don't have app domain info (http://lambert.geek.nz/2007/05/29/unmanaged-appdomain-callback/). someone suggested to use delegates but my underlying c++ layer is expecting object not function pointer(http://www.lenholgate.com/blog/2009/07/error-cannot-pass-a-gchandle-across-appdomains.html). I have also tried IntPtr but in this case i am not able to cast it to my managed object during callbacks.

更新

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

Let me elaborate my problem a bit more.

我在 C# 中有 "Receiver" 类,它作为输入参数传递给其中一个 api.此接收器对象用于回调.在 C++/CLI 中,我创建了一个本机/非托管类 "ObjectBinder",它是托管 Receiver 类的相同副本(具有相同的方法).它在 gcroot 中保存托管接收器对象的引用.当我们从 C# 调用该 api 时,它涉及 CLI 层,应用程序域是客户端 exe".我们将参数托管接收器对象"存储在 gcroot 中的 ObjectBinder 中,并将本机 ObjectBinder 对象的引用传递给 C++.现在后端代码(c++ 和 c)发送一个 asyn 回调(新线程)到 c++ 层,它使用 ObjectBinder 对象将调用发送回 CLI.现在我们在 ObjectBinder 对象的 CLI 层.但应用程序域已更改(在 WCF 或 NUNIT 或任何其他创建它自己的应用程序域的服务的情况下,在编译时未知).现在我想访问存储在 gcroot 中的托管 Receiver 对象以将回调发送回 C#,但它给出了 APP DOMAIN 错误.

I have "Receiver" class in C# and this is passed as input parameter to one of the api. This receiver object is used for callback. In C++/CLI I have created a Native /unmanaged class "ObjectBinder" which is same replica (has same methods) of managed Receiver class. It holds reference of managed receiver object in gcroot. When we call that api from C# it comes to CLI layer and app domain is "client exe". we store the parameter "managed receiver object" in ObjectBinder in gcroot and pass reference of native ObjectBinder object to C++. Now the backend code (c++ and c) send an asyn callback (new thread) to c++ layer which use ObjectBinder object to send back call to CLI. Now we are in CLI layer in ObjectBinder object. BUT App domain has been changed (in case of WCF or NUNIT or any other service that creates it's own App domain which is not known at compile time) . Now i want to access managed Receiver object which is stored in gcroot to send back callback to C# but it gave APP DOMAIN error.

我也尝试过 IntPtrIUnknown * 而不是 gcrootMarshal::GetIUnknownForObject>Marshal::GetObjectForIUnknown 但得到同样的错误.

I have also tried IntPtr and IUnknown * instead of gcroot with Marshal::GetIUnknownForObject and Marshal::GetObjectForIUnknown but getting same error.

推荐答案

您不能简单地使用 GCHandle.ToIntPtr/GCHandle.FromIntPtr,即使您从 MarshalByRefObjectContextBoundObject 派生.

You cannot marshal a managed object between .NET application domains simply with GCHandle.ToIntPtr/GCHandle.FromIntPtr, even if you derive from MarshalByRefObject or ContextBoundObject.

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

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.

另一种选择是使用 Marshal.GetIUnknownForObject 创建一个 COM 可调用包装器 (CCW),然后使用来自另一个域的 Marshal.GetObjectForIUnknown.如果您从 MarshalByRefObject 派生,您将返回一个托管代理对象,否则将返回一个非托管 RCW 代理.如果您在同一个线程(尽管来自另一个应用程序域)上调用您的托管对象,这将起作用.

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).

这是一个示例,它说明了原始问题(据我所知)和这两种可能的解决方案.我在这里使用后期绑定的 InterfaceIsIDispatch 接口来避免必须注册类型库(无需执行 RegAsm,以防您还想编组跨域,除了跨域).

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);
            }
        }
    }
}

这篇关于无法跨 AppDomains 传递 GCHandle:没有委托的解决方案?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆