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: }