Reputation: 1764
I have a MMC snap-in written in C#. It seems that MMC creates a separate AppDomain for each managed snap-in. It also has a default AppDomain for managed system dlls like mscorlib.dll, Microsoft.ManagementConsole.dll, etc.
My snap-in has a native C++ dll that creates native threads that can call the managed code via Interop. The problem is when the native thread accesses my managed code it tries to do it within the default AppDomain, not my snap-in's one.
Is there a way to force the native thread "to switch" to snap-in's AppDomain? I cannot rewrite the native dll. The only thing I can do is implement some C++ interfaces in C++/CLI that this dll will call.
The Minimal, Complete, and Verifiable example is below. To compile it choose C++/CLR Console Application project type in 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;
}
It throws the following exception because of wrong 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
Upvotes: 3
Views: 1664
Reputation: 504
This could be done the way is suggested here:
http://www.lenholgate.com/blog/2009/07/error-cannot-pass-a-gchandle-across-appdomains.html
To summarize the solution:
The trick is that you need to use a delegate, which knows about the AppDomain that it relates to, and then call through the delegate by converting it to a function pointer. This effectively marshals the unmanaged call into the correct AppDomain before executing the managed c
Applying the solution to your code, it compiles and executes as expected:
#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;
}
Upvotes: 6