Een aangepaste .NET-host schrijven om de .NET-runtime te beheren vanuit uw systeemeigen code

Net als alle beheerde code worden .NET-toepassingen uitgevoerd door een host. De host is verantwoordelijk voor het starten van de runtime (inclusief onderdelen zoals de JIT en garbagecollector) en het aanroepen van beheerde toegangspunten.

Het hosten van de .NET-runtime is een geavanceerd scenario en in de meeste gevallen hoeven .NET-ontwikkelaars zich geen zorgen te maken over hosting, omdat .NET-buildprocessen een standaardhost bieden voor het uitvoeren van .NET-toepassingen. In sommige gespecialiseerde omstandigheden kan het echter handig zijn om de .NET-runtime expliciet te hosten, ofwel als een manier om beheerde code aan te roepen in een systeemeigen proces of om meer controle te krijgen over de werking van de runtime.

Dit artikel bevat een overzicht van de stappen die nodig zijn om de .NET-runtime te starten vanuit systeemeigen code en beheerde code erin uit te voeren.

Vereisten

Omdat hosts systeemeigen toepassingen zijn, wordt in deze zelfstudie beschreven hoe u een C++-toepassing maakt voor het hosten van .NET. U hebt een C++-ontwikkelomgeving nodig (zoals die van Visual Studio).

U moet ook een .NET-onderdeel bouwen om de host te testen, dus moet u de .NET SDK installeren.

Api's hosten

Het hosten van de .NET-runtime in .NET Core 3.0 en hoger wordt uitgevoerd met de nethost API's en hostfxr bibliotheken. Deze toegangspunten verwerken de complexiteit van het zoeken en instellen van de runtime voor initialisatie en het starten van een beheerde toepassing en het aanroepen van een statische beheerde methode.

Vóór .NET Core 3.0 was de enige optie voor het hosten van de runtime via de coreclrhost.h API. Deze hosting-API is nu verouderd en mag niet worden gebruikt voor het hosten van .NET Core 3.0 en hogere runtimes.

Een host maken met behulp van nethost.h en hostfxr.h

Een voorbeeldhost die de stappen laat zien die in de onderstaande zelfstudie worden beschreven, is beschikbaar in de GitHub-opslagplaats dotnet/samples. Opmerkingen in het voorbeeld koppelen duidelijk de genummerde stappen uit deze zelfstudie aan waar ze in het voorbeeld worden uitgevoerd. Zie Voorbeelden en zelfstudies voor downloadinstructies.

Houd er rekening mee dat de voorbeeldhost bedoeld is om te worden gebruikt voor leerdoeleinden, dus het is licht op foutcontrole en ontworpen om de leesbaarheid over efficiëntie te benadrukken.

In de volgende stappen wordt beschreven hoe u de nethosthostfxr .NET-runtime kunt starten in een systeemeigen toepassing en hoe u een beheerde statische methode aanroept. In het voorbeeld wordt de nethost header en bibliotheek gebruikt die is geïnstalleerd met de .NET SDK en kopieën van de coreclr_delegates.h en hostfxr.h bestanden uit de dotnet/runtime-opslagplaats .

Stap 1: de geëxporteerde hostingfuncties laden hostfxr en ophalen

De nethost bibliotheek biedt de functie voor het get_hostfxr_path zoeken naar de hostfxr bibliotheek. De hostfxr bibliotheek bevat functies voor het hosten van de .NET-runtime. De volledige lijst met functies vindt u in hostfxr.h en het systeemeigen ontwerpdocument voor hosting. Het voorbeeld en deze zelfstudie gebruiken het volgende:

  • hostfxr_initialize_for_runtime_config: initialiseert een hostcontext en bereidt zich voor op initialisatie van de .NET-runtime met behulp van de opgegeven runtimeconfiguratie.
  • hostfxr_get_runtime_delegate: Haalt een gemachtigde op voor runtime-functionaliteit.
  • hostfxr_close: Hiermee sluit u een hostcontext.

De hostfxr bibliotheek wordt gevonden met behulp van get_hostfxr_path API uit nethost de bibliotheek. Het wordt vervolgens geladen en de exports worden opgehaald.

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

In het voorbeeld wordt gebruikgemaakt van het volgende:

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

Deze bestanden zijn te vinden op de volgende locaties:

Stap 2: de .NET-runtime initialiseren en starten

De hostfxr_initialize_for_runtime_config functies hostfxr_get_runtime_delegate initialiseren en starten de .NET-runtime met behulp van de runtimeconfiguratie voor het beheerde onderdeel dat wordt geladen. De hostfxr_get_runtime_delegate functie wordt gebruikt om een runtime-gemachtigde op te halen waarmee een beheerde assembly kan worden geladen en een functiepointer naar een statische methode in die assembly kan worden gebracht.

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

Stap 3: beheerde assembly laden en functiepointer ophalen naar een beheerde methode

De runtime-gemachtigde wordt aangeroepen om de beheerde assembly te laden en een functiepointer op te halen naar een beheerde methode. De gedelegeerde vereist het assemblypad, het typenaam en de methodenaam als invoer en retourneert een functiepointer die kan worden gebruikt om de beheerde methode aan te roepen.

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

Als u de naam van het gemachtigde type doorgeeft nullptr bij het aanroepen van de runtime-gemachtigde, gebruikt het voorbeeld een standaardhandtekening voor de beheerde methode:

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

Een andere handtekening kan worden gebruikt door de naam van het gemachtigde type op te geven bij het aanroepen van de runtime-gemachtigde.

Stap 4: Beheerde code uitvoeren!

De systeemeigen host kan nu de beheerde methode aanroepen en de gewenste parameters doorgeven.

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

hello(&args, sizeof(args));