次の方法で共有


ネイティブ コードから .NET ランタイムを制御するカスタム .NET ホストを記述する

すべてのマネージド コードと同様に、.NET アプリケーションはホストによって実行されます。 ホストは、ランタイム (JIT やガベージ コレクターなどのコンポーネントを含む) を起動し、マネージド エントリ ポイントを呼び出す役割を担います。

.NET ランタイムのホストは高度なシナリオであり、ほとんどの場合、.NET 開発者はホストについて心配する必要はありません。.NET ビルド プロセスには、.NET アプリケーションを実行するための既定のホストが用意されているためです。 ただし、特殊な状況では、ネイティブ プロセスでマネージド コードを呼び出す手段として、またはランタイムの動作をより詳細に制御するために、.NET ランタイムを明示的にホストすると便利な場合があります。

この記事では、ネイティブ コードから .NET ランタイムを開始し、その中でマネージド コードを実行するために必要な手順の概要について説明します。

前提条件

ホストはネイティブ アプリケーションであるため、このチュートリアルでは.NET をホストする C++ アプリケーションの構築について説明します。 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 以降での .NET ランタイムのホストは、 nethost および hostfxr ライブラリの API を使用して行われます。 これらのエントリ ポイントは、初期化のためのランタイムの検索と設定の複雑さを処理し、マネージド アプリケーションの起動と静的マネージド メソッドの呼び出しの両方を可能にします。

.NET Core 3.0 より前では、ランタイムをホストするための唯一のオプションは、 coreclrhost.h API を使用していました。 現在、このホスティング API は廃止されており、.NET Core 3.0 以降のランタイムのホストには使用しないでください。

nethost.hhostfxr.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: ホスト コンテキストを閉じます。

hostfxr ライブラリは、nethost ライブラリget_hostfxr_path API を使用して見つかります。 その後、読み込まれ、エクスポートが取得されます。

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

制限事項

1 つのプロセス内に読み込むことができるランタイムは 1 つだけです。 ランタイムが既に読み込まれているときに hostfxr_initialize_for_runtime_config API が呼び出された場合、既存のランタイムが指定された初期化パラメーターと互換性があるかどうかを確認します。 互換性がある場合、既存のランタイムが使用され、互換性がない場合、API はエラーを返します。