Clr Hosting

Eu deparei recentemente com a necessidade de adicionar novos recursos em uma aplicação C++ nativa, onde a complexidade de realizar alterações neste sistema faz com que o o esforço seja compatível ao tamanho da aplicação ou seja, grande. Minha vantagem é a disponibilidade de diversas ferramentas desenvolvidas em .Net que com um pouco de adaptação atenderiam perfeitamente as necessidades. Assim meu problema se resume a um sistema de difícil manutenção de um lado e do outro um conjunto de componentes prontos, a decisão foi tentar integrar tudo.

Geralmente a carga da CLR é transparente para as aplicações desenvolvidas e compiladas em .Net, porém o .Net framework fornece uma API que permite carregar a CLR dentro de um processo não gerenciado. E esse tipo de carga é conhecido como CLR Hosting.

Carregando a CLR

Como este exemplo esta sendo feito para carregar o .Net 4.0, para utilizar as bibliotecas é preciso carregar a MSCorEE.dll e incluir o header.

1
2
#pragma comment(lib, "mscoree.lib")
#include "metahost.h"

Depois disso utilizaremos 3 interfaces dessa biblioteca
1
2
3
ICLRMetaHost *pMetaHost = nullptr;
ICLRRuntimeInfo *pRuntimeInfo = nullptr;
ICLRRuntimeHost *pClrRuntimeHost = nullptr;

Através da ICLRMetaHost nós podemos obter informações sobre as versões da CLR disponíveis no sistema, ver quais estão carregadas e, o que usaremos neste artigo, carregar uma versão específica. Para obter a instância dessa interface precisamos usar o método abaixo:
1
CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));

Com isso podemos criar a instância de ICLRRuntimeInfo utilizando o método GetRuntime que recebe a versão da CLR desejada, o id do tipo de interface desejado e o ponteiro para retorno.

Atenção

  • O valor passado como versão desejada deve ser igual aos nomes dentro da pasta do framework;
  • O único id de interface valido é _IID_ICLRRuntimeInfo_Utilizando a marco IID_PPV_ARGS é possível simplificar a passagem dos dois últimos parâmetros.

    1
    pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo));

    Agora temos em uma instância da ICLRRuntimeInfo que é responsável pelo procedimento de carga da CLR. A carga ocorre no momento da chamanda do método GetInterface que devolve um ponteiro para a interface ICLRRuntimeHost.

    1
    pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_PPV_ARGS(&pClrRuntimeHost));

    A interface ICLRRuntimeHost permite iniciar e parar a CLR além de executar código.

  • A iniciaçização explícita da CLR com a chamada do método Start não é necessária, pois na primeira vez que um código gerenciado executasse ela seria iniciada automaticamente.

    1
    pClrRuntimeHost->Start();

    Neste ponto temos a CLR versão 4.0 carregada e inicializada, podendo desta maneira começar a executar código gerenciado a partir de um processo não gerenciado.

Executando Código Gerenciado

Para executar o código são utilizados os seguintes métodos da ICLRRuntimeHost :

  • ExecuteInAppDomain
  • ExecuteInDefaultAppDomainA diferença entre eles é que o primeiro permite especificar em qual AppDomain deve ser executado o código, enquanto o segundo sempre executará no default AppDomain do assembly especificado. Para a chamada do ExecuteInDefaultAppDomain o método alvo (gerenciado) deve seguir a seguinte assinatura:

    1
    static int MethodName(String argument)

    Uma implementação para exibir uma caixa de mensagem ficaria da seguinte maneira:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    namespace ClrHosting.Dummy
    {
    public class DummyEntryPoint
    {
    public static int ShowMessageBox(String Text)
    {
    System.Windows.Forms.MessageBox.Show(Text);
    return 0;
    }
    }
    }

    E para executar este código é necessário chamar o método ExecuteInDefaultAppDomain passando:

  • Caminho para dll

  • Nome da classe
  • Nome do método
  • parâmetro, assim o código não gerenciado para chamar será:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    #include <Windows.h>
    #include <metahost.h>
    #pragma comment(lib, "mscoree.lib")

    int _tmain(int argc, _TCHAR* argv[])
    {
    ICLRMetaHost *pMetaHost;
    ICLRRuntimeInfo *pRuntimeInfo;
    ICLRRuntimeHost *pClrRuntimeHost;

    HRESULT hr;
    DWORD retVal = 0;
    hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
    if ( SUCCEEDED(hr) )
    {
    hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo));
    if ( SUCCEEDED(hr) )
    {
    hr = pRuntimeInfo->GetInterface(
    CLSID_CLRRuntimeHost,
    IID_PPV_ARGS(&pClrRuntimeHost));

    if ( SUCCEEDED(hr) )
    {
    hr = pClrRuntimeHost->Start();
    if ( SUCCEEDED(hr) )
    {
    hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(
    L"D:\\appolinario\\teste\\ClrHosting.Dummy\\ClrHosting.Dummy\\bin\\Debug\\ClrHosting.Dummy.dll", // Caminho do assembly
    L"ClrHosting.Dummy.DummyEntryPoint", // Nome do Tipo
    L"ShowMessageBox", // Nome do método
    L"Ok!", // parâmetro
    &retVal); // retorno
    }
    }
    }
    }
    return 0;
    }

Compilando e executando o código deve ser exibido uma caixa de mensagem com o texto “Ok!”

Resultado esperado
Neste ponto temos a CLR carregada, inicializada e executando código gerenciado dentro do processo não gerenciado, mesmo que a rotina sendo executada seja simples o procedimento é o mesmo para algo mais complexo. Entretanto para realizar a integração ainda é necessário trafegar objetos entre a parte gerenciada e a não gerenciada, mas isso fica para o próximo post.

Clr Hosting OverviewICLRMetaHost InterfaceICLRRuntimeInfo InterfaceICLRRuntimeHost Interface