共用方式為


撰寫自定義的 .NET 主機,以從原生程式代碼控制 .NET 運行時間

如同所有 Managed 程式代碼,.NET 應用程式是由主機執行。 主機負責啟動執行階段(包括 JIT 和垃圾收集器等元件),以及叫用受控進入點。

裝載 .NET 運行時間是進階案例,而且在大多數情況下,.NET 開發人員不需要擔心裝載,因為 .NET 建置程式會提供預設主機來執行 .NET 應用程式。 不過,在某些特殊情況下,明確裝載 .NET 執行階段可能很有用,無論是在原生進程中執行 managed 程式碼,還是為了更好地控制執行階段的運作方式。

本文概述了從原生代碼啟動 .NET 執行階段並在其中執行受控代碼所需的步驟。

先決條件

由於主機是原生應用程式,本教學課程涵蓋建構C++應用程式來裝載 .NET。 您將需要C++開發環境(例如 Visual Studio 所提供的環境)。

您也需要建置 .NET 元件來測試主機,因此您應該安裝 .NET SDK。 其中包含連結所需的標頭和連結庫。 例如,在具有 .NET 8 SDK 的 Windows 上,您可以在 中找到 C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\8.0.4\runtimes\win-x64\native檔案。

裝載 API

在 .NET Core 3.0 和更新版本中,使用 nethosthostfxr 函式庫的 API 來裝載 .NET 執行階段。 這些進入點會處理尋找和設定執行環境初始化複雜性的問題,並允許啟動受控應用程式及呼叫靜態受控方法。

在 .NET Core 3.0 之前,裝載運行時間的唯一選項是透過 coreclrhost.h API。 此裝載 API 現在已過時,不應該用於裝載 .NET Core 3.0 和更新版本的運行時間。

使用 nethost.hhostfxr.h 建立主機

示範下列教學課程中所述步驟的 範例主機 可在 dotnet/samples GitHub 存放庫中取得。 範例中的批注會清楚將本教學課程中的編號步驟與範例中執行的步驟產生關聯。 如需下載指示,請參閱 範例和教學課程

請記住,範例主機的目的是要用於學習目的,因此可以輕視錯誤檢查,並設計來強調效率的可讀性。

下列步驟詳細說明如何使用 nethosthostfxr 連結庫,在原生應用程式中啟動 .NET 運行時間,並呼叫Managed靜態方法。 此範例會使用nethost標頭和函式庫,以及隨 .NET SDK 一起安裝的coreclr_delegates.hhostfxr.h標頭。

步驟 1 - 載入 hostfxr 並取得導出的裝載函式

nethost 程式庫提供 get_hostfxr_path 函式來尋找 hostfxr 程式庫。 連結 hostfxr 庫會公開裝載 .NET 運行時間的函式。 您可以在hostfxr.h原生主機設計檔中找到完整的功能清單。 此範例和本教學課程使用下列專案:

  • hostfxr_initialize_for_runtime_config:初始化主機內容,並準備使用指定的運行時間組態初始化 .NET 運行時間。
  • hostfxr_get_runtime_delegate:取得執行階段功能的委託。
  • hostfxr_close:關閉主機環境。

可使用nethost程式庫中的get_hostfxr_path API 找到hostfxr程式庫。 接著將其載入,並擷取其匯出資料。

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

此範例使用的項目包含以下內容:

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

您可以在下列位置找到這些檔案:

或者,如果您已在 Windows 上安裝 .NET 8 SDK:

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

步驟 2 - 初始化並啟動 .NET 運行時間

hostfxr_initialize_for_runtime_confighostfxr_get_runtime_delegate 函式會使用即將載入的 Managed 元件的運行時間組態,來初始化並啟動 .NET 運行時間。 函數 hostfxr_get_runtime_delegate 用來取得執行時委派,以允許載入管理的程序集,並取得該程序集中靜態方法的函數指標。

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

步驟 3 - 載入受控組件並取得受控方法的函式指標

呼叫執行階段的委派以載入受控組件,並取得受控方法的函式指標。 委派需要元件路徑、類型名稱和方法名稱做為輸入,並傳回可用來叫用 Managed 方法的函式指標。

// 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 為委派類型名稱,範例使用 Managed 方法的預設簽章:

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

呼叫運行時間委派時,可以指定委派類型名稱來使用不同的簽章。

步驟 4 - 執行受控代碼!

原生主機現在可以呼叫Managed方法,並傳遞所需的參數。

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

hello(&args, sizeof(args));

局限性

單一進程內只能載入一個執行環境。 hostfxr_initialize_for_runtime_config如果在載入運行時間時呼叫 API,它會檢查現有的運行時間是否與指定的初始化參數相容。 如果相容,則會使用現有的運行時間,如果不相容,API 將會傳回失敗。