Clr Hosting - Transferindo Objetos

No último artigo demonstrei como carregar e iniciar a CLR dentro de um processo não gerenciado e como executar um código simples, porém o objetivo é utilizar componentes .Net dentro de um processo Win32. Para isso iremos criar um formulário no lado .Net, exibir utilizando o C++ e alimentar este formulário com dados da parte não gerenciada.

COM

Essa troca de objetos entre o modo gerenciado e não gerenciado será feito através de interfaces COM dentro do mesmo processo, mas para termos as interfaces acessíveis tanto na parte .Net quanto C++ é preciso que sejam declaradas em um dos lados e depois exportar para o outro. Neste artigo declaramos as interfaces no lado .Net e usaremos a ferramenta tlbexp.exe para gerar um arquivo _tlb _o qual será incluído nos fontes do lado C++.

Declarando as Interfaces

Dentro do projeto .Net declaramos as duas interfaces que serão utilizadas, tanto a que será implementada no .Net quanto no C++ e compilamos isso em uma DLL.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComVisible(true)]
[Guid("F0483A98-DE08-4886-AAD5-E4FDBA0DA482")]
public interface IManagedControl
{
IntPtr CreateForm(IUnmanagedCode code);
void SetCaption(string text);
}

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComVisible(true)]
[Guid("9FEC93AF-4BA4-4E2E-9213-8EA9A3471E21")]
public interface IUnmanagedCode
{
int Add(int a, int b);
}

E a partir da dll gerada exportamos isso com a ferramenta tlbexp.exe que gera o arquivo tlb a ser incluído no código C++.

Exportando o tlb
Para utilizarmos o arquivo contendo as interfaces basta usar a diretiva #import
_
_
1
#import "..\ClrHosting.Dummy\bin\Debug\ClrHosting.Dummy.tlb" raw_interfaces_only

Trocando objetos

Agora que as interfaces estão disponíveis temos de implementar cada uma delas em seu respectivo domínio.

.Net

Do lado .Net além da Implementação da interface _IManagedControl _teremos também a implementação do formulário que será utilizado no C++, no exemplo será um formulário simples, com 3 caixas de texto e um botão que chamará um método de soma no lado C++ pela interface IUnmanagedCode.

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
public partial class DummyForm : Form
{
private IUnmanagedCode code_;
public DummyForm(IUnmanagedCode code)
{
InitializeComponent();
code_ = code;
}
private void button1_Click(object sender, EventArgs e)
{
txtAdd.Text = code_.Add(int.Parse(txtA.Text), int.Parse(txtB.Text)).ToString();
}
}

public class AddImpl : IManagedControl
{
private Form form_;
#region IManagedControl Members
public IntPtr CreateForm(IUnmanagedCode code)
{
form_ = new DummyForm(code);
return form_.Handle;
}
public void SetCaption(string text)
{
form_.Text = text;
}
#endregion
}

C++

O código do C++ é uma simples função de soma da interface IUnmanagedCode, e a implementação da interface IUnknowInterface .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 class UnmanagedCodeImpl : public ClrHosting_Dummy::IUnmanagedCode
{
private:
volatile long refCount_;
public:
UnmanagedCodeImpl(void);
~UnmanagedCodeImpl(void);

// IUnmanagedCode
virtual HRESULT WINAPI Add(long a,long b, long* pRetVal);

// IUnknown methods
virtual HRESULT WINAPI QueryInterface(REFIID riid, void** ppv);
virtual ULONG WINAPI AddRef();
virtual ULONG WINAPI Release();
};

HRESULT WINAPI UnmanagedCodeImpl::Add(long a,long b, long* pRetVal)
{
(*pRetVal) = a + b;
return S_OK;
}

// Implementação da interface IUnknown omitida

Com a implementação das interfaces prontas temos de realizar a troca dos objetos, do lado do .Net é criado uma instância de AddImpl da qual é feito um Marshal para uma interface COM e essa é passada como um ponteiro para o lado não gerenciado. Isso é feito com o método GetComInterfaceForObject, onde passamos o objeto e com qual interface COM será exposto. Conforme vimos também, a única assinatura de método disponível para chamarmos via CLRRuntimeHost retorna um int que é por onde devemos passamos o ponteiro.

1
2
3
4
5
6
7
8
public class DummyEntryPoint
{
public static int ClrEntryPoint(String text)
{
AddImpl impl = new AddImpl();
return (int)Marshal.GetComInterfaceForObject(impl, typeof(IManagedControl));
}
}

No C++ além da inicialização da CLR, precisamos fazer o cast do int para o CComPtr que receberá a interface que solicitamos no .Net e criamos uma instância da implementação de IManagedControl que é o necessário para utilizar os objetos no lado não gerenciado e prover dados para o lado gerenciado.

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
// chamada pela ClrHost para criação do objeto .net
hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(
L"D:\\ClrHosting.Dummy\\bin\\Debug\\ClrHosting.Dummy.dll", // Caminho do assembly
L"ClrHosting.Dummy.DummyEntryPoint", // Nome do Tipo
L"ClrEntryPoint", // Nome do método
L"", // parâmetro
&retVal); // retorno

// Objeto do .net
CComPtr<ClrHosting_Dummy::IManagedControl> netobj = (ClrHosting_Dummy::IManagedControl*)retVal;

// Implementacao C++
CComPtr<ClrHosting_Dummy::IUnmanagedCode> unmanaged = new UnmanagedCodeImpl();

// criação de form .net
long frmHandle;
netobj->CreateForm(unmanaged, &frmHandle);

// exibir o form .net com a API do windows
::ShowWindow((HWND)frmHandle, SW_SHOWDEFAULT);

// chamada do c++ p/ .net
netobj->SetCaption(L"C++");

if ( SUCCEEDED(hr) )
{
MSG msg;
while (::GetMessage(&msg, 0, 0, 0) )
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}

Neste código são executadas as seguintes operações:

  • Com a chamada do método _ExecuteInDefaultAppDomain _obtemos um ponteiro para a interface IManagedControl, e temos assim a referência do objeto gerenciado;
  • Com a chamada do método _CreateForm _passamos o ponteiro do objeto não gerenciado para o .Net;
  • Com o método _SetCaption _enviamos dados do C++ para o .Net;
  • Clicando no botão Soma do formulário é chamado o método Add da interface IUnmanagedCode onde é enviado dados para C++ e recebe uma resposta;
    Formulário .Net com dados do C++

    Desta forma conseguimos criar controles gerenciados e utilizar num ambiente não gerenciado, o que permite a utilização de componentes disponíveis, que era a minha necessidade, assim como permite a criação de um esquema de plugin com uma liberdade maior de tecnologias para a expansão do aplicativo.

Interoperating with Unmanaged Code
Tlbexp.exe
Implementing IUnknown in C++
Marshal.GetComInterfaceForObject Method