Marshalling C++ pointer interface back though C# f

2020-07-22 15:13发布

问题:

I have a working CLI interface between C++ and C# code. The code has a C++ abstract interface like:

-------------C++ Interface---------------
namespace cppns
{
   class cppInterface
   {
      public:
         virtual bool Start(const char *pcDir) = 0;
   };
}

------Implementation of abstract C++ interface in same dll---------
namespace cppns
{
   class cppimp : public cppInterface
   private:
       gcroot<MyInternalCSharpClass^> mInternalClassAccess;
   public:
       cppimp::cppimp()
       {
           mInternalClassAccess = gcnew MyInternalCSharpClass();
       }

       virtual bool cppimp::Start(const char *pcDir)
       {
           System::AppDomain ^appDom = AppDomain::CurrentDomain::get();
           System::String ^strDomainName = appDom->FriendlyName;

           mInternalClassAccess->Initalize(pcDir);
       }
}

---------Method to create an instance of the class in a factory--------------
cppns::cppInterface *GetImplObject()
{
    return new cppns::cppimp();
}

----------Factory class .h to allow C++ to get an instance of the cppimp class------
------The C++ code knows about the abstract interface by including the header file--
------FactoryExport is __declspec(dllexport) when compiled in dll and---------------
----- __declspec(dllimport) when used as a header file in exe that uses header------
class FactoryExport ClassFactory
{
    public:
       static cppns::cppInterface *CreateImpl();
};

----------Factory class .cpp to allow C++ to get an instance of the cppimp class------
cppns::cppInterface *ClassFactory::CreateImpl()
{
    return GetImplObject();
}

This code correctly allows me to call CreateImpl to get an implementation of the interface that contains the Start method. My issue is that I'm trying to force the whole CLR/.NET loading and executing into an AppDomain that is not the default AppDomain. I can create a secondary AppDomain using the following code:

   CComPtr<ICorRuntimeHost> pRuntimeHost;
   //Retrieve a pointer to the ICorRuntimeHost interface
   HRESULT hr = CorBindToRuntimeEx(
                L"v2.0.50727", //Retrieve last version before 4.0.
                // NULL, //Retrieve latest version by default
                L"wks",
                STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN | STARTUP_CONCURRENT_GC, 
                CLSID_CorRuntimeHost,
                IID_ICorRuntimeHost,
                (void**)&pRuntimeHost.p
                );

hr = pRuntimeHost->Start();

DWORD dwAppDomainId = 22;
WCHAR domainName[80 + 1];
    swprintf(domainName, 80, L"%s-%ld",L"NoDefaultDomain", dwAppDomainId);

CComPtr<IUnknown> pUnknownAppDomain;
hr = pRuntimeHost->CreateDomainEx(domainName, NULL, NULL, &pUnknownAppDomain);

CComPtr<_AppDomain> pAppDomain;
hr = pUnknownAppDomain->QueryInterface(__uuidof(_AppDomain), (VOID**)&pAppDomain.p);

BSTR bstrFriendlyName;
hr = pAppDomain->get_FriendlyName(&bstrFriendlyName);
if (SUCCEEDED(hr))
{
    _bstr_t bstrFriendlyNameWrap(bstrFriendlyName, false);
}

_bstr_t bstrAssemblyName("InteropCode");
CComPtr<_Assembly> pAssembly;
hr = pAppDomain->Load_2(bstrAssemblyName, &pAssembly);

BSTR bstrFullName;
hr = pAssembly->get_FullName(&bstrFullName);
if (SUCCEEDED(hr))
{
    _bstr_t bstrFullNameWrap(bstrFullName, false);
    std::cout << "Assembly name is: " << bstrFullNameWrap << "\n";
}

Every attempt of getting the factory to return to me an interface to cppns::cppInterface within this secondary application domain has failed. I have even attempted to create a secondary factory that is a C# class that returns the pointer to the implemented interface so that an Invoke call on the Assembly would hopefully cause the rest of the code to execute in the AppDomain that I loaded the Assembly into but the Invoke returns an IDispatch pointer that I can't seem to map back into any type of C++ pointer on my interface.

namespace cppns
{
    public ref class NetFactory
    {
    public:
        NetFactory()
        {
        }

        cppInterface *CreateInterop()
        {
            return GetImplObject();;
        }
    };
}

Is there another way to get everything to run in a secondary AppDomain or is the IDispatch pointer usable in calling the Start method?

回答1:

I have managed to get most of the .NET stuff running in another domain. It seems like there is no way to get the CLI layer to run in anything other than the default AppDomain.

To make this work I needed to make the class that sits within both appdomains derive from MarshalByRefObject. In my example above that meant I had to change MyInternalCSharpClass so that it derived from MarshalByRefObject. It was also nessary to made the objects sent and returned from MyInternalCSharpClass also derive from MarshalByRefObject. Finally I these same objects that were passed and returned had to have the [Serializable] property and to also mark all their private variables public. Note if the classes being transferred though the AppDomains are already using the Serializable attribute you can use [XmlIgnore] on each formally private variable to avoid changing the serialization that is being done.

Now that everything can be moved between the AppDomains I created a second AppDomain by doing the following:

bool CreateInstanceInAppDomain(const char *pcAppDomainName)
{
    bool bRtn = false;

    gcroot<String^> csStrAppDomainName (gcnew String(pcAppDomainName));
    mAppDomain = AppDomain::CreateDomain(csStrAppDomainName);
    delete csStrAppDomainName;
    Object^ MyInternalObject = mAppDomain->CreateInstanceAndUnwrap("AssemblyName", "ClassNameSpace.MyInternalCSharpClass");
    mInternalClassAccess = dynamic_cast<MyInternalCSharpClass^>(MyInternalObject);
    if (mInternalClassAccess)
    {
        bRtn = true;
    }

    return bRtn;
}