2014-06-09 18 views
7

Posiadam bibliotekę podstawową w języku C++, a aplikacja kliencka jest w języku C#. Istnieje interfejs C++/cli do uzyskiwania dostępu do aplk C++ z C#. Wszystko działa dobrze, dopóki więcej niż jedna domena aplikacji nie wchodzi w grę, tak jak hostowanie NUnit lub WCF, czyli jedna domena aplikacji.Nie można przekazać klucza GCH do domeny AppDomains: rozwiązanie bez delegatów?

Mam przechowywane obiekt zarządzany w gcroot w cli dla oddzwonienia. Czytałem, że jest to podstawowa przyczyna problemu z domeną aplikacji ("Nie można przekazać GCHANLE w obrębie AppDomains"), ponieważ nie mają informacji o domenie aplikacji (http://lambert.geek.nz/2007/05/29/unmanaged-appdomain-callback/). ktoś zasugerował użycie delegatów, ale moja podstawowa warstwa C++ oczekuje wskaźnika obiektu, a nie funkcji (http://www.lenholgate.com/blog/2009/07/error-cannot-pass-a-gchandle-across-appdomains.html). Próbowałem również IntPtr, ale w tym przypadku nie mogę przesłać go do mojego zarządzanego obiektu podczas wywołań zwrotnych.

UPDATE

Pozwól mi rozwinąć mój problem nieco bardziej.

Mam "Odbiornik" klasy w języku C# i jest przekazywany jako parametr wejściowy do jednego z api. Ten obiekt odbiornika jest używany do wywołania zwrotnego. W C++/CLI utworzyłem klasę Native/unmanaged "ObjectBinder", która jest tą samą repliką (ma te same metody) zarządzanej klasy Receiver. Zawiera odniesienie do obiektu zarządzanego odbiornika w gcroot. Kiedy wywołujemy ten api z C#, przychodzi do warstwy CLI, a domeną aplikacji jest "klient exe". przechowujemy parametr "managed receiver object" w ObjectBinder w gcroot i przekazujemy referencyjny obiekt obiektu ObjectBinder do C++. Teraz kod zaplecza (C++ i c) przesyła asyn wywołanie zwrotne (nowy wątek) do warstwy C++, która używa obiektu ObjectBinder do wysłania z powrotem wywołania do CLI. Teraz jesteśmy w warstwie CLI w obiekcie ObjectBinder. ALE domena aplikacji została zmieniona (w przypadku WCF lub NUNIT lub jakiejkolwiek innej usługi, która tworzy własną domenę aplikacji, która nie jest znana w czasie kompilacji). Teraz chcę uzyskać dostęp do zarządzanego obiektu Receiver, który jest przechowywany w gcroot, aby oddzwonić do C#, ale spowodował błąd APP DOMAIN.

Próbowałem również IntPtr i IUnknown * zamiast gcroot z Marszałek :: GetIUnknownForObject i Marszałek :: GetObjectForIUnknown ale uzyskanie tego samego błędu.

+0

Trudno zrozumieć Twoją aktualizację, nie widząc rzeczywistego kodu. Jeśli mam to dobrze, masz * niezarządzany * obiekt A, który przechowuje wskaźnik interfejsu COM do * zarządzanego * obiektu B. A jest dostępny z innej aplikacji, gdzie oddzwania B, czy to prawda? Jeśli tak, ustaw B na podstawie 'MarshalByRefObject' i sprawdź, czy to pomaga. – Noseratio

+0

@Noseratio Masz to dobrze. –

+0

A więc, czy nie uzyskano odpowiedzi na 'B' z' MarshalByRefObject' i dostęp do niego poprzez 'Marshal :: GetIUnknownForObject()'? Czy robisz to w tym samym wątku (pomimo innej aplikacji)? – Noseratio

Odpowiedz

6

Nie można zebrać zarządzanego obiektu pomiędzy domenami aplikacji .NET po prostu z GCHandle.ToIntPtr/GCHandle.FromIntPtr, nawet jeśli pochodzą z MarshalByRefObject lub ContextBoundObject.

Jedną z opcji jest użycie COM i Global Interface Table (GIT). Środowisko uruchomieniowe COM Marshaller i .NET będzie koordynować połączenia, ale trzeba będzie trzymać się interfejsu COM zaimplementowanego przez zarządzany obiekt. Będzie to działać w przypadku połączeń między różnymi domiami i różnych wątków w domach COM.

Inną opcją jest utworzenie opakowania zbiorczego COM (CCW) z Marshal.GetIUnknownForObject, a następnie użycie Marshal.GetObjectForIUnknown z innej domeny. Dostaniesz ponownie zarządzany obiekt proxy, jeśli otrzymałeś od MarshalByRefObject lub inaczej niezarządzanego proxy RCW. To zadziała, jeśli wywołasz zarządzany obiekt w tym samym wątku (aczkolwiek z innej domeny aplikacji).

Oto przykład ilustrujący pierwotny problem (jak rozumiem) i te dwa możliwe rozwiązania. Używam interfejsu późno-limitowanego InterfaceIsIDispatch tutaj, aby uniknąć konieczności rejestrowania biblioteki typów (nie trzeba robić RegAsm, na wypadek, gdybyś chciał także prowadzić rozmowy między apartamentami, oprócz domen między domenami).

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] Obiekt ComObject nie może być rzutowany na [B] ComObject. Typ A pochodzi z "CSImageDBConsoleApp, Version = 1.0.0.0, Culture = neutral, PublicKeyToken = null" w kontekście "Default" w lokalizacji "E: \ CSImageDBConsoleApp.exe". Typ B pochodzi z "CSImageDBConsoleApp, Version = 1.0.0.0, Culture = neutral, PublicKeyToken = null" w kontekście "Default" w lokalizacji ** var gcObj2 = (ComObject) (gcHandle2.Target); ** –

+0

@dream_machine, I nie potrafię powiedzieć, co jest nie tak z twoim kodem - nie napisałeś nawet żadnego kodu. OTOH, kod w mojej odpowiedzi działa dobrze. Wywołanie "Raportu" przenosi się do pierwotnej domeny z domeny "Nowa domena". – Noseratio

+0

Po prostu wypróbowałem twój kod. Sprawdzę to jeszcze raz –

0

Jedną z możliwości obejścia tego problemu bez delegatów jest wywołać CrossAppDomainSingleton z klasy ObjectBinder. CrossAppDomainSingleton może przechowywać odwołanie do instancji Odbiornika. To rozwiązanie wyśle ​​Twoje połączenie do dedykowanej domeny aplikacji.

Jeśli masz wiele instancji Receiver, może to nadal działać z logiką odwzorowania w singleton i przekazywaniem pewnego rodzaju identyfikatora w wywołaniu zwrotnym.

Możesz znaleźć implementację here.

Powiązane problemy