2016-09-26 4 views
0

J'ai un composant logiciel enfichable MMC écrit en C#. Il semble que MMC crée un AppDomain distinct pour chaque composant logiciel enfichable géré. Il a également un AppDomain par défaut pour les DLL système gérés comme mscorlib.dll, Microsoft.ManagementConsole.dll, etc.Comment puis-je forcer AppDomain à partir du thread natif à accéder au code managé?

Mon composant logiciel enfichable possède une DLL C++ native qui crée des threads natifs pouvant appeler le code managé via Interop. Le problème est lorsque le thread natif accède à mon code managé, il essaie de le faire dans le domaine par défaut AppDomain, pas celui de mon composant logiciel enfichable.

Existe-t-il un moyen de forcer le thread natif à «basculer» vers AppDomain du composant logiciel enfichable? Je ne peux pas réécrire la DLL native. La seule chose que je peux faire est d'implémenter des interfaces C++ en C++/CLI que cette DLL appellera.

L'exemple minimal, complet et vérifiable est ci-dessous. Pour le compiler, choisissez le type de projet C++/CLR Console Application dans Visual Studio.

#include <Windows.h> 
#include <msclr/gcroot.h> 

using namespace System; 

#pragma unmanaged 

class IService 
{ 
public: 
    virtual void Operate() = 0; 
}; 

DWORD __stdcall MyNativeThread(LPVOID arg) 
{ 
    IService* service = (IService*)arg; 

    service->Operate(); 

    return 0; 
} 

void StartNativeThread(IService* service) 
{ 
    CloseHandle(CreateThread(NULL, 0, &MyNativeThread, service, 0, NULL)); 
} 

#pragma managed 

public ref class ServiceManagedImpl 
{ 
public: 
    void Operate() 
    { 
     System::Console::WriteLine("ServiceManagedImpl::Operate: Domain: {0}", System::AppDomain::CurrentDomain->Id); 
    } 
}; 

class ServiceImpl : public IService 
{ 
public: 
    ServiceImpl(ServiceManagedImpl^ managedImpl) 
    { 
     m_managedImpl = managedImpl; 
    } 

    void Operate() override 
    { 
     m_managedImpl->Operate(); 
    } 

private: 
    msclr::gcroot<ServiceManagedImpl^> m_managedImpl; 
}; 

public ref class MyMmcSnapIn : MarshalByRefObject 
{ 
public: 
    MyMmcSnapIn() 
    { 
     System::Console::WriteLine("MyMmcSnapIn.ctor: Domain: {0}", AppDomain::CurrentDomain->Id); 

     ServiceImpl testImpl = ServiceImpl(gcnew ServiceManagedImpl()); 

     StartNativeThread(&testImpl); 

     Threading::Thread::Sleep(10000); 
    } 
}; 

int main() 
{ 
    Console::WriteLine(L"Main: Domain: {0}", AppDomain::CurrentDomain->Id); 

    AppDomain^ mmcSnapInAppDomain = AppDomain::CreateDomain("AppDomainForMyMmcSnapIn"); 

    // direct instantiation works as expected 
    // gcnew MyMmcSnapIn(); 

    String^ entryAssemblyLocation = Reflection::Assembly::GetEntryAssembly()->Location; 
    mmcSnapInAppDomain->CreateInstanceFrom(entryAssemblyLocation, "MyMmcSnapIn"); 

    return 0; 
} 

Il jette l'exception suivante en raison du mauvais AppDomain:

Exception type: System.ArgumentException 
Message:   Cannot pass a GCHandle across AppDomains. 
InnerException: <none> 
StackTrace (generated): 
    SP  IP  Function 
    00000000 00000001 mscorlib_ni!System.Runtime.InteropServices.GCHandle.InternalCheckDomain(IntPtr)+0x2 
    01DBFA9C 71FA20C4 mscorlib_ni!System.Runtime.InteropServices.GCHandle.FromIntPtr(IntPtr)+0x34 
    01DBFAAC 72721151 mscorlib_ni!System.Runtime.InteropServices.GCHandle.op_Explicit(IntPtr)+0x1d 
    01DBFAB4 00361F16 ConsoleApplication15!<Module>.msclr.gcroot<ServiceManagedImpl ^>.->(msclr.gcroot<ServiceManagedImpl ^>*)+0x36 
    01DBFAD4 00361EB8 ConsoleApplication15!<Module>.ServiceImpl.Operate(ServiceImpl*)+0x28 

Répondre

3

Cela pourrait se faire le chemin est suggéré ici:

http://www.lenholgate.com/blog/2009/07/error-cannot-pass-a-gchandle-across-appdomains.html

Pour résumer la solution:

L'astuce est que vous devez utiliser un délégué, qui connaît le AppDomain auquel il se rapporte, puis appeler le délégué par en le convertissant en un pointeur de fonction. Ce maréchaux efficacement le appel non géré dans le AppDomain correct avant d'exécuter la gestion c

application de la solution à votre code, il compile et exécute comme prévu:

#include "stdafx.h" 
#include <Windows.h> 
#include <msclr/gcroot.h> 

using namespace System; 
using namespace System::Runtime::InteropServices; 

#pragma unmanaged 

class IService 
{ 
public: 
    virtual void Operate() = 0; 
}; 

DWORD __stdcall MyNativeThread(LPVOID arg) 
{ 
    IService* service = (IService*)arg; 

    service->Operate(); 

    return 0; 
} 

void StartNativeThread(IService* service) 
{ 
    CloseHandle(CreateThread(NULL, 0, &MyNativeThread, service, 0, NULL)); 
} 

#pragma managed 

typedef void (__stdcall ConnectFnc)(); 

public ref class ServiceManagedImpl 
{ 
public: 
    ServiceManagedImpl() 
    { 
     m_OperateDelegate = gcnew Delegate(this, &ServiceManagedImpl::Operate); 
    } 
    ConnectFnc *GetDelegateFunctionPointer() 
    { 
     return (ConnectFnc*)(Marshal::GetFunctionPointerForDelegate(m_OperateDelegate).ToPointer()); 
    } 

public: 
    void Operate() 
    { 
     System::Console::WriteLine("ServiceManagedImpl::Operate: Domain: {0}", System::AppDomain::CurrentDomain->Id); 
    } 
private: 
    delegate void Delegate(); 
    Delegate ^m_OperateDelegate; 
}; 

class ServiceImpl : public IService 
{ 
public: 
    ServiceImpl(ServiceManagedImpl^ managedImpl) 
    { 
     m_managedImpl = new msclr::gcroot<ServiceManagedImpl^>(managedImpl); 
     m_pFunction = (*m_managedImpl)->GetDelegateFunctionPointer(); 
    } 
    ~ServiceImpl() 
    { 
     delete m_managedImpl; 
    } 
    void operator()() const 
    { 
     m_pFunction(); 
    } 

    virtual void Operate() override 
    { 
     m_pFunction(); 
    } 

private: 
    msclr::gcroot<ServiceManagedImpl^> *m_managedImpl; 
    ConnectFnc *m_pFunction; 
}; 

public ref class MyMmcSnapIn : MarshalByRefObject 
{ 
public: 
    MyMmcSnapIn() 
    { 
     System::Console::WriteLine("MyMmcSnapIn.ctor: Domain: {0}", AppDomain::CurrentDomain->Id); 

     ServiceImpl testImpl = ServiceImpl(gcnew ServiceManagedImpl()); 

     StartNativeThread(&testImpl); 

     Threading::Thread::Sleep(10000); 
    } 
}; 

int main() 
{ 
    Console::WriteLine(L"Main: Domain: {0}", AppDomain::CurrentDomain->Id); 

    AppDomain^ mmcSnapInAppDomain = AppDomain::CreateDomain("AppDomainForMyMmcSnapIn"); 

    // direct instantiation works as expected 
    // gcnew MyMmcSnapIn(); 

    String^ entryAssemblyLocation = Reflection::Assembly::GetEntryAssembly()->Location; 
    mmcSnapInAppDomain->CreateInstanceFrom(entryAssemblyLocation, "MyMmcSnapIn"); 

    return 0; 
}