Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Como todo código gerenciado, os aplicativos .NET são executados por um host. O host é responsável por iniciar o runtime (incluindo componentes como o JIT e o coletor de lixo) e invocar pontos de entrada gerenciados.
Hospedar o runtime do .NET é um cenário avançado e, na maioria dos casos, os desenvolvedores do .NET não precisam se preocupar com a hospedagem porque os processos de build do .NET fornecem um host padrão para executar aplicativos .NET. Em algumas circunstâncias especializadas, porém, pode ser útil hospedar explicitamente o runtime do .NET, seja como um meio de invocar o código gerenciado em um processo nativo ou para obter mais controle sobre como o runtime funciona.
Este artigo fornece uma visão geral das etapas necessárias para iniciar o runtime do .NET a partir do código nativo e executar o código gerenciado nele.
Pré-requisitos
Como os hosts são aplicativos nativos, este tutorial aborda a construção de um aplicativo C++ para hospedar o .NET. Você precisará de um ambiente de desenvolvimento C++ (como o fornecido pelo Visual Studio).
Você também precisará criar um componente do .NET com o qual testar o host, portanto, você deve instalar o SDK do .NET. Ele inclui os cabeçalhos e as bibliotecas necessárias para vinculação. Por exemplo, no Windows com o SDK do .NET 8, os arquivos podem ser encontrados em C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\8.0.4\runtimes\win-x64\native
.
APIs de hospedagem
Hospedar o runtime do .NET no .NET Core 3.0 e superior é feito com as APIs das bibliotecas nethost
e hostfxr
. Esses pontos de entrada lidam com a complexidade de localizar e configurar o runtime para inicialização e permitem iniciar um aplicativo gerenciado e chamar um método gerenciado estático.
Antes do .NET Core 3.0, a única opção para hospedar o runtime era por meio da coreclrhost.h
API. Essa API de hospedagem está obsoleta agora e não deve ser usada para hospedar o .NET Core 3.0 e runtimes mais altos.
Criar um host usando nethost.h
e hostfxr.h
Um host de exemplo que demonstra as etapas descritas no tutorial a seguir está disponível no repositório GitHub do dotnet/samples. Os comentários no exemplo associam claramente as etapas numeradas deste tutorial ao local em que são executados no exemplo. Para obter instruções de download, consulte Exemplos e Tutoriais.
Tenha em mente que o host de exemplo deve ser usado para fins de aprendizagem, portanto, ele possui pouca verificação de erros e foi projetado para enfatizar a legibilidade em detrimento da eficiência.
As etapas a seguir detalham o uso das bibliotecas nethost
e hostfxr
para iniciar o tempo de execução do .NET em um aplicativo nativo e chamar um método estático gerenciado. O exemplo usa os cabeçalhos nethost
e a biblioteca, além dos cabeçalhos coreclr_delegates.h
e hostfxr.h
instalados com o SDK do .NET.
Etapa 1 – Carregar hostfxr
e obter funções de hospedagem exportadas
A nethost
biblioteca fornece a get_hostfxr_path
função para localizar a hostfxr
biblioteca. A hostfxr
biblioteca expõe funções para hospedar o runtime do .NET. A lista completa de funções pode ser encontrada em hostfxr.h
e no documento de design de hospedagem nativo. O exemplo e este tutorial usam o seguinte:
hostfxr_initialize_for_runtime_config
: inicializa um contexto de host e se prepara para a inicialização do runtime do .NET usando a configuração de runtime especificada.-
hostfxr_get_runtime_delegate
: obtém um representante para a funcionalidade de runtime. -
hostfxr_close
: fecha um contexto de host.
A biblioteca hostfxr
é encontrada usando a API get_hostfxr_path
da biblioteca nethost
. Ela é carregada e suas exportações são recuperadas.
// 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);
}
O exemplo usa os seguintes elementos:
#include <nethost.h>
#include <coreclr_delegates.h>
#include <hostfxr.h>
Esses arquivos podem ser encontrados nos seguintes locais:
- 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
Ou, se você instalou o SDK do .NET 8 no Windows:
C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\8.0.4\runtimes\win-x64\native
Etapa 2 – Inicializar e iniciar o runtime do .NET
As funções hostfxr_initialize_for_runtime_config
e hostfxr_get_runtime_delegate
inicializam e iniciam o runtime do .NET usando a configuração de runtime para o componente gerenciado que será carregado. A função hostfxr_get_runtime_delegate
é usada para obter um representante do runtime que permite carregar um assembly gerenciado e obter um ponteiro de função para um método estático no assembly em questão.
// 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;
}
Etapa 3 – Carregar o assembly gerenciado e obter o ponteiro de função para um método gerenciado
O representante do runtime é chamado para carregar o assembly gerenciado e obter um ponteiro de função para um método gerenciado. O representante exige o caminho do assembly, o nome do tipo e o nome do método como entradas e retorna um ponteiro de função que pode ser usado para invocar o método gerenciado.
// 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);
Ao passar nullptr
como o nome do tipo delegado ao chamar o delegado de runtime, o exemplo usa uma assinatura padrão para o método gerenciado:
public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes);
Uma assinatura diferente pode ser usada especificando o nome do tipo de representante ao chamar o representante do runtime.
Etapa 4 – Executar código gerenciado!
O host nativo agora pode chamar o método gerenciado e passar os parâmetros desejados para ele.
lib_args args
{
STR("from host!"),
i
};
hello(&args, sizeof(args));
Limitações
Somente um runtime pode ser carregado dentro de um único processo. Se a hostfxr_initialize_for_runtime_config
API for chamada quando um runtime já estiver carregado, ela verificará se o runtime existente é compatível com os parâmetros de inicialização especificados. Se for compatível, o runtime existente será usado e, se não for compatível, a API retornará uma falha.