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 akan 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.

API Hosting

Hosting runtime .NET di .NET Core 3.0 ke nethost atas dilakukan dengan API pustaka 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.

Sebelum .NET Core 3.0, satu-satunya opsi untuk menghosting runtime adalah melalui coreclrhost.h API. API hosting ini sudah usang sekarang dan tidak boleh digunakan untuk menghosting .NET Core 3.0 dan runtime yang lebih tinggi.

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 pustaka dan hostfxr untuk memulai runtime .NET dalam aplikasi asli dan memanggil ke metode statis terkelola. Sampel menggunakan header dan pustaka yang nethost diinstal dengan .NET SDK dan salinan coreclr_delegates.h file dan hostfxr.h dari repositori dotnet/runtime.

Langkah 1 - Muat hostfxr dan dapatkan fungsi hosting yang diekspor

nethost Pustaka menyediakan get_hostfxr_path fungsi untuk menemukan hostfxr pustaka. hostfxr Pustaka mengekspos 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 get_hostfxr_path API dari nethost pustaka. 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 ini digunakan untuk mendapatkan delegasi runtime yang memungkinkan pemuatan rakitan terkelola dan 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 dan meneruskannya parameter yang diinginkan.

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

hello(&args, sizeof(args));