Pular para o conteúdo principal

Servidor e usuário COM na mesma "solution"

 Um dos objetivos da próxima iteração no meu projeto atual é quebrar o sistema em mais pedaços, e levantar os pedaços um nível de abstração até componentes COM.
Uma das consequências é substituir diversas ligações mais estáticas e torná-las ligações mais dinâmicas através do COM.

Tipicamente, um programa usuário de um componente COM, escrito para o Visual Studio, usará a diretiva #import para incluir na unidade de tradução C++ as declarações necessárias. Isso implica que o componente deve estar instalado na máquina que constrói o programa.

O que fazer quando o componente em questão está no mesmo lote de construção que o programa usuário? A solução inicial é criar uma dependência de construção tal que o componente seja sempre construído primeiro, e usar um post-build event ao projeto do componente para registrá-lo no sistema. Desse modo, na construção do programa, o ambiente estará correto.

Eu considero este arranjo prejudicial. Minha melhor razão é esta: o servidor de construção principal é um Windows Server 2008 x86-64, onde o usuário que constrói não é membro do grupo Administradores.

Evitando debater essa opinião, caminhamos para uma alternativa. Apesar de #import ser a maneira mais fácil de usar o componente, a maneira básica de referenciar componentes (e interfaces) COM é através da IDL que os definem.

Desse modo, o desenvolvedor incluiria os arquivos IDL que definem o componente (e suas interfaces) no projeto do programa. Construir arquivos IDL é gerar arquivos .h e .c, que devem compor o programa como de costume.

Mas como lidar com um componente ainda em desenvolvimento, cujas definições em IDL estão ainda incompletas ou não são definitivas? É, no mínimo, um saco copiar os arquivos IDL para todos os programas usuários (que, no meu caso, são vários) a cada vez que esses arquivos são modificados. Por princípio, desejamos definir as coisas em um único lugar, e referenciar este lugar em todos os outros lugares onde as coisas são necessárias.

Como reusar arquivos IDL dessa maneira? Infelizmente, não é possível usar #import com arquivos IDL; além disso, não faz sentido usar #include. Arquivos IDL devem ser compilados junto com o programa, não incluídos diretamente em unidades de tradução C++.

A base da minha solução, com o Visual Studio, é incluir os arquivos IDL presentes no projeto do componente em todos os projetos usuários usando o comando Add Existing Item do menu Project. Este comando insere uma referência aos arquivos selecionados no lote de construção do projeto-alvo sem copiar estes arquivos.

Essa técnica não funciona para componentes definidos em múltiplos arquivos IDL que fazem referência uns aos outros através da diretiva import. (Não confundir!) O midl.exe provavelmente não será capaz de ajustar os caminhos de import para o novo contexto -- o local de construção do programa é diferente do local de construção do componente. O arquivo A.idl referencia "B.idl" mas este arquivo não existe no local dos fontes do programa, mas sim no local dos fontes do componente.

A solução para essa situação é ajustar a configuração Additional Include Directories na aba MIDL das Configuration Properties do projeto.

Com essa configuração ajustada, os arquivos .h e .c correspondentes às IDL serão criados no local dos fontes do programa, como desejado. Estes arquivos devem ser incluídos da forma costumeira na construção do programa. Novamente, a técnica não funcionará para componentes definidos em múltiplos arquivos IDL, devido a um desacordo entre o midl.exe e o Visual Studio. A configuração padrão produzirá, para Foo.idl, arquivos Foo_h.h; porém, no texto gerado pelo midl.exe, ocorrerá #include "Foo.h".

A solução para essa situação é ajustar a configuração Header File na aba Output, na aba MIDL das Configuration Properties para o valor $(Filename).h.

Com essa configuração ajustada, os arquivos gerados terão texto e nome corretos, e será possível compilar o programa. Aqui, um último problema pode surgir, caso o projeto do programa use PCH. Muito provavelmente, o arquivo PCH terá sido compilado como unidade de tradução C++. Porém, o midl.exe gera arquivos C e não arquivos C++. Não é permitido incluir PCHs C++ em unidades de tradução C. É preciso desabilitar PCH para todo o projeto, ou especificamente para os arquivos C gerados. Como fazer isso fica como exercício para o leitor.

Atenção: esta técnica permitirá ao programa incluir as declarações necessárias ao uso do componente; porém, as declarações disponíveis serão muito mais simples que o tipicamente injetado pelo #import. Sugiro o uso de ATL::CComPtr ou, se não for possível usar ATL, o uso de _COM_SMARTPTR_TYPEDEF.

Comentários

Postagens mais visitadas deste blog

return void();

É uma pequeneza mas eu gostaria muito que as linguagens da família do C permitissem retornar void quando a função retorna void . Escrever da forma a seguir me aborrece. public class Foo  {    public void log (String msg) { }    public void bar (String x, String y) { }    public void bar (String x)    {      if (x == null)      {        log("x was null");        return;      }      bar(x, "default");    } } Eu ficaria mais feliz escrevendo assim: public class Foo  {    public void log (String msg) { }    public void bar (String x, String y) { }    public void bar (String x)    {      if (x == null) return log("x was null");      bar(x, "default");     } }

.NET COM Interop, IStream e Visual Studio

Investigando oportunidades para otimizar um componente, experimentei definir métodos que não solicitam dados (em arrays etc.) como argumentos mas sim instâncias de IStream , e me deparei com um problema interessante: interoperabilidade com .NET. Usar componentes COM em programas .NET não é particularmente difícil; no Visual Studio, é possível adicionar componentes COM como referências no projeto. Se o componente for capaz de Automation, é possível usar a ferramenta tlbimp.exe para gerar manualmente um callable wrapper para o componente, que, se eu entendi as coisas corretamente, não precisa ser distribuído com a aplicação. Porém, todos os métodos que eu aprendi tem essa peculiaridade: eles parecem assumir que um componente não usa interfaces definidas por outros componentes -- como, por exemplo, declarando parâmetros com tipo IStream , uma interface definida pelo sistema. Pelo método do callable wrapper , a ferramenta assume que as interfaces mencionadas integram o componente...

Eclipse PDE com Gradle

Na virada de 2016 para 2017, adotamos oficialmente o Gradle como ferramenta de construção de software na Prodist. A fila da migração está fluindo confortavelmente desde então. Já temos as provas de conceito necessárias para todos os projetos, inclusive os projetos nativos para compilador C++. Para fontes Java usamos Eclipse. Integramos projetos Gradle ao Eclipse usando Buildship . Acho que o Buildship ainda tem um longo caminho pela frente, mas realiza tudo o que necessitamos. Já estamos entregando com base nesse esquema. O problema mais importante que encontramos foi na integração de projetos OSGi. No Eclipse, trabalhamos projetos OSGi com o PDE. Nosso ciclo de desenvolvimento inclui rodar configurações "OSGi Framework" no Eclipse para teste. Infelizmente, o PDE assume uma estrutura de projeto própria e não funciona corretamente caso contrário. Estes problemas estão registrados em bugs como, por exemplo, o  bug 153023 . Nossos projetos OSGi com Gradle usam o layout ...