Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Como todo código gerenciado, os aplicativos .NET são executados por um host. O host é responsável por iniciar o tempo de execução (incluindo componentes como o JIT e o coletor de lixo) e invocar pontos de entrada gerenciados.
Hospedar o tempo de execução 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 compilação do .NET fornecem um host padrão para executar aplicativos .NET. Em algumas circunstâncias especializadas, no entanto, pode ser útil hospedar explicitamente o tempo de execução do .NET, seja como um meio de invocar código gerenciado em um processo nativo ou para obter mais controle sobre como o tempo de execução funciona.
Este artigo fornece uma visão geral das etapas necessárias para iniciar o tempo de execução 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ê precisa de um ambiente de desenvolvimento C++ (como o fornecido pelo Visual Studio).
Você também precisa criar um componente .NET para testar o host, portanto, você deve instalar o SDK .NET mais recente. Inclui os cabeçalhos e bibliotecas necessários para vincular.
APIs de hospedagem
A hospedagem do runtime do .NET é feita pelas APIs das bibliotecas nethost e hostfxr. Esses pontos de entrada lidam com a complexidade de localizar e configurar o tempo de execução para inicialização e permitem iniciar um aplicativo gerenciado e chamar para um método gerenciado estático.
Importante
As nethost APIs de hospedagem e hostfxr suportam apenas implantações dependentes da estrutura. As implantações independentes devem ser tratadas como executáveis autónomos. Se você estiver avaliando modelos de implantação para seu aplicativo, use uma implantação dependente da estrutura para garantir a compatibilidade com essas APIs de hospedagem nativas.
Crie um host usando nethost.h e hostfxr.h
Um host de exemplo demonstrando as etapas descritas no tutorial abaixo está disponível no repositório dotnet/samples do GitHub. Os comentários no exemplo associam claramente as etapas numeradas deste tutorial ao local onde elas são executadas no exemplo. Para obter instruções de download, consulte Exemplos e Tutoriais.
Tenha em mente que o exemplo de host deve ser usado para fins de aprendizagem, portanto, é leve na detecção de erros e projetado para privilegiar a legibilidade em detrimento da eficiência.
As etapas a seguir detalham como usar as nethost bibliotecas 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 e a nethost biblioteca, bem como os cabeçalhos coreclr_delegates.h e hostfxr.h instalados com o SDK do .NET.
Passo 1 - Carregue hostfxr e obtenha 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 alojar o runtime do .NET. A lista completa de funções pode ser encontrada no hostfxr.h e no documento de design da hospedagem nativa. O exemplo e este tutorial usam o seguinte:
-
hostfxr_initialize_for_runtime_config: Inicializa um contexto de host e prepara para a inicialização do tempo de execução do .NET usando a configuração de tempo de execução especificada. -
hostfxr_get_runtime_delegate: Obtém um delegado para a funcionalidade de tempo de execução. -
hostfxr_close: Fecha um contexto de host.
A biblioteca hostfxr é encontrada usando a API get_hostfxr_path da biblioteca nethost. Em seguida, é carregado e as 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 utiliza as seguintes inclusões:
#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
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 .NET utilizando a configuração de runtime para o componente gerido que será carregado. A hostfxr_get_runtime_delegate função é usada para obter um delegado de tempo de execução que permite carregar um assembly gerenciado e obter um ponteiro de função para um método estático nesse assembly.
// 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 delegado de tempo de execução é chamado para carregar o assembly gerenciado e obter um ponteiro de função para um método gerenciado. O delegado requer 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 de delegado ao chamar o delegado de tempo de execução, o exemplo usa uma assinatura padrão para o método gerenciado:
public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes);
Pode ser usada uma assinatura diferente especificando o nome do tipo de delegado ao chamar o delegado em tempo de execução.
Passo 4 - Execute o código gerenciado!
O host nativo agora pode chamar o método gerenciado e passá-lo os parâmetros desejados.
lib_args args
{
STR("from host!"),
i
};
hello(&args, sizeof(args));
Limitações
Apenas um tempo de execução pode ser carregado num único processo. Se a hostfxr_initialize_for_runtime_config API for chamada quando um tempo de execução já estiver carregado, ela verificará se o tempo de execução existente é compatível com os parâmetros de inicialização especificados. Se compatível, o tempo de execução existente será usado e, se não for compatível, a API retornará uma falha.