Menulis host .NET kustom untuk mengontrol runtime .NET dari kode asli Anda

Seperti semua kode terkelola, aplikasi .NET dijalankan oleh host. Host bertanggung jawab untuk memulai runtime (termasuk komponen seperti JIT dan pengumpul sampah) dan memanggil titik masuk terkelola.

Hosting runtime .NET adalah skenario lanjutan dan, dalam kebanyakan kasus, pengembang .NET tidak perlu khawatir tentang hosting karena proses build .NET menyediakan host default untuk menjalankan aplikasi .NET. Namun, dalam beberapa keadaan khusus, dapat berguna untuk secara eksplisit menghosting runtime .NET, baik sebagai sarana untuk memanggil kode terkelola dalam proses asli atau untuk mendapatkan lebih banyak kontrol atas cara kerja runtime.

Artikel ini memberikan gambaran umum tentang langkah-langkah yang diperlukan untuk memulai runtime .NET dari kode asli dan menjalankan kode terkelola di dalamnya.

Prasyarat

Karena host adalah aplikasi asli, tutorial ini mencakup pembuatan aplikasi C++ untuk menghosting .NET. Anda memerlukan lingkungan pengembangan C++ (seperti yang disediakan oleh Visual Studio).

Anda juga perlu membangun komponen .NET untuk menguji host, jadi Anda harus menginstal .NET SDK terbaru. Ini termasuk header dan pustaka yang dibutuhkan untuk dihubungkan.

API Hosting

Menjalankan runtime .NET dilakukan dengan API pustaka nethost dan hostfxr. Titik masuk ini menangani kompleksitas menemukan dan menyiapkan runtime untuk inisialisasi dan memungkinkan peluncuran aplikasi terkelola dan memanggil ke dalam metode terkelola statis.

Penting

API hosting nethost dan hostfxr hanya mendukung penyebaran yang bergantung pada framework. Penyebaran mandiri harus diperlakukan sebagai executable yang berdiri sendiri. Jika Anda mengevaluasi model penyebaran untuk aplikasi Anda, gunakan penyebaran yang bergantung pada kerangka kerja untuk memastikan kompatibilitas dengan API hosting asli ini.

Membuat host menggunakan nethost.h dan hostfxr.h

Contoh host yang menunjukkan langkah-langkah yang diuraikan dalam tutorial di bawah ini tersedia di repositori GitHub dotnet/samples. Komentar dalam sampel dengan jelas mengaitkan langkah-langkah bernomor dari tutorial ini dengan tempat mereka dilakukan dalam sampel. Untuk petunjuk pengunduhan, lihat sampel dan Tutorial.

Perlu diingat bahwa host sampel dimaksudkan untuk digunakan untuk tujuan pembelajaran, sehingga ringan pada pemeriksaan kesalahan dan dirancang untuk menekankan keterbacaan atas efisiensi.

Langkah-langkah berikut merinci cara menggunakan nethost dan pustaka hostfxr untuk memulai runtime .NET dalam aplikasi asli dan memanggil metode statis terkelola. sampel menggunakan header dan pustaka, serta header nethost dan coreclr_delegates.h yang diinstal dengan .NET SDK.

Langkah 1 - Muat hostfxr dan dapatkan fungsi hosting yang diekspor

nethost Pustaka menyediakan get_hostfxr_path fungsi untuk menemukan hostfxr pustaka. Pustaka hostfxr menyediakan fungsi untuk menghosting runtime .NET. Daftar lengkap fungsi dapat ditemukan di hostfxr.h dan dokumen desain hosting asli. Sampel dan tutorial ini menggunakan yang berikut:

  • hostfxr_initialize_for_runtime_config: Menginisialisasi konteks host dan mempersiapkan inisialisasi runtime .NET menggunakan konfigurasi runtime yang ditentukan.
  • hostfxr_get_runtime_delegate: Mendapatkan delegasi untuk fungsionalitas runtime.
  • hostfxr_close: Menutup konteks host.

Pustaka hostfxr ditemukan menggunakan API get_hostfxr_path dari pustaka nethost. Kemudian dimuat dan ekspornya diambil.

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

Sampel menggunakan yang berikut ini meliputi:

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

File-file ini dapat ditemukan di lokasi berikut:

Langkah 2 - Menginisialisasi dan memulai runtime .NET

Fungsi hostfxr_initialize_for_runtime_config dan hostfxr_get_runtime_delegate menginisialisasi dan memulai runtime .NET menggunakan konfigurasi runtime untuk komponen terkelola yang akan dimuat. Fungsi hostfxr_get_runtime_delegate digunakan untuk mendapatkan delegasi runtime yang memungkinkan pemuatan rakitan terkelola serta mendapatkan penunjuk fungsi ke metode statis dalam rakitan tersebut.

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

Langkah 3 - Muat rakitan terkelola dan dapatkan penunjuk fungsi ke metode terkelola

Delegasi runtime dipanggil untuk memuat rakitan terkelola dan mendapatkan penunjuk fungsi ke metode terkelola. Delegasi memerlukan jalur perakitan, nama jenis, dan nama metode sebagai input dan mengembalikan penunjuk fungsi yang dapat digunakan untuk memanggil metode terkelola.

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

Dengan meneruskan nullptr sebagai nama jenis delegasi saat memanggil delegasi runtime, sampel menggunakan tanda tangan default untuk metode terkelola:

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

Tanda tangan yang berbeda dapat digunakan dengan menentukan nama jenis delegasi saat memanggil delegasi runtime.

Langkah 4 - Jalankan kode terkelola!

Host asli sekarang dapat memanggil metode terkelola ini dan meneruskan parameter yang diinginkan kepadanya.

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

hello(&args, sizeof(args));

Keterbatasan

Hanya satu runtime yang dapat dimuat dalam satu proses. hostfxr_initialize_for_runtime_config Jika API dipanggil ketika runtime sudah dimuat, api akan memeriksa apakah runtime yang ada kompatibel dengan parameter inisialisasi yang ditentukan. Jika kompatibel, runtime yang ada akan digunakan dan jika tidak kompatibel, API akan mengembalikan kegagalan.