user10101
user10101

Reputation: 1764

How do I force right AppDomain from native thread accessing managed code?

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

Answers (1)

GeorgeT
GeorgeT

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

Related Questions