Sdílet prostřednictvím


Napsání vlastního hostitele .NET pro řízení modulu runtime .NET z nativního kódu

Stejně jako všechny spravované kódy jsou aplikace .NET spouštěné hostitelem. Hostitel zodpovídá za spuštění modulu runtime (včetně komponent, jako je JIT a uvolňování paměti) a vyvolání spravovaných vstupních bodů.

Hostování modulu runtime .NET je pokročilý scénář a vývojáři .NET se ve většině případů nemusí starat o hostování, protože procesy sestavení .NET poskytují výchozího hostitele pro spouštění aplikací .NET. V některých specializovaných případech ale může být užitečné explicitně hostovat modul runtime .NET, a to buď jako prostředek vyvolání spravovaného kódu v nativním procesu, nebo za účelem získání větší kontroly nad fungováním modulu runtime.

Tento článek poskytuje přehled kroků potřebných ke spuštění modulu runtime .NET z nativního kódu a spuštění spravovaného kódu v něm.

Požadavky

Vzhledem k tomu, že hostitelé jsou nativní aplikace, tento kurz se zabývá vytvořením aplikace C++, která bude hostitelem .NET. Budete potřebovat vývojové prostředí C++ (například prostředí poskytované sadou Visual Studio).

Budete také muset sestavit komponentu .NET pro otestování hostitele, takže byste měli nainstalovat sadu .NET SDK. Obsahuje potřebné hlavičky a knihovny pro propojení. Například na Windows se sadou .NET 8 SDK najdete soubory v C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\8.0.4\runtimes\win-x64\native.

Hostování rozhraní API

Hostování modulu runtime .NET v .NET Core 3.0 a novějších se provádí pomocí API knihoven nethost a hostfxr. Tyto vstupní body zpracovávají složitost hledání a nastavení modulu runtime pro inicializaci a umožňují spuštění spravované aplikace i volání do statické spravované metody.

Před .NET Core 3.0 byla jediná možnost hostování modulu runtime prostřednictvím coreclrhost.h rozhraní API. Toto rozhraní API pro hostování je zastaralé a nemělo by být používáno k hostování .NET Core 3.0 a novějších runtime.

Vytvoření hostitele pomocí nethost.h a hostfxr.h

Ukázkový hostitel demonstrující kroky popsané v následujícím kurzu je k dispozici v úložišti dotnet/samples na GitHubu. Komentáře v ukázce jasně přidružují očíslované kroky z tohoto kurzu k tomu, kde se v ukázce provádějí. Pokyny ke stažení najdete ve vzorech a návodech .

Mějte na paměti, že ukázkový hostitel je určený k použití pro účely učení, takže je light na kontrole chyb a je navržený tak, aby zdůraznil čitelnost nad efektivitou.

Následující kroky podrobně popisují, jak pomocí knihoven nethost a hostfxr spustit modul runtime .NET v nativní aplikaci a zavolat spravovanou statickou metodu. Ukázka používánethost hlavičky a knihovnu a coreclr_delegates.h a hostfxr.h hlavičky, které jsou nainstalovány se sadou .NET SDK.

Krok 1 - Načtěte hostfxr a získejte exportované hostitelské funkce

Knihovna nethost poskytuje get_hostfxr_path funkci pro vyhledání hostfxr knihovny. Knihovna hostfxr zveřejňuje funkce pro hostování modulu runtime .NET. Úplný seznam funkcí najdete v hostfxr.h nativním dokumentu návrhu hostování. Ukázka a tento kurz používají následující:

  • hostfxr_initialize_for_runtime_config: Inicializuje kontext hostitele a připraví na inicializaci modulu runtime .NET pomocí zadané konfigurace modulu runtime.
  • hostfxr_get_runtime_delegate: Získá delegáta pro funkce modulu runtime.
  • hostfxr_close: Zavře kontext hostitele.

Knihovna hostfxr je nalezena rozhraním get_hostfxr_path API z knihovny nethost. Pak se načte a jeho exporty se získají.

// 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);
}

Ukázka používá následující prvky:

#include <nethost.h>
#include <coreclr_delegates.h>
#include <hostfxr.h>

Tyto soubory najdete v následujících umístěních:

Nebo pokud jste nainstalovali sadu .NET 8 SDK ve Windows:

  • C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\8.0.4\runtimes\win-x64\native

Krok 2 – Inicializace a spuštění modulu runtime .NET

hostfxr_get_runtime_delegate Funkce hostfxr_initialize_for_runtime_config inicializují a spustí modul runtime .NET pomocí konfigurace modulu runtime pro spravovanou komponentu, která se načte. Funkce hostfxr_get_runtime_delegate se používá k získání delegáta prostředí runtime, který umožňuje načíst spravované sestavení a získat ukazatel na funkci statické metody v tomto sestavení.

// 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;
}

Krok 3 – Načtení spravovaného sestavení a získání ukazatele funkce k spravované metodě

Delegát modulu runtime se volá, aby načetl spravované sestavení a získal ukazatel na funkci spravované metody. Delegát vyžaduje cestu sestavení, název typu a název metody jako vstupy a vrátí ukazatel funkce, který lze použít k vyvolání spravované metody.

// 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);

Předáním názvu typu delegáta nullptr při volání delegáta runtime, použije ukázka výchozí podpis pro spravovanou metodu:

public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes);

Jiný podpis lze použít zadáním názvu typu delegáta při volání delegáta modulu runtime.

Krok 4 : Spuštění spravovaného kódu

Nativní hostitel teď může volat spravovanou metodu a předat jí požadované parametry.

lib_args args
{
    STR("from host!"),
    i
};

hello(&args, sizeof(args));

Omezení

Do jednoho procesu lze načíst pouze jedno runtime. Pokud je rozhraní API voláno při již načteném modulu runtime, zkontroluje, jestli je existující modul runtime kompatibilní se zadanými inicializačními parametry. Pokud je kompatibilní, použije se existující modul runtime a pokud není kompatibilní, rozhraní API vrátí chybu.