Pisanie niestandardowego hosta platformy .NET w celu kontrolowania środowiska uruchomieniowego platformy .NET z kodu natywnego
Podobnie jak cały kod zarządzany, aplikacje platformy .NET są wykonywane przez hosta. Host jest odpowiedzialny za uruchamianie środowiska uruchomieniowego (w tym składników, takich jak JIT i moduł odśmiecania pamięci) oraz wywoływanie zarządzanych punktów wejścia.
Hostowanie środowiska uruchomieniowego platformy .NET jest zaawansowanym scenariuszem, a w większości przypadków deweloperzy platformy .NET nie muszą martwić się o hosting, ponieważ procesy kompilacji platformy .NET zapewniają domyślny host do uruchamiania aplikacji platformy .NET. Jednak w pewnych wyspecjalizowanych okolicznościach może być przydatne jawne hostowanie środowiska uruchomieniowego platformy .NET— jako środka wywoływania kodu zarządzanego w procesie natywnym lub w celu uzyskania większej kontroli nad działaniem środowiska uruchomieniowego.
Ten artykuł zawiera omówienie kroków niezbędnych do uruchomienia środowiska uruchomieniowego platformy .NET z kodu natywnego i wykonania w nim kodu zarządzanego.
Wymagania wstępne
Ponieważ hosty są aplikacjami natywnymi, w tym samouczku opisano tworzenie aplikacji języka C++ do hostowania platformy .NET. Będzie potrzebne środowisko programistyczne języka C++ (takie jak udostępnione przez program Visual Studio).
Należy również skompilować składnik platformy .NET w celu przetestowania hosta przy użyciu programu , więc należy zainstalować zestaw SDK platformy .NET. Zawiera on niezbędne nagłówki i biblioteki do połączenia. Na przykład w systemie Windows z zestawem .NET 8 SDK pliki można znaleźć w pliku C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\8.0.4\runtimes\win-x64\native
.
Hostowanie interfejsów API
Hostowanie środowiska uruchomieniowego platformy .NET w programie .NET Core 3.0 lub nowszym odbywa się przy użyciu nethost
interfejsów API bibliotek i hostfxr
. Te punkty wejścia obsługują złożoność znajdowania i konfigurowania środowiska uruchomieniowego na potrzeby inicjowania oraz umożliwiają uruchamianie aplikacji zarządzanej i wywoływanie statycznej metody zarządzanej.
Przed platformą .NET Core 3.0 jedyną opcją hostowania środowiska uruchomieniowego było użycie interfejsu coreclrhost.h
API. Ten interfejs API hostingu jest teraz przestarzały i nie powinien być używany do hostowania środowisk uruchomieniowych platformy .NET Core 3.0 i wyższych.
Tworzenie hosta przy użyciu polecenia nethost.h
i hostfxr.h
Przykładowy host demonstrujący kroki opisane w poniższym samouczku jest dostępny w repozytorium GitHub dotnet/samples. Komentarze w przykładzie wyraźnie kojarzą ponumerowane kroki z tego samouczka, gdzie są wykonywane w przykładzie. Aby uzyskać instrukcje dotyczące pobierania, zobacz Przykłady i samouczki.
Należy pamiętać, że przykładowy host ma być używany do celów szkoleniowych, dlatego jest to światło na sprawdzanie błędów i zaprojektowano, aby podkreślić czytelność nad wydajnością.
Poniższe kroki szczegółowo opisują sposób uruchamiania środowiska uruchomieniowego platformy .NET w aplikacji natywnej przy użyciu nethost
bibliotek i hostfxr
wywoływania metody statycznej zarządzanej. W przykładzienethost
użyto nagłówków i bibliotek oraz coreclr_delegates.h
nagłówków i hostfxr.h
zainstalowanych za pomocą zestawu .NET SDK.
Krok 1. Ładowanie hostfxr
i pobieranie wyeksportowanych funkcji hostingu
Biblioteka nethost
udostępnia get_hostfxr_path
funkcję lokalizowania hostfxr
biblioteki. Biblioteka hostfxr
uwidacznia funkcje hostowania środowiska uruchomieniowego platformy .NET. Pełną listę funkcji można znaleźć w hostfxr.h
dokumencie projektowania natywnego hostingu. W przykładzie i w tym samouczku są używane następujące elementy:
hostfxr_initialize_for_runtime_config
: inicjuje kontekst hosta i przygotowuje się do inicjowania środowiska uruchomieniowego platformy .NET przy użyciu określonej konfiguracji środowiska uruchomieniowego.hostfxr_get_runtime_delegate
: Pobiera delegata na potrzeby funkcji środowiska uruchomieniowego.hostfxr_close
: zamyka kontekst hosta.
Biblioteka hostfxr
znajduje się przy użyciu interfejsu get_hostfxr_path
API z nethost
biblioteki. Następnie jest ładowany, a jego eksporty są pobierane.
// 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);
}
W przykładzie użyto następujących elementów:
#include <nethost.h>
#include <coreclr_delegates.h>
#include <hostfxr.h>
Te pliki można znaleźć w następujących lokalizacjach:
- 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
Możesz też zainstalować zestaw SDK platformy .NET 8 w systemie Windows:
C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\8.0.4\runtimes\win-x64\native
Krok 2. Inicjowanie i uruchamianie środowiska uruchomieniowego platformy .NET
Funkcje hostfxr_initialize_for_runtime_config
i hostfxr_get_runtime_delegate
inicjują i uruchamiają środowisko uruchomieniowe platformy .NET przy użyciu konfiguracji środowiska uruchomieniowego dla składnika zarządzanego, który zostanie załadowany. Funkcja hostfxr_get_runtime_delegate
służy do pobierania delegata środowiska uruchomieniowego, który umożliwia ładowanie zarządzanego zestawu i pobieranie wskaźnika funkcji do metody statycznej w tym zestawie.
// 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. Ładowanie zarządzanego zestawu i pobieranie wskaźnika funkcji do metody zarządzanej
Delegat środowiska uruchomieniowego jest wywoływany w celu załadowania zarządzanego zestawu i pobrania wskaźnika funkcji do metody zarządzanej. Delegat wymaga ścieżki zestawu, nazwy typu i nazwy metody jako danych wejściowych i zwraca wskaźnik funkcji, który może służyć do wywoływania metody zarządzanej.
// 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);
nullptr
Przekazując jako nazwę typu delegata podczas wywoływania delegata środowiska uruchomieniowego, przykład używa domyślnego podpisu dla metody zarządzanej:
public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes);
Inny podpis może być używany przez określenie nazwy typu delegata podczas wywoływania delegata środowiska uruchomieniowego.
Krok 4. Uruchamianie kodu zarządzanego!
Host macierzysty może teraz wywołać metodę zarządzaną i przekazać jej żądane parametry.
lib_args args
{
STR("from host!"),
i
};
hello(&args, sizeof(args));