从C++中通过CLR hosting API调用.net dll

时间:2022-09-01 17:53:37
Use CLR4 Hosting API to invoke .NET assembly from native C++

The Common Language Runtime (CLR) allows a level of integration between itself and a host. This article is about a C++ code sample that demonstrates using the Hosting Interfaces of .NET Framework 4.0 to host a specific version of CLR in the process, load a .NET assembly, and invoke the types in the assembly. The code sample also demonstrates the new In-Process Side-by-Side feature in .NET Framework 4. The .NET Framework 4 runtime, and all future runtimes, are able to run in-process with one another. .NET Framework 4 runtime and beyond are also able to run in-process with any single older runtime. In ther words, you will be able to load 4, 5 and 2.0 in the same process, but you will not be able to load 1.1 and 2.0 in the same process. The code sample hosts .NET runtime 4.0 and 2.0 side by side, and loads a .NET 2.0 assembly into the two runtimes.

The code sample in this article (CppHostCLR) can be downloaded from Microsoft All-In-One Code Framework, which is a centralized code sample library.

The following steps walk through a demonstration of the CLR Hosting sample.

Step1. After you successfully build the sample project, and the dependent .NET class library projects (CSClassLibrary and CSNET2ClassLibrary) in Visual Studio 2010, you will get an application and two libraries: CppHostCLR.exe, CSClassLibrary.dll, and CSNET2ClassLibrary.dll. Make sure that the files are in the same folder.

Step2. Run the application in a command prompt. The application demonstrates the CLR In-Process Side-by-Side feature that is new in .NET 4 first. It hosts the .NET runtime 4.0 first and loads a .NET 2.0 assembly into the runtime to invoke its types. Then the application hosts the .NET runtime 2.0 side by side with the .NET runtime 4.0 and loads the same .NET 2.0 assembly into the .NET 4.0 runtime. If the operations succeed, the application prints the
following content in the console.

    Load and start the .NET runtime v4.0.30319
    Load the assembly CSNET2ClassLibrary
    Call CSNET2ClassLibrary.CSSimpleObject.GetStringLength("HelloWorld") => 10
    Call CSNET2ClassLibrary.CSSimpleObject.ToString() => 0.00

    Load and start the .NET runtime v2.0.50727
    Load the assembly CSNET2ClassLibrary
    Call CSNET2ClassLibrary.CSSimpleObject.GetStringLength("HelloWorld") => 10
    Call CSNET2ClassLibrary.CSSimpleObject.ToString() => 0.00

    Presss ENTER to continue ...

You can verify that both .NET 2.0 and .NET 4.0 runtimes are loaded by using Process Explorer (http://technet.microsoft.com/en-us/sysinternals/bb896653.aspx). In Lower Pane View / DLLs of the tool, you can see all modules loaded in the process. If clr.dll (the .NET 4 runtime module) and mscorwks.dll (the .NET 2 runtime module) are in the list, both .NET 2.0 and .NET 4.0 runtimes are loaded.

Step3. Press ENTER to continue. The application will host the .NET runtime 4.0 and use the ICLRRuntimeHost interface that was provided in .NET v2.0 to
load a .NET 4.0 assembly and invoke its type.

    Load and start the .NET runtime v4.0.30319
    Load the assembly CSClassLibrary.dll
    Call CSClassLibrary.CSSimpleObject.GetStringLength("HelloWorld") => 10

The key code in the code sample is attached below.

//
  //   FUNCTION: RuntimeHostV4Demo1(PCWSTR, PCWSTR)
  //
  //   PURPOSE: The function demonstrates using .NET Framework 4.0 Hosting 
  //   Interfaces to host a .NET runtime, and use the ICorRuntimeHost interface
  //   that was provided in .NET v1.x to load a .NET assembly and invoke its 
  //   type. 
  //   
  //   If the .NET runtime specified by the pszVersion parameter cannot be 
 //   loaded into the current process, the function prints ".NET runtime  cannot be loaded", and return.
 //   
 //   If the .NET runtime is successfully loaded, the function loads the 
 //   assembly identified by the pszAssemblyName parameter. Next, the function 
 //   instantiates the class (pszClassName) in the assembly, calls its 
 //   ToString() member method, and print the result. Last, the demo invokes 
 //   the public static function 'int GetStringLength(string str)' of the class 
 //   and print the result too.
 //
 //   PARAMETERS:
 //   * pszVersion - The desired DOTNETFX version, in the format “°vX.X.XXXXX”±. 
 //     The parameter must not be NULL. It’ˉs important to note that this 
 //     parameter should match exactly the directory names for each version of
 //     the framework, under C:/Windows/Microsoft.NET/Framework[64]. The 
 //     current possible values are "v1.0.3705", "v1.1.4322", "v2.0.50727" and 
 //     "v4.0.30319". Also, note that the “°v”± prefix is mandatory.
 //   * pszAssemblyName - The display name of the assembly to be loaded, such 
 //     as "CSClassLibrary". The ".DLL" file extension is not appended.
 //   * pszClassName - The name of the Type that defines the method to invoke.
 //
 //   RETURN VALUE: HRESULT of the demo.
 //
 HRESULT RuntimeHostV4Demo1(PCWSTR pszVersion, PCWSTR pszAssemblyName, 
     PCWSTR pszClassName)
 {
     HRESULT hr;
     ICLRMetaHost *pMetaHost = NULL;
     ICLRRuntimeInfo *pRuntimeInfo = NULL;
     // ICorRuntimeHost and ICLRRuntimeHost are the two CLR hosting interfaces
     // supported by CLR 4.0. Here we demo the ICorRuntimeHost interface that 
     // was provided in .NET v1.x, and is compatible with all .NET Frameworks. 
     ICorRuntimeHost *pCorRuntimeHost = NULL;
     IUnknownPtr spAppDomainThunk = NULL;
     _AppDomainPtr spDefaultAppDomain = NULL;
     // The .NET assembly to load.
     bstr_t bstrAssemblyName(pszAssemblyName);
     _AssemblyPtr spAssembly = NULL;
     // The .NET class to instantiate.
     bstr_t bstrClassName(pszClassName);
     _TypePtr spType = NULL;
     variant_t vtObject;
     variant_t vtEmpty;
     // The static method in the .NET class to invoke.
     bstr_t bstrStaticMethodName(L"GetStringLength");
     SAFEARRAY *psaStaticMethodArgs = NULL;
     variant_t vtStringArg(L"HelloWorld");
     variant_t vtLengthRet;
     // The instance method in the .NET class to invoke.
     bstr_t bstrMethodName(L"ToString");
     SAFEARRAY *psaMethodArgs = NULL;
     variant_t vtStringRet;
     // 
     // Load and start the .NET runtime.
     // 
     wprintf(L"Load and start the .NET runtime %s /n", pszVersion);
     hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
     if (FAILED(hr))
     {
         wprintf(L"CLRCreateInstance failed w/hr 0x%08lx/n", hr);
         goto Cleanup;
     }
     // Get the ICLRRuntimeInfo corresponding to a particular CLR version. It 
     // supersedes CorBindToRuntimeEx with STARTUP_LOADER_SAFEMODE.
     hr = pMetaHost->GetRuntime(pszVersion, IID_PPV_ARGS(&pRuntimeInfo));
     if (FAILED(hr))
     {
         wprintf(L"ICLRMetaHost::GetRuntime failed w/hr 0x%08lx/n", hr);
         goto Cleanup;
     }
     // Check if the specified runtime can be loaded into the process. This 
     // method will take into account other runtimes that may already be 
     // loaded into the process and set pbLoadable to TRUE if this runtime can 
     // be loaded in an in-process side-by-side fashion. 
     BOOL fLoadable;
     hr = pRuntimeInfo->IsLoadable(&fLoadable);
     if (FAILED(hr))
     {
         wprintf(L"ICLRRuntimeInfo::IsLoadable failed w/hr 0x%08lx/n", hr);
         goto Cleanup;
     }
     if (!fLoadable)
     {
         wprintf(L".NET runtime %s cannot be loaded/n", pszVersion);
         goto Cleanup;
     }
     // Load the CLR into the current process and return a runtime interface 
     // pointer. ICorRuntimeHost and ICLRRuntimeHost are the two CLR hosting  
     // interfaces supported by CLR 4.0. Here we demo the ICorRuntimeHost 
     // interface that was provided in .NET v1.x, and is compatible with all 
    // .NET Frameworks. 
    hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost,
         IID_PPV_ARGS(&pCorRuntimeHost));
    if (FAILED(hr))
    {
        wprintf(L"ICLRRuntimeInfo::GetInterface failed w/hr 0x%08lx/n", hr);
        goto Cleanup;
    }
    // Start the CLR.
    hr = pCorRuntimeHost->Start();
    if (FAILED(hr))
    {
        wprintf(L"CLR failed to start w/hr 0x%08lx/n", hr);
        goto Cleanup;
    }
    // 
    // Load the NET assembly. Call the static method GetStringLength of the 
    // class CSSimpleObject. Instantiate the class CSSimpleObject and call 
    // its instance method ToString.
    // 
    // The following C++ code does the same thing as this C# code:
    // 
    //   Assembly assembly = AppDomain.CurrentDomain.Load(pszAssemblyName);
    //   object length = type.InvokeMember("GetStringLength", 
    //       BindingFlags.InvokeMethod | BindingFlags.Static | 
    //       BindingFlags.Public, null, null, new object[] { "HelloWorld" });
    //   object obj = assembly.CreateInstance("CSClassLibrary.CSSimpleObject");
    //   object str = type.InvokeMember("ToString", 
    //       BindingFlags.InvokeMethod | BindingFlags.Instance | 
    //       BindingFlags.Public, null, obj, new object[] { });
    // Get a pointer to the default AppDomain in the CLR.
    hr = pCorRuntimeHost->GetDefaultDomain(&spAppDomainThunk);
    if (FAILED(hr))
    {
        wprintf(L"ICorRuntimeHost::GetDefaultDomain failed w/hr 0x%08lx/n", hr);
        goto Cleanup;
    }
    hr = spAppDomainThunk->QueryInterface(IID_PPV_ARGS(&spDefaultAppDomain));
    if (FAILED(hr))
    {
        wprintf(L"Failed to get default AppDomain w/hr 0x%08lx/n", hr);
        goto Cleanup;
    }
    // Load the .NET assembly.
    wprintf(L"Load the assembly %s/n", pszAssemblyName);
    hr = spDefaultAppDomain->Load_2(bstrAssemblyName, &spAssembly);
    if (FAILED(hr))
    {
        wprintf(L"Failed to load the assembly w/hr 0x%08lx/n", hr);
        goto Cleanup;
    }
    // Get the Type of CSSimpleObject.
    hr = spAssembly->GetType_2(bstrClassName, &spType);
    if (FAILED(hr))
    {
        wprintf(L"Failed to get the Type interface w/hr 0x%08lx/n", hr);
        goto Cleanup;
    }
    // Call the static method of the class: 
    //   public static int GetStringLength(string str);
    // Create a safe array to contain the arguments of the method. The safe 
    // array must be created with vt = VT_VARIANT because .NET reflection 
    // expects an array of Object - VT_VARIANT. There is only one argument, 
    // so cElements = 1.
    psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);
    LONG index = 0;
    hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtStringArg);
    if (FAILED(hr))
    {
        wprintf(L"SafeArrayPutElement failed w/hr 0x%08lx/n", hr);
        goto Cleanup;
    }
    // Invoke the "GetStringLength" method from the Type interface.
    hr = spType->InvokeMember_3(bstrStaticMethodName, static_cast(
        BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_Public), 
        NULL, vtEmpty, psaStaticMethodArgs, &vtLengthRet);
    if (FAILED(hr))
    {
        wprintf(L"Failed to invoke GetStringLength w/hr 0x%08lx/n", hr);
        goto Cleanup;
    }
    // Print the call result of the static method.
    wprintf(L"Call %s.%s(/"%s/") => %d/n", 
        static_cast(bstrClassName), 
        static_cast(bstrStaticMethodName), 
        static_cast(vtStringArg.bstrVal), 
        vtLengthRet.lVal);
    // Instantiate the class.
    hr = spAssembly->CreateInstance(bstrClassName, &vtObject);
    if (FAILED(hr))
    {
        wprintf(L"Assembly::CreateInstance failed w/hr 0x%08lx/n", hr);
        goto Cleanup;
    }
    // Call the instance method of the class.
    //   public string ToString();
    // Create a safe array to contain the arguments of the method.
    psaMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0);
    // Invoke the "ToString" method from the Type interface.
    hr = spType->InvokeMember_3(bstrMethodName, static_cast(
        BindingFlags_InvokeMethod | BindingFlags_Instance | BindingFlags_Public),
        NULL, vtObject, psaMethodArgs, &vtStringRet);
    if (FAILED(hr))
    {
        wprintf(L"Failed to invoke ToString w/hr 0x%08lx/n", hr);
        goto Cleanup;
    }
    // Print the call result of the method.
    wprintf(L"Call %s.%s() => %s/n", 
        static_cast(bstrClassName), 
        static_cast(bstrMethodName), 
        static_cast(vtStringRet.bstrVal));
Cleanup:
    if (pMetaHost)
    {
        pMetaHost->Release();
        pMetaHost = NULL;
    }
    if (pRuntimeInfo)
    {
        pRuntimeInfo->Release();
        pRuntimeInfo = NULL;
    }
    if (pCorRuntimeHost)
    {
        // Please note that after a call to Stop, the CLR cannot be 
        // reinitialized into the same process. This step is usually not 
        // necessary. You can leave the .NET runtime loaded in your process.
        //wprintf(L"Stop the .NET runtime/n");
        //pCorRuntimeHost->Stop();
        pCorRuntimeHost->Release();
        pCorRuntimeHost = NULL;
    }
    if (psaStaticMethodArgs)
    {
        SafeArrayDestroy(psaStaticMethodArgs);
        psaStaticMethodArgs = NULL;
    }
    if (psaMethodArgs)
    {
        SafeArrayDestroy(psaMethodArgs);
        psaMethodArgs = NULL;
    }
    return hr;
}

//
//   FUNCTION: RuntimeHostV4Demo2(PCWSTR, PCWSTR)
//
//   PURPOSE: The function demonstrates using .NET Framework 4.0 Hosting 
//   Interfaces to host a .NET runtime, and use the ICLRRuntimeHost interface
//   that was provided in .NET v2.0 to load a .NET assembly and invoke its 
//   type. Because ICLRRuntimeHost is not compatible with .NET runtime v1.x, 
//   the requested runtime must not be v1.x.
//   
//   If the .NET runtime specified by the pszVersion parameter cannot be 
//   loaded into the current process, the function prints ".NET runtime  cannot be loaded", and return.
//   
//   If the .NET runtime is successfully loaded, the function loads the 
//   assembly identified by the pszAssemblyName parameter. Next, the function 
//   invokes the public static function 'int GetStringLength(string str)' of 
//   the class and print the result.
//
//   PARAMETERS:
//   * pszVersion - The desired DOTNETFX version, in the format “°vX.X.XXXXX”±. 
//     The parameter must not be NULL. It’ˉs important to note that this 
//     parameter should match exactly the directory names for each version of
//     the framework, under C:/Windows/Microsoft.NET/Framework[64]. Because 
//     the ICLRRuntimeHost interface does not support the .NET v1.x runtimes, 
//     the current possible values of the parameter are "v2.0.50727" and 
//     "v4.0.30319". Also, note that the “°v”± prefix is mandatory.
//   * pszAssemblyPath - The path to the Assembly to be loaded.
//   * pszClassName - The name of the Type that defines the method to invoke.
//
//   RETURN VALUE: HRESULT of the demo.
//
HRESULT RuntimeHostV4Demo2(PCWSTR pszVersion, PCWSTR pszAssemblyPath, 
    PCWSTR pszClassName)
{
    HRESULT hr;
    ICLRMetaHost *pMetaHost = NULL;
    ICLRRuntimeInfo *pRuntimeInfo = NULL;
    // ICorRuntimeHost and ICLRRuntimeHost are the two CLR hosting interfaces
    // supported by CLR 4.0. Here we demo the ICLRRuntimeHost interface that 
    // was provided in .NET v2.0 to support CLR 2.0 new features. 
    // ICLRRuntimeHost does not support loading the .NET v1.x runtimes.
    ICLRRuntimeHost *pClrRuntimeHost = NULL;
    // The static method in the .NET class to invoke.
    PCWSTR pszStaticMethodName = L"GetStringLength";
    PCWSTR pszStringArg = L"HelloWorld";
    DWORD dwLengthRet;
    // 
    // Load and start the .NET runtime.
    // 
    wprintf(L"Load and start the .NET runtime %s /n", pszVersion);
    hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
    if (FAILED(hr))
    {
        wprintf(L"CLRCreateInstance failed w/hr 0x%08lx/n", hr);
        goto Cleanup;
    }
    // Get the ICLRRuntimeInfo corresponding to a particular CLR version. It 
    // supersedes CorBindToRuntimeEx with STARTUP_LOADER_SAFEMODE.
    hr = pMetaHost->GetRuntime(pszVersion, IID_PPV_ARGS(&pRuntimeInfo));
    if (FAILED(hr))
    {
        wprintf(L"ICLRMetaHost::GetRuntime failed w/hr 0x%08lx/n", hr);
        goto Cleanup;
    }
    // Check if the specified runtime can be loaded into the process. This 
    // method will take into account other runtimes that may already be 
    // loaded into the process and set pbLoadable to TRUE if this runtime can 
    // be loaded in an in-process side-by-side fashion. 
    BOOL fLoadable;
    hr = pRuntimeInfo->IsLoadable(&fLoadable);
    if (FAILED(hr))
    {
        wprintf(L"ICLRRuntimeInfo::IsLoadable failed w/hr 0x%08lx/n", hr);
        goto Cleanup;
    }
    if (!fLoadable)
    {
        wprintf(L".NET runtime %s cannot be loaded/n", pszVersion);
        goto Cleanup;
    }
    // Load the CLR into the current process and return a runtime interface 
    // pointer. ICorRuntimeHost and ICLRRuntimeHost are the two CLR hosting  
    // interfaces supported by CLR 4.0. Here we demo the ICLRRuntimeHost 
    // interface that was provided in .NET v2.0 to support CLR 2.0 new 
    // features. ICLRRuntimeHost does not support loading the .NET v1.x 
    // runtimes.
    hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, 
        IID_PPV_ARGS(&pClrRuntimeHost));
    if (FAILED(hr))
    {
        wprintf(L"ICLRRuntimeInfo::GetInterface failed w/hr 0x%08lx/n", hr);
        goto Cleanup;
    }
    // Start the CLR.
    hr = pClrRuntimeHost->Start();
    if (FAILED(hr))
    {
        wprintf(L"CLR failed to start w/hr 0x%08lx/n", hr);
        goto Cleanup;
    }
    // 
    // Load the NET assembly and call the static method GetStringLength of 
    // the type CSSimpleObject in the assembly.
    // 
    wprintf(L"Load the assembly %s/n", pszAssemblyPath);
    // The invoked method of ExecuteInDefaultAppDomain must have the 
    // following signature: static int pwzMethodName (String pwzArgument)
    // where pwzMethodName represents the name of the invoked method, and 
    // pwzArgument represents the string value passed as a parameter to that 
    // method. If the HRESULT return value of ExecuteInDefaultAppDomain is 
    // set to S_OK, pReturnValue is set to the integer value returned by the 
    // invoked method. Otherwise, pReturnValue is not set.
    hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(pszAssemblyPath, 
        pszClassName, pszStaticMethodName, pszStringArg, &dwLengthRet);
    if (FAILED(hr))
    {
        wprintf(L"Failed to call GetStringLength w/hr 0x%08lx/n", hr);
        goto Cleanup;
    }
    // Print the call result of the static method.
    wprintf(L"Call %s.%s(/"%s/") => %d/n", pszClassName, pszStaticMethodName, 
        pszStringArg, dwLengthRet);
Cleanup:
    if (pMetaHost)
    {
        pMetaHost->Release();
        pMetaHost = NULL;
    }
    if (pRuntimeInfo)
    {
        pRuntimeInfo->Release();
        pRuntimeInfo = NULL;
    }
    if (pClrRuntimeHost)
    {
        // Please note that after a call to Stop, the CLR cannot be 
        // reinitialized into the same process. This step is usually not 
        // necessary. You can leave the .NET runtime loaded in your process.
        //wprintf(L"Stop the .NET runtime/n");
        //pClrRuntimeHost->Stop();
        pClrRuntimeHost->Release();
        pClrRuntimeHost = NULL;
    }
    return hr;
}