【问题标题】:How do I force right AppDomain from native thread accessing managed code?如何从访问托管代码的本机线程强制使用正确的 AppDomain?
【发布时间】:2016-09-26 09:18:16
【问题描述】:

我有一个用 C# 编写的 MMC 管理单元。似乎 MMC 为每个托管管理单元创建了一个单独的 AppDomain。它还为 mscorlib.dll、Microsoft.ManagementConsole.dll 等托管系统 dll 提供了默认 AppDomain。

我的管理单元有一个本地 C++ dll,它创建可以通过互操作调用托管代码的本地线程。问题是当本机线程访问我的托管代码时,它会尝试在默认的 AppDomain 中执行此操作,而不是我的管理单元。

有没有办法强制本地线程“切换”到管理单元的 AppDomain?我无法重写本机 dll。我唯一能做的就是在这个 dll 将调用的 C++/CLI 中实现一些 C++ 接口。

下面是最小、完整和可验证的示例。要编译它,请在 Visual Studio 中选择 C++/CLR 控制台应用程序项目类型。

#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;
}

由于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

【问题讨论】:

    标签: c# .net c++-cli interop appdomain


    【解决方案1】:

    这可以按照这里建议的方式完成:

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

    总结一下解决方案:

    诀窍是你需要使用一个委托,它知道 它关联的 AppDomain,然后通过委托调用 将其转换为函数指针。这有效地编组了 在执行托管之前对正确 AppDomain 的非托管调用 c

    将解决方案应用于您的代码,它会按预期编译和执行:

    #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;
    }
    

    【讨论】:

    • 我以为我遇到了我见过的最糟糕的问题,我永远无法弄清楚如何解决它,直到我找到你的答案。我正在开发一个包含一些 C++ 非托管代码的 C# 插件。问题是非托管代码必须向外部驱动程序(基本上是 C dll)注册回调才能接收一些事件。 “注册”只是将静态 C++ 函数传递给 DLL,这导致回调在另一个未初始化的应用程序域(默认域)中调用。使用委托是要走的路,你帮了我很多,谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多