CLR寄宿——C++调用C# dll

在看到CLR寄宿的内容的时候,研究了一下非托管C++启用CLR的过程。在MSDN sample gallary中看到了一个很好的例子。直接把代码贴到下面(略作了一些修改)。生怕以后找不到了。另外,从C++传送到C#方法的参数只试验过int, double, wchar_t*(字符串)。其他的没有做过实验。

C++代码

//

// FUNCTION: RuntimeHostV4Demo1(PCWSTR, PCWSTR)

//

// Invoking way: RuntimeHostV4Demo1(L"v4.0.30319", L"CSNET2ClassLibrary", L"CSNET2ClassLibrary.CSSimpleObject");

//

// 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 <the

// runtime version> 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 vtStringArg1(L"HelloWorld");

variant_t vtStringArg2(18);

variant_t vtStringArg3(123.321);

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", 18, 123.321 });

// 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, 3);

LONG index = 0;

hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtStringArg1);

index = 1;

hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtStringArg2);

index = 2;

hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtStringArg3);

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>(

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<PCWSTR>(bstrClassName),

static_cast<PCWSTR>(bstrStaticMethodName),

static_cast<PCWSTR>(vtStringArg1.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>(

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<PCWSTR>(bstrClassName),

static_cast<PCWSTR>(bstrMethodName),

static_cast<PCWSTR>(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; }

C#代码

namespace CSNET2ClassLibrary

{

public class CSSimpleObject

{

/// <summary>

/// Constructor

/// </summary>

public CSSimpleObject()

{

}

private float fField = 0F;

/// <summary>

/// This is a public Property. It allows you to get and set the value

/// of a float field.

/// </summary>

public float FloatProperty

{

get { return fField; }

set

{

// Fire the event FloatPropertyChanging

bool cancel = false;

if (FloatPropertyChanging != null)

{

FloatPropertyChanging(value, out cancel);

}

// If the change is not canceled, make the change.

if (!cancel)

{

fField = value;

}

}

}

/// <summary>

/// Returns a String that represents the current Object. Here, we

/// return the string form of the float field fField.

/// </summary>

/// <returns>the string form of the float field fField.</returns>

public override string ToString()

{

return this.fField.ToString("F2");

}

/// <summary>

/// This is a public static method. It returns the number of

/// characters in a string.

/// </summary>

/// <param name="str">a string</param>

/// <returns>the number of characters in the string</returns>

public static int GetStringLength(string str, string length)

{

return (str.Length + Int32.Parse(length));

}

public static int GetStringLength(string str, int length, double somevalue)

{

Console.WriteLine(str + " " + length.ToString() + " " + somevalue.ToString() );

return (str.Length + length + (int)somevalue);

}

/// <summary>

/// This is an event. The event is fired when the float property is

/// set.

/// </summary>

public event PropertyChangingEventHandler FloatPropertyChanging;

}

/// <summary>

/// Property value changing event handler

/// </summary>

/// <param name="NewValue">the new value of the property</param>

/// <param name="Cancel">

/// Output whether the change should be cancelled or not.

/// </param>

public delegate void PropertyChangingEventHandler(object NewValue, out bool Cancel); }