2014-06-09 11 views
7

Ho una libreria di base in C++ e l'applicazione client è in C#. C'è l'interfaccia C++/cli per accedere a C++ api da C#. Ogni cosa funziona bene fino a quando non entrano in gioco più domini di app come NUnit o WCF hosting con un dominio app.Impossibile passare un GCHandle attraverso AppDomain: soluzione senza delegati?

Ho memorizzato l'oggetto gestito in gcroot in cli per la richiamata. Ho letto che questa è la causa principale del problema del dominio dell'app ("Impossibile passare un GCHandle attraverso AppDomains") perché non dispongono di informazioni sul dominio dell'app (http://lambert.geek.nz/2007/05/29/unmanaged-appdomain-callback/). qualcuno ha suggerito di usare delegati ma il mio strato C++ sottostante si aspetta un puntatore oggetto non funzionante (http://www.lenholgate.com/blog/2009/07/error-cannot-pass-a-gchandle-across-appdomains.html). Ho anche provato IntPtr ma in questo caso non sono in grado di trasmetterlo al mio oggetto gestito durante i callback.

UPDATE

Vorrei esporre il mio problema un po 'più.

I Classe "ricevitore" in C# e viene passato come parametro di input a una delle API. Questo oggetto ricevente viene utilizzato per la richiamata. In C++/CLI ho creato una classe nativa/non gestita "ObjectBinder" che è la stessa replica (ha gli stessi metodi) della classe Receiver gestita. Contiene riferimento dell'oggetto destinatario gestito in gcroot. Quando chiamiamo quell'api da C# viene a livello CLI e il dominio app è "client exe". archiviamo il parametro "oggetto destinatario gestito" in ObjectBinder in gcroot e passiamo il riferimento dell'oggetto ObjectBinder nativo a C++. Ora il codice di backend (C++ e c) invia un callback asyn (nuovo thread) al livello C++ che utilizza l'oggetto ObjectBinder per inviare indietro la chiamata a CLI. Ora siamo nel livello CLI nell'oggetto ObjectBinder. MA Il dominio dell'app è stato modificato (in caso di WCF o NUNIT o qualsiasi altro servizio che crea il proprio dominio app che non è noto al momento della compilazione). Ora voglio accedere all'oggetto Receiver gestito che è memorizzato in gcroot per rimandare la callback a C# ma ha dato errore APP DOMAIN.

Ho anche provato IntPtr e IUnknown * invece di gcroot con maresciallo :: GetIUnknownForObject e maresciallo :: GetObjectForIUnknown ma ottenere lo stesso errore.

+0

Difficile capire l'aggiornamento senza visualizzare il codice effettivo. Se ho capito bene, hai un oggetto * non gestito * che contiene un puntatore dell'interfaccia COM su un oggetto * gestito * B. Si accede a un altro appdomain, dove richiama B, è giusto? Se è così, fai in modo che B derivi da "MarshalByRefObject" e vedi se questo aiuta. – Noseratio

+0

@Noseratio Hai capito bene. –

+0

Quindi, ha derivato 'B' da 'MarshalByRefObject' e l'accesso tramite' Marshal :: GetIUnknownForObject() 'non è stato aiutato? Inoltre, stai facendo questo sullo stesso thread (nonostante un diverso appdomain)? – Noseratio

risposta

6

Non è possibile marshalling un oggetto gestito tra domini di applicazione .NET semplicemente con GCHandle.ToIntPtr/GCHandle.FromIntPtr, anche se derivano da MarshalByRefObject o ContextBoundObject.

Un'opzione per fare ciò è utilizzare COM e Global Interface Table (GIT). COM Marshaller e .NET runtime eseguiranno il marshalling delle chiamate insieme, ma sarà necessario mantenere un'interfaccia COM implementata dall'oggetto gestito. Ciò funzionerà per le chiamate tra diversi domini e diversi thread di appartamenti COM.

Un'altra opzione è quella di creare un wrapper con calle COM (01WC) con Marshal.GetIUnknownForObject, quindi utilizzare Marshal.GetObjectForIUnknown da un altro dominio. Riceverete un oggetto proxy gestito se derivate da MarshalByRefObject o altrimenti da un proxy RCW non gestito. Questo funzionerà se chiami l'oggetto gestito sullo stesso thread (anche se da un altro dominio dell'app).

Ecco un esempio che illustra il problema originale (come ho capito) e queste due possibili soluzioni. Qui utilizzo un'interfaccia InterfaceIsIDispatch tardiva per evitare di dover registrare la libreria dei tipi (non è necessario eseguire RegAsm, nel caso in cui si desideri effettuare il marshalling di cross-apartments, oltre ai domini incrociati).

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

[A] Impossibile eseguire il cast di ComObject su [B] ComObject. Il tipo A deriva da "CSImageDBConsoleApp, Version = 1.0.0.0, Culture = neutral, PublicKeyToken = null" nel contesto "Predefinito" nella posizione "E: \ CSImageDBConsoleApp.exe". Il tipo B proviene da "CSImageDBConsoleApp, Version = 1.0.0.0, Culture = neutral, PublicKeyToken = null" nel contesto "Predefinito" nella posizione ** var gcObj2 = (ComObject) (gcHandle2.Target); ** –

+0

@dream_machine, I non riesci a capire cosa c'è che non va nel tuo codice: non hai nemmeno pubblicato alcun codice. OTOH, il codice nella mia risposta funziona bene. La chiamata a "Report" viene eseguita il marshalling sul dominio originale dal dominio "" Nuovo dominio "'. – Noseratio

+0

Ho appena provato il tuo codice. Lo controllerò di nuovo –

0

Una soluzione possibile per questo problema senza delegati è quello di chiamare un CrossAppDomainSingleton dalla classe ObjectBinder. CrossAppDomainSingleton può contenere il riferimento all'istanza di Destinatario. Questa soluzione invierà la tua chiamata a un dominio app dedicato.

Se si dispone di più istanze Receiver, questo potrebbe ancora funzionare con una logica di mapping nel singleton e passare una sorta di id nel callback.

È possibile trovare un'implementazione here.

Problemi correlati