撰寫自訂 .NET 主機以從原生程式碼控制 .NET 執行階段
如同所有受控程式碼一般,.NET 應用程式也是由主機執行。 該主機會負責啟動執行階段 (包括 JIT 和記憶體回收行程等元件) 及叫用受控進入點。
裝載 .NET 執行階段是進階案例。在大多數情況下,由於 .NET 建置程序會提供用於執行 .NET 應用程式的預設主機,因此 .NET 開發人員不需要擔心裝載相關事宜。 不過在某些特殊情況下,明確裝載 .NET 執行階段可能會很有用,例如叫用原生處理序中的受控程式碼時,或是用於加強對執行階段運作方式的控制。
本文會概述使用機器碼啟動 .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
您可使用 nethost
與 hostfxr
程式庫的 API,在 .NET Core 3.0 和更新版本中裝載 .NET 執行階段。 這些進入點能處理尋找及設定初始化執行階段的複雜程度,並允許啟動受控應用程式及呼叫靜態受控方法。
在 .NET Core 3.0 發佈之前,裝載執行階段的唯一方法是透過 coreclrhost.h
API。 這個裝載 API 現已淘汰,不應再用於裝載 .NET Core 3.0 和更新版本的執行階段。
使用 nethost.h
與 hostfxr.h
建立主機
dotnet/samples GitHub 存放庫中提供的範例主機可示範本教學課程列出的步驟。 範例中的註解清楚地為本教學課程中的編號步驟與範例中的執行位置建立了關聯。 如需下載指示,請參閱範例和教學課程。
請記住,範例主機是為了用於學習,因此錯誤檢查較不嚴謹,而且比起效率更重視可讀性。
下列步驟詳細說明如何使用 nethost
與 hostfxr
程式庫在原生應用程式中啟動 .NET 執行階段,並呼叫受控靜態方法。 此範例會使用 nethost
標頭和程式庫,以及隨 .NET SDK 安裝的 coreclr_delegates.h
和 hostfxr.h
標頭。
步驟 1:載入 hostfxr
並取得匯出的裝載函式
nethost
程式庫會提供用來找出 hostfxr
程式庫的 get_hostfxr_path
函式。 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>
在下列位置可找到這些檔案:
- 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
或者,如果您已在 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_config
和 hostfxr_get_runtime_delegate
函式會使用初始化載入受控元件的執行階段設定,並啟動 .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 - 載入受控組件並取得針對受控方法的函式指標
系統會呼叫執行階段委派來載入受控組件,並取得針對受控方法的函式指標。 該委派需要組件路徑、類型名稱及方法名稱作為輸入,並會傳回可用來叫用受控方法的函式指標。
// 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
傳遞為委派類型名稱,範例會針對受控方法使用預設簽章:
public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes);
若要使用不同的簽章,可以在呼叫執行階段委派時指定委派類型名稱。
步驟 4 - 執行受控程式碼!
原生主機現在可以呼叫受控方法,並向它傳遞所需的參數。
lib_args args
{
STR("from host!"),
i
};
hello(&args, sizeof(args));