Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Analogamente a tutto il codice gestito, le applicazioni .NET vengono eseguite da un host. L'host è responsabile dell'avvio del runtime (inclusi i componenti come JIT e Garbage Collector) e di richiamare i punti di ingresso gestiti.
L'hosting del runtime .NET è uno scenario avanzato e, nella maggior parte dei casi, gli sviluppatori .NET non devono preoccuparsi dell'hosting perché i processi di compilazione .NET forniscono un host predefinito per l'esecuzione di applicazioni .NET. In alcune circostanze specializzate, tuttavia, può essere utile ospitare in modo esplicito il runtime .NET, come mezzo per richiamare il codice gestito in un processo nativo o per ottenere un maggiore controllo sul funzionamento del runtime.
Questo articolo offre una panoramica dei passaggi necessari per avviare il runtime .NET dal codice nativo ed eseguirvi codice gestito.
Prerequisiti
Poiché gli host sono applicazioni native, questa esercitazione illustra la creazione di un'applicazione C++ per ospitare .NET. È necessario un ambiente di sviluppo C++, ad esempio quello fornito da Visual Studio.
È anche necessario compilare un componente .NET per testare l'host con , quindi è necessario installare le versioni più recenti di .NET SDK. Comprende le intestazioni e le librerie necessarie per il collegamento.
API di hosting
L'hosting del runtime .NET viene eseguito con le API delle librerie nethost e hostfxr. Questi punti di ingresso gestiscono la complessità di ricerca e configurazione del runtime per l'inizializzazione e consentono sia l'avvio di un'applicazione gestita che la chiamata a un metodo gestito statico.
Importante
Le nethost API di hosting e hostfxr supportano solo le distribuzioni dipendenti dal framework. Le distribuzioni autonome devono essere considerate come eseguibili autonomi. Se si valutano i modelli di distribuzione per l'applicazione, usare una distribuzione dipendente dal framework per garantire la compatibilità con queste API di hosting native.
Creare un host usando nethost.h e hostfxr.h
Un host di esempio che illustra i passaggi descritti nell'esercitazione seguente è disponibile nel repository GitHub dotnet/samples. I commenti nell'esempio associano chiaramente i passaggi numerati di questa esercitazione alla posizione in cui vengono eseguiti nell'esempio. Per istruzioni sul download, vedere esempi ed esercitazioni .
Tieni presente che l'host di esempio è pensato per l'apprendimento, quindi è leggero sul controllo degli errori e progettato per privilegiare la leggibilità rispetto all'efficienza.
I passaggi seguenti illustrano in dettaglio come usare le nethost librerie e hostfxr per avviare il runtime .NET in un'applicazione nativa e chiamare un metodo statico gestito.
L'esempio usa le intestazioni e la libreria nethost, insieme alle intestazioni coreclr_delegates.h e hostfxr.h installate con l'SDK di .NET.
Passaggio 1: Caricare hostfxr e ottenere le funzioni di hosting esportate
La nethost libreria fornisce la get_hostfxr_path funzione per l'individuazione della hostfxr libreria. La hostfxr libreria espone le funzioni per l'hosting del runtime .NET. L'elenco completo delle funzioni è disponibile in hostfxr.h e nel documento di progettazione dell'hosting nativo. L'esempio e questa esercitazione usano quanto segue:
-
hostfxr_initialize_for_runtime_config: inizializza un contesto host e prepara l'inizializzazione del runtime .NET usando la configurazione di runtime specificata. -
hostfxr_get_runtime_delegate: ottiene un delegato per la funzionalità di runtime. -
hostfxr_close: chiude un contesto host.
La hostfxr libreria viene trovata usando l'API get_hostfxr_path dalla nethost libreria. Viene quindi caricato e le relative esportazioni vengono recuperate.
// Using the nethost library, discover the location of hostfxr and get exports
bool load_hostfxr()
{
// Pre-allocate a large buffer for the path to hostfxr
char_t buffer[MAX_PATH];
size_t buffer_size = sizeof(buffer) / sizeof(char_t);
int rc = get_hostfxr_path(buffer, &buffer_size, nullptr);
if (rc != 0)
return false;
// Load hostfxr and get desired exports
void *lib = load_library(buffer);
init_fptr = (hostfxr_initialize_for_runtime_config_fn)get_export(lib, "hostfxr_initialize_for_runtime_config");
get_delegate_fptr = (hostfxr_get_runtime_delegate_fn)get_export(lib, "hostfxr_get_runtime_delegate");
close_fptr = (hostfxr_close_fn)get_export(lib, "hostfxr_close");
return (init_fptr && get_delegate_fptr && close_fptr);
}
L'esempio comprende i seguenti elementi:
#include <nethost.h>
#include <coreclr_delegates.h>
#include <hostfxr.h>
Questi file sono disponibili nei percorsi seguenti:
- https://github.com/dotnet/runtime/blob/main/src/native/corehost/nethost/nethost.h
- https://github.com/dotnet/runtime/blob/main/src/native/corehost/coreclr_delegates.h
- https://github.com/dotnet/runtime/blob/main/src/native/corehost/hostfxr.h
Passaggio 2: Inizializzare e avviare il runtime .NET
Le funzioni hostfxr_initialize_for_runtime_config e hostfxr_get_runtime_delegate inizializzano e avviano il runtime .NET usando la configurazione di runtime per il componente gestito che verrà caricato. La funzione hostfxr_get_runtime_delegate viene usata per ottenere un delegato di runtime che consente di caricare un assembly gestito e ottenere un puntatore di funzione a un metodo statico in tale assembly.
// Load and initialize .NET Core and get desired function pointer for scenario
load_assembly_and_get_function_pointer_fn get_dotnet_load_assembly(const char_t *config_path)
{
// Load .NET Core
void *load_assembly_and_get_function_pointer = nullptr;
hostfxr_handle cxt = nullptr;
int rc = init_fptr(config_path, nullptr, &cxt);
if (rc != 0 || cxt == nullptr)
{
std::cerr << "Init failed: " << std::hex << std::showbase << rc << std::endl;
close_fptr(cxt);
return nullptr;
}
// Get the load assembly function pointer
rc = get_delegate_fptr(
cxt,
hdt_load_assembly_and_get_function_pointer,
&load_assembly_and_get_function_pointer);
if (rc != 0 || load_assembly_and_get_function_pointer == nullptr)
std::cerr << "Get delegate failed: " << std::hex << std::showbase << rc << std::endl;
close_fptr(cxt);
return (load_assembly_and_get_function_pointer_fn)load_assembly_and_get_function_pointer;
}
Passaggio 3: Caricare l'assembly gestito e ottenere il puntatore di funzione a un metodo gestito
Il delegato di runtime viene chiamato per caricare l'assembly gestito e ottenere un puntatore verso una funzione di un metodo gestito. Il delegato richiede il percorso dell'assembly, il nome del tipo e il nome del metodo come dati di input e restituisce un puntatore di funzione che può essere usato per richiamare il metodo gestito.
// Function pointer to managed delegate
component_entry_point_fn hello = nullptr;
int rc = load_assembly_and_get_function_pointer(
dotnetlib_path.c_str(),
dotnet_type,
dotnet_type_method,
nullptr /*delegate_type_name*/,
nullptr,
(void**)&hello);
Passando nullptr come nome del tipo di delegato quando si chiama il delegato di runtime, l'esempio utilizza una firma predefinita per il metodo gestito:
public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes);
È possibile usare una firma diversa specificando il nome del tipo delegato al momento della chiamata del delegato di runtime.
Passaggio 4: Eseguire il codice gestito.
L'host nativo può ora chiamare il metodo gestito e passarlo ai parametri desiderati.
lib_args args
{
STR("from host!"),
i
};
hello(&args, sizeof(args));
Limitazioni
È possibile caricare un solo runtime all'interno di un singolo processo. Se l'API hostfxr_initialize_for_runtime_config viene chiamata quando un runtime è già caricato, verifica se il runtime esistente è compatibile con i parametri di inizializzazione specificati. Se compatibile, verrà usato il runtime esistente e, se non è compatibile, l'API restituirà un errore.