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

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

裝載 .NET 運行時間是進階案例,而且在大多數情況下,.NET 開發人員不需要擔心裝載,因為 .NET 建置程式會提供預設主機來執行 .NET 應用程式。 不過,在某些特殊情況下,明確裝載 .NET 執行階段可能會很有用,可以作為在本機進程中叫用 Managed 程式碼的方法,或進一步控制執行階段的運作方式。

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

先決條件

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

你也需要建立一個 .NET 元件來測試主機,所以你應該安裝 最新的 .NET SDK。 其中包含連結所需的標頭和連結庫。

裝載 API

託管 .NET 執行階段是透過 nethost 和 hostfxr 程式庫的 API 完成的。 這些進入點會處理尋找和設定執行環境初始化複雜性的問題,並允許啟動受控應用程式及呼叫靜態受控方法。

這很重要

和 nethosthostfxr 裝載 API 僅支援架構相依的部署。 獨立部署應視為獨立可執行檔。 如果您要評估應用程式的部署模型,請使用架構相依的部署,以確保與這些原生裝載 API 相容。

使用 nethost.h 和 hostfxr.h 建立主機

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

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

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

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

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

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

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

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

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

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

hostfxr_initialize_for_runtime_config 和 hostfxr_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 - 載入受控組件並取得受控方法的函式指標

呼叫執行階段的委派以載入受控組件,並取得受控方法的函式指標。 代理需要組合語言路徑、組合語言限定型別名稱及方法名稱作為輸入,並回傳一個函式指標,可用於呼叫受管理方法。

const string_t dotnetlib_path = root_path + STR("DotNetLib.dll");
const char_t *dotnet_type = STR("DotNetLib.Lib, DotNetLib");
const char_t *dotnet_type_method = STR("Hello");
// 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 將會傳回失敗。