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 (https://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.
1: //
2: // FUNCTION: RuntimeHostV4Demo1(PCWSTR, PCWSTR)
3: //
4: // PURPOSE: The function demonstrates using .NET Framework 4.0 Hosting
5: // Interfaces to host a .NET runtime, and use the ICorRuntimeHost interface
6: // that was provided in .NET v1.x to load a .NET assembly and invoke its
7: // type.
8: //
9: // If the .NET runtime specified by the pszVersion parameter cannot be
10: // loaded into the current process, the function prints ".NET runtime <the
11: // runtime version> cannot be loaded", and return.
12: //
13: // If the .NET runtime is successfully loaded, the function loads the
14: // assembly identified by the pszAssemblyName parameter. Next, the function
15: // instantiates the class (pszClassName) in the assembly, calls its
16: // ToString() member method, and print the result. Last, the demo invokes
17: // the public static function 'int GetStringLength(string str)' of the class
18: // and print the result too.
19: //
20: // PARAMETERS:
21: // * pszVersion - The desired DOTNETFX version, in the format “vX.X.XXXXX”.
22: // The parameter must not be NULL. It’s important to note that this
23: // parameter should match exactly the directory names for each version of
24: // the framework, under C:\Windows\Microsoft.NET\Framework[64]. The
25: // current possible values are "v1.0.3705", "v1.1.4322", "v2.0.50727" and
26: // "v4.0.30319". Also, note that the “v” prefix is mandatory.
27: // * pszAssemblyName - The display name of the assembly to be loaded, such
28: // as "CSClassLibrary". The ".DLL" file extension is not appended.
29: // * pszClassName - The name of the Type that defines the method to invoke.
30: //
31: // RETURN VALUE: HRESULT of the demo.
32: //
33: HRESULT RuntimeHostV4Demo1(PCWSTR pszVersion, PCWSTR pszAssemblyName,
34: PCWSTR pszClassName)
35: {
36: HRESULT hr;
37: ICLRMetaHost *pMetaHost = NULL;
38: ICLRRuntimeInfo *pRuntimeInfo = NULL;
39: // ICorRuntimeHost and ICLRRuntimeHost are the two CLR hosting interfaces
40: // supported by CLR 4.0. Here we demo the ICorRuntimeHost interface that
41: // was provided in .NET v1.x, and is compatible with all .NET Frameworks.
42: ICorRuntimeHost *pCorRuntimeHost = NULL;
43: IUnknownPtr spAppDomainThunk = NULL;
44: _AppDomainPtr spDefaultAppDomain = NULL;
45: // The .NET assembly to load.
46: bstr_t bstrAssemblyName(pszAssemblyName);
47: _AssemblyPtr spAssembly = NULL;
48: // The .NET class to instantiate.
49: bstr_t bstrClassName(pszClassName);
50: _TypePtr spType = NULL;
51: variant_t vtObject;
52: variant_t vtEmpty;
53: // The static method in the .NET class to invoke.
54: bstr_t bstrStaticMethodName(L"GetStringLength");
55: SAFEARRAY *psaStaticMethodArgs = NULL;
56: variant_t vtStringArg(L"HelloWorld");
57: variant_t vtLengthRet;
58: // The instance method in the .NET class to invoke.
59: bstr_t bstrMethodName(L"ToString");
60: SAFEARRAY *psaMethodArgs = NULL;
61: variant_t vtStringRet;
62: //
63: // Load and start the .NET runtime.
64: //
65: wprintf(L"Load and start the .NET runtime %s \n", pszVersion);
66: hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
67: if (FAILED(hr))
68: {
69: wprintf(L"CLRCreateInstance failed w/hr 0x%08lx\n", hr);
70: goto Cleanup;
71: }
72: // Get the ICLRRuntimeInfo corresponding to a particular CLR version. It
73: // supersedes CorBindToRuntimeEx with STARTUP_LOADER_SAFEMODE.
74: hr = pMetaHost->GetRuntime(pszVersion, IID_PPV_ARGS(&pRuntimeInfo));
75: if (FAILED(hr))
76: {
77: wprintf(L"ICLRMetaHost::GetRuntime failed w/hr 0x%08lx\n", hr);
78: goto Cleanup;
79: }
80: // Check if the specified runtime can be loaded into the process. This
81: // method will take into account other runtimes that may already be
82: // loaded into the process and set pbLoadable to TRUE if this runtime can
83: // be loaded in an in-process side-by-side fashion.
84: BOOL fLoadable;
85: hr = pRuntimeInfo->IsLoadable(&fLoadable);
86: if (FAILED(hr))
87: {
88: wprintf(L"ICLRRuntimeInfo::IsLoadable failed w/hr 0x%08lx\n", hr);
89: goto Cleanup;
90: }
91: if (!fLoadable)
92: {
93: wprintf(L".NET runtime %s cannot be loaded\n", pszVersion);
94: goto Cleanup;
95: }
96: // Load the CLR into the current process and return a runtime interface
97: // pointer. ICorRuntimeHost and ICLRRuntimeHost are the two CLR hosting
98: // interfaces supported by CLR 4.0. Here we demo the ICorRuntimeHost
99: // interface that was provided in .NET v1.x, and is compatible with all
100: // .NET Frameworks.
101: hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost,
102: IID_PPV_ARGS(&pCorRuntimeHost));
103: if (FAILED(hr))
104: {
105: wprintf(L"ICLRRuntimeInfo::GetInterface failed w/hr 0x%08lx\n", hr);
106: goto Cleanup;
107: }
108: // Start the CLR.
109: hr = pCorRuntimeHost->Start();
110: if (FAILED(hr))
111: {
112: wprintf(L"CLR failed to start w/hr 0x%08lx\n", hr);
113: goto Cleanup;
114: }
115: //
116: // Load the NET assembly. Call the static method GetStringLength of the
117: // class CSSimpleObject. Instantiate the class CSSimpleObject and call
118: // its instance method ToString.
119: //
120: // The following C++ code does the same thing as this C# code:
121: //
122: // Assembly assembly = AppDomain.CurrentDomain.Load(pszAssemblyName);
123: // object length = type.InvokeMember("GetStringLength",
124: // BindingFlags.InvokeMethod | BindingFlags.Static |
125: // BindingFlags.Public, null, null, new object[] { "HelloWorld" });
126: // object obj = assembly.CreateInstance("CSClassLibrary.CSSimpleObject");
127: // object str = type.InvokeMember("ToString",
128: // BindingFlags.InvokeMethod | BindingFlags.Instance |
129: // BindingFlags.Public, null, obj, new object[] { });
130: // Get a pointer to the default AppDomain in the CLR.
131: hr = pCorRuntimeHost->GetDefaultDomain(&spAppDomainThunk);
132: if (FAILED(hr))
133: {
134: wprintf(L"ICorRuntimeHost::GetDefaultDomain failed w/hr 0x%08lx\n", hr);
135: goto Cleanup;
136: }
137: hr = spAppDomainThunk->QueryInterface(IID_PPV_ARGS(&spDefaultAppDomain));
138: if (FAILED(hr))
139: {
140: wprintf(L"Failed to get default AppDomain w/hr 0x%08lx\n", hr);
141: goto Cleanup;
142: }
143: // Load the .NET assembly.
144: wprintf(L"Load the assembly %s\n", pszAssemblyName);
145: hr = spDefaultAppDomain->Load_2(bstrAssemblyName, &spAssembly);
146: if (FAILED(hr))
147: {
148: wprintf(L"Failed to load the assembly w/hr 0x%08lx\n", hr);
149: goto Cleanup;
150: }
151: // Get the Type of CSSimpleObject.
152: hr = spAssembly->GetType_2(bstrClassName, &spType);
153: if (FAILED(hr))
154: {
155: wprintf(L"Failed to get the Type interface w/hr 0x%08lx\n", hr);
156: goto Cleanup;
157: }
158: // Call the static method of the class:
159: // public static int GetStringLength(string str);
160: // Create a safe array to contain the arguments of the method. The safe
161: // array must be created with vt = VT_VARIANT because .NET reflection
162: // expects an array of Object - VT_VARIANT. There is only one argument,
163: // so cElements = 1.
164: psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);
165: LONG index = 0;
166: hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtStringArg);
167: if (FAILED(hr))
168: {
169: wprintf(L"SafeArrayPutElement failed w/hr 0x%08lx\n", hr);
170: goto Cleanup;
171: }
172: // Invoke the "GetStringLength" method from the Type interface.
173: hr = spType->InvokeMember_3(bstrStaticMethodName, static_cast<BindingFlags>(
174: BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_Public),
175: NULL, vtEmpty, psaStaticMethodArgs, &vtLengthRet);
176: if (FAILED(hr))
177: {
178: wprintf(L"Failed to invoke GetStringLength w/hr 0x%08lx\n", hr);
179: goto Cleanup;
180: }
181: // Print the call result of the static method.
182: wprintf(L"Call %s.%s(\"%s\") => %d\n",
183: static_cast<PCWSTR>(bstrClassName),
184: static_cast<PCWSTR>(bstrStaticMethodName),
185: static_cast<PCWSTR>(vtStringArg.bstrVal),
186: vtLengthRet.lVal);
187: // Instantiate the class.
188: hr = spAssembly->CreateInstance(bstrClassName, &vtObject);
189: if (FAILED(hr))
190: {
191: wprintf(L"Assembly::CreateInstance failed w/hr 0x%08lx\n", hr);
192: goto Cleanup;
193: }
194: // Call the instance method of the class.
195: // public string ToString();
196: // Create a safe array to contain the arguments of the method.
197: psaMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0);
198: // Invoke the "ToString" method from the Type interface.
199: hr = spType->InvokeMember_3(bstrMethodName, static_cast<BindingFlags>(
200: BindingFlags_InvokeMethod | BindingFlags_Instance | BindingFlags_Public),
201: NULL, vtObject, psaMethodArgs, &vtStringRet);
202: if (FAILED(hr))
203: {
204: wprintf(L"Failed to invoke ToString w/hr 0x%08lx\n", hr);
205: goto Cleanup;
206: }
207: // Print the call result of the method.
208: wprintf(L"Call %s.%s() => %s\n",
209: static_cast<PCWSTR>(bstrClassName),
210: static_cast<PCWSTR>(bstrMethodName),
211: static_cast<PCWSTR>(vtStringRet.bstrVal));
212: Cleanup:
213: if (pMetaHost)
214: {
215: pMetaHost->Release();
216: pMetaHost = NULL;
217: }
218: if (pRuntimeInfo)
219: {
220: pRuntimeInfo->Release();
221: pRuntimeInfo = NULL;
222: }
223: if (pCorRuntimeHost)
224: {
225: // Please note that after a call to Stop, the CLR cannot be
226: // reinitialized into the same process. This step is usually not
227: // necessary. You can leave the .NET runtime loaded in your process.
228: //wprintf(L"Stop the .NET runtime\n");
229: //pCorRuntimeHost->Stop();
230: pCorRuntimeHost->Release();
231: pCorRuntimeHost = NULL;
232: }
233: if (psaStaticMethodArgs)
234: {
235: SafeArrayDestroy(psaStaticMethodArgs);
236: psaStaticMethodArgs = NULL;
237: }
238: if (psaMethodArgs)
239: {
240: SafeArrayDestroy(psaMethodArgs);
241: psaMethodArgs = NULL;
242: }
243: return hr;
244: }
245:
246: //
247: // FUNCTION: RuntimeHostV4Demo2(PCWSTR, PCWSTR)
248: //
249: // PURPOSE: The function demonstrates using .NET Framework 4.0 Hosting
250: // Interfaces to host a .NET runtime, and use the ICLRRuntimeHost interface
251: // that was provided in .NET v2.0 to load a .NET assembly and invoke its
252: // type. Because ICLRRuntimeHost is not compatible with .NET runtime v1.x,
253: // the requested runtime must not be v1.x.
254: //
255: // If the .NET runtime specified by the pszVersion parameter cannot be
256: // loaded into the current process, the function prints ".NET runtime <the
257: // runtime version> cannot be loaded", and return.
258: //
259: // If the .NET runtime is successfully loaded, the function loads the
260: // assembly identified by the pszAssemblyName parameter. Next, the function
261: // invokes the public static function 'int GetStringLength(string str)' of
262: // the class and print the result.
263: //
264: // PARAMETERS:
265: // * pszVersion - The desired DOTNETFX version, in the format “vX.X.XXXXX”.
266: // The parameter must not be NULL. It’s important to note that this
267: // parameter should match exactly the directory names for each version of
268: // the framework, under C:\Windows\Microsoft.NET\Framework[64]. Because
269: // the ICLRRuntimeHost interface does not support the .NET v1.x runtimes,
270: // the current possible values of the parameter are "v2.0.50727" and
271: // "v4.0.30319". Also, note that the “v” prefix is mandatory.
272: // * pszAssemblyPath - The path to the Assembly to be loaded.
273: // * pszClassName - The name of the Type that defines the method to invoke.
274: //
275: // RETURN VALUE: HRESULT of the demo.
276: //
277: HRESULT RuntimeHostV4Demo2(PCWSTR pszVersion, PCWSTR pszAssemblyPath,
278: PCWSTR pszClassName)
279: {
280: HRESULT hr;
281: ICLRMetaHost *pMetaHost = NULL;
282: ICLRRuntimeInfo *pRuntimeInfo = NULL;
283: // ICorRuntimeHost and ICLRRuntimeHost are the two CLR hosting interfaces
284: // supported by CLR 4.0. Here we demo the ICLRRuntimeHost interface that
285: // was provided in .NET v2.0 to support CLR 2.0 new features.
286: // ICLRRuntimeHost does not support loading the .NET v1.x runtimes.
287: ICLRRuntimeHost *pClrRuntimeHost = NULL;
288: // The static method in the .NET class to invoke.
289: PCWSTR pszStaticMethodName = L"GetStringLength";
290: PCWSTR pszStringArg = L"HelloWorld";
291: DWORD dwLengthRet;
292: //
293: // Load and start the .NET runtime.
294: //
295: wprintf(L"Load and start the .NET runtime %s \n", pszVersion);
296: hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
297: if (FAILED(hr))
298: {
299: wprintf(L"CLRCreateInstance failed w/hr 0x%08lx\n", hr);
300: goto Cleanup;
301: }
302: // Get the ICLRRuntimeInfo corresponding to a particular CLR version. It
303: // supersedes CorBindToRuntimeEx with STARTUP_LOADER_SAFEMODE.
304: hr = pMetaHost->GetRuntime(pszVersion, IID_PPV_ARGS(&pRuntimeInfo));
305: if (FAILED(hr))
306: {
307: wprintf(L"ICLRMetaHost::GetRuntime failed w/hr 0x%08lx\n", hr);
308: goto Cleanup;
309: }
310: // Check if the specified runtime can be loaded into the process. This
311: // method will take into account other runtimes that may already be
312: // loaded into the process and set pbLoadable to TRUE if this runtime can
313: // be loaded in an in-process side-by-side fashion.
314: BOOL fLoadable;
315: hr = pRuntimeInfo->IsLoadable(&fLoadable);
316: if (FAILED(hr))
317: {
318: wprintf(L"ICLRRuntimeInfo::IsLoadable failed w/hr 0x%08lx\n", hr);
319: goto Cleanup;
320: }
321: if (!fLoadable)
322: {
323: wprintf(L".NET runtime %s cannot be loaded\n", pszVersion);
324: goto Cleanup;
325: }
326: // Load the CLR into the current process and return a runtime interface
327: // pointer. ICorRuntimeHost and ICLRRuntimeHost are the two CLR hosting
328: // interfaces supported by CLR 4.0. Here we demo the ICLRRuntimeHost
329: // interface that was provided in .NET v2.0 to support CLR 2.0 new
330: // features. ICLRRuntimeHost does not support loading the .NET v1.x
331: // runtimes.
332: hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost,
333: IID_PPV_ARGS(&pClrRuntimeHost));
334: if (FAILED(hr))
335: {
336: wprintf(L"ICLRRuntimeInfo::GetInterface failed w/hr 0x%08lx\n", hr);
337: goto Cleanup;
338: }
339: // Start the CLR.
340: hr = pClrRuntimeHost->Start();
341: if (FAILED(hr))
342: {
343: wprintf(L"CLR failed to start w/hr 0x%08lx\n", hr);
344: goto Cleanup;
345: }
346: //
347: // Load the NET assembly and call the static method GetStringLength of
348: // the type CSSimpleObject in the assembly.
349: //
350: wprintf(L"Load the assembly %s\n", pszAssemblyPath);
351: // The invoked method of ExecuteInDefaultAppDomain must have the
352: // following signature: static int pwzMethodName (String pwzArgument)
353: // where pwzMethodName represents the name of the invoked method, and
354: // pwzArgument represents the string value passed as a parameter to that
355: // method. If the HRESULT return value of ExecuteInDefaultAppDomain is
356: // set to S_OK, pReturnValue is set to the integer value returned by the
357: // invoked method. Otherwise, pReturnValue is not set.
358: hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(pszAssemblyPath,
359: pszClassName, pszStaticMethodName, pszStringArg, &dwLengthRet);
360: if (FAILED(hr))
361: {
362: wprintf(L"Failed to call GetStringLength w/hr 0x%08lx\n", hr);
363: goto Cleanup;
364: }
365: // Print the call result of the static method.
366: wprintf(L"Call %s.%s(\"%s\") => %d\n", pszClassName, pszStaticMethodName,
367: pszStringArg, dwLengthRet);
368: Cleanup:
369: if (pMetaHost)
370: {
371: pMetaHost->Release();
372: pMetaHost = NULL;
373: }
374: if (pRuntimeInfo)
375: {
376: pRuntimeInfo->Release();
377: pRuntimeInfo = NULL;
378: }
379: if (pClrRuntimeHost)
380: {
381: // Please note that after a call to Stop, the CLR cannot be
382: // reinitialized into the same process. This step is usually not
383: // necessary. You can leave the .NET runtime loaded in your process.
384: //wprintf(L"Stop the .NET runtime\n");
385: //pClrRuntimeHost->Stop();
386: pClrRuntimeHost->Release();
387: pClrRuntimeHost = NULL;
388: }
389: return hr;
390: }
Comments
Anonymous
September 15, 2012
How to pass array to .net and modify its element in netAnonymous
November 25, 2012
Direct link to the tutorial code.msdn.microsoft.com/CppHostCLR-e6581ee0Anonymous
August 17, 2014
how to use _Type.InvokeMember with out Parameter?