A tempo perso, insieme all'amico e collega Bruno, sto lavorando ad una idea. Tra i vari ostacoli da superare c'è stato (in realtà c'è ancora in parte) scrivere un'applicazione C++ che faccia da host per un execution engine .Net. Siccome il codice deve girare su SO differenti, su Windows la scelta è ricaduta sul CLR, mentre su Linux e altri è ricaduta su Mono. L'hosting del runtime Mono è un film alquanto complesso che magari racconterò in un post successivo. L'oggetto invece di questo post è l'hosting del CLR.
Devo dire che per un novizio dell'argomento, e io lo sono, capire esattamente cosa occorre fare non è semplice, complice la scarsa e frammentaria documentazione sull'argomento. Infine dopo vari tentativi e qualche dritta di Raf si è riusciti a realizzare la cosa. Dico "si è riusciti" e non "sono riuscito" perchè a dirla tutta il contributo maggiore lo ha dato l'amico Bruno, scarsamente visibile sulla rete ma tecnico veramente brillante.
Cerco di delineare i passi fondamentali per riuscire a: caricare il CLR, caricare un assembly nel default AppDomain (creato in automatico quando viene caricato il CLR), creare un'instanza di una classe e infine invocare un metodo. In realtà, al di la del nostro scopo, l'argomento è decisamente più complesso e ampio. Le API per fare hosting permettono molto altro: creare nuovi AppDomain, controllare la sicurezza, controllare il Garbage Collector, ...
Vengo al dunque con un esempio scritto in C++ con Visual Studio 2005. La premessa è che occorre linkare la libreria mscoree.lib, generalmente localizzata in "C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Lib\mscoree.lib".

#include "stdafx.h"
#include
"ole2.h"
#include <mscoree.h>

#import <C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorlib.tlb> raw_interfaces_only high_property_prefixes("_get","_put","_putref")
using namespace mscorlib;

int _tmain(int argc, _TCHAR* argv[])
{
  
ICorRuntimeHost *pClrHost;
  
IUnknown *unknownDomain;
  
_AppDomain *domain;
  
_Assembly *assembly;
  
_Type *type;
  
VARIANT retVal, target;
  
BSTR str;

   // Inizializzazione del CLR. In sostanza si dichiara quale versione del CLR si vuole.  
   HRESULT hr = CorBindToRuntimeEx(NULL, NULL, 0, CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (PVOID*) &pClrHost);

   // Caricamento e avvio del CLR.
  
hr = pClrHost->Start();

   // Viene recuperato un puntatore all'interfaccia COM IUnknown esposta dall'oggetto "Default AppDomain".
   // Il CLR crea sempre un AppDomain di default.
   hr = pClrHost->GetDefaultDomain(&unknownDomain);

   // Viene recuperato un riferimento all'interfaccia _AppDomain implementata dal Default AppDomain.
  
hr = unknownDomain->QueryInterface(__uuidof(_AppDomain), (PVOID*) &domain);

   // Caricamente dell'assembly (è un assembly di esempio) "Sample.dll[/.exe]" localizzato nella stessa directory
  
// del programma corrente. E' chiaramente possibile caricare da qualsiasi percorso.
  
str = SysAllocString(L"Sample"
);
  
hr = domain->Load_2(str, &assembly);
  
SysFreeString(str);

   // Viene recuperato un puntatore al tipo "Foo" presente nell'assembly caricato.
  
str = SysAllocString(L"Foo"
);
  
hr = assembly->GetType_2(str, &type);
  
SysFreeString(str);

   // Viene creata una istanza di Foo.
  
str = SysAllocString(L"Foo"
);
  
hr = assembly->CreateInstance(str, &target);
  
SysFreeString(str);

   // Viene invocato il metodo "Method1", la cui signature è: public void Method1() { ... }
   str = SysAllocString(L
"Method1"
);
  
hr = type->InvokeMember_3(str, BindingFlags::BindingFlags_InvokeMethod, NULL, target, NULL, &retVal);
  
SysFreeString(str); 

   return 0;
}

Le cose sono un po' più complicate per metodi che accettano parametri perchè in questo caso occorrerebbe passarli come SAFEARRAY.