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.
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
Postar um comentário