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.