Dela via


Skriv en anpassad .NET-värd för att styra .NET-körningen från din interna kod

Precis som all hanterad kod körs .NET-program av en värd. Värden ansvarar för att starta körningen (inklusive komponenter som JIT och skräpinsamlaren) och anropa hanterade startpunkter.

Att vara värd för .NET-körningen är ett avancerat scenario och i de flesta fall behöver .NET-utvecklare inte bekymra sig om värdtjänster eftersom .NET-byggprocesser tillhandahåller en standardvärd för att köra .NET-program. Under vissa särskilda omständigheter kan det dock vara användbart att uttryckligen vara värd för .NET-körningen, antingen som ett sätt att anropa hanterad kod i en intern process eller för att få mer kontroll över hur körningen fungerar.

Den här artikeln ger en översikt över de steg som krävs för att starta .NET-körningen från intern kod och köra hanterad kod i den.

Förutsättningar

Eftersom värdar är interna program omfattar den här självstudien att skapa ett C++-program som är värd för .NET. Du behöver en C++-utvecklingsmiljö (till exempel den som tillhandahålls av Visual Studio).

Du måste också skapa en .NET-komponent för att testa värden med, så du bör installera .NET SDK. Den innehåller de rubriker och bibliotek som behövs för att länka till. I Windows med .NET 8 SDK kan du till exempel hitta filerna i C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\8.0.4\runtimes\win-x64\native.

Värd-API:er

Värd för .NET-körningen i .NET Core 3.0 och senare görs med API:erna och hostfxr bibliotekennethost. Dessa startpunkter hanterar komplexiteten i att hitta och konfigurera körningen för initiering och gör det möjligt att både starta ett hanterat program och anropa till en statisk hanterad metod.

Före .NET Core 3.0 var det enda alternativet för att vara värd för körningen via API:et coreclrhost.h . Det här värd-API:et är föråldrat nu och bör inte användas för att vara värd för .NET Core 3.0 och högre körningar.

Skapa en värd med och nethost.hhostfxr.h

En exempelvärd som visar stegen som beskrivs i självstudien nedan är tillgänglig på GitHub-lagringsplatsen dotnet/samples. Kommentarer i exemplet associerar tydligt de numrerade stegen från den här självstudien med var de utförs i exemplet. Instruktioner för nedladdning finns i Exempel och självstudier.

Tänk på att exempelvärden är tänkt att användas i utbildningssyfte, så det är lätt på felkontroll och utformat för att betona läsbarhet framför effektivitet.

Följande steg beskriver hur du använder biblioteken nethost och hostfxr för att starta .NET-körningen i ett internt program och anropa till en hanterad statisk metod. Exemplet använder rubrikerna nethost och biblioteket och coreclr_delegates.h huvudena och hostfxr.h som är installerade med .NET SDK.

Steg 1 – Läsa in hostfxr och hämta exporterade värdfunktioner

Biblioteket nethost tillhandahåller funktionen get_hostfxr_path för att hostfxr hitta biblioteket. Biblioteket hostfxr exponerar funktioner för att vara värd för .NET-körningen. Den fullständiga listan över funktioner finns i hostfxr.h och det interna värddesigndokumentet. Exemplet och den här självstudien använder följande:

  • hostfxr_initialize_for_runtime_config: Initierar en värdkontext och förbereder för initiering av .NET-körningen med den angivna körningskonfigurationen.
  • hostfxr_get_runtime_delegate: Hämtar ett ombud för körningsfunktioner.
  • hostfxr_close: Stänger en värdkontext.

Biblioteket hostfxr hittas med hjälp av get_hostfxr_path API:et från nethost biblioteket. Den läses sedan in och dess exporter hämtas.

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

Exemplet använder följande:

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

Dessa filer finns på följande platser:

Eller om du har installerat .NET 8 SDK i Windows:

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

Steg 2 – Initiera och starta .NET-körningen

Funktionerna hostfxr_initialize_for_runtime_config och hostfxr_get_runtime_delegate initierar och startar .NET-körningen med hjälp av körningskonfigurationen för den hanterade komponent som ska läsas in. Funktionen hostfxr_get_runtime_delegate används för att hämta ett körningsdelegat som gör det möjligt att läsa in en hanterad sammansättning och hämta en funktionspekare till en statisk metod i sammansättningen.

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

Steg 3 – Läsa in hanterad sammansättning och hämta funktionspekaren till en hanterad metod

Runtime-ombudet anropas för att läsa in den hanterade sammansättningen och hämta en funktionspekare till en hanterad metod. Ombudet kräver sammansättningssökvägen, typnamnet och metodnamnet som indata och returnerar en funktionspekare som kan användas för att anropa den hanterade metoden.

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

Genom att skicka nullptr som namn på ombudstyp när du anropar körningsdelegaten använder exemplet en standardsignatur för den hanterade metoden:

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

En annan signatur kan användas genom att ange namnet på ombudstypen när du anropar runtime-ombudet.

Steg 4 – Kör hanterad kod!

Den interna värden kan nu anropa den hanterade metoden och skicka den önskade parametrarna.

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

hello(&args, sizeof(args));