Ao manter um componente VCL escrito em Delphi mas consumido por utilizadores tanto de Delphi como de C++Builder, apercebe-se rapidamente de que os dois sistemas de compilação tratam as dependências de forma muito diferente. Uma unidade que compila perfeitamente em Delphi pode causar erros catastróficos no vinculador (linker) em C++Builder. Esta discrepância é uma armadilha frequente para autores de componentes, particularmente ao adicionar novas unidades internas a uma biblioteca existente.
Eis uma análise detalhada da razão pela qual isto acontece, utilizando exemplos reais do componente HotXLS, e como blindar o seu processo de compilação cruzada utilizando inclusões explícitas, {$HPPEMIT} e vinculação de pragma (pragma linking).
A Armadilha: Compilação Implícita vs. Inclusão Explícita
Suponha que cria uma nova unidade Delphi, lxXlsSummary.pas, para lidar com metadados de documentos, e a inclui (uses) na sua unidade principal de análise, lxRead.pas. Clica em compilar no IDE Delphi, a compilação é bem-sucedida e distribui a atualização.
No dia seguinte, os seus utilizadores de C++Builder reportam um erro durante a fase de vinculação (linking): Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj.
A Abordagem Delphi (dcc32)
Quando o compilador Delphi processa um pacote (.dpk), observa as unidades explicitamente listadas na cláusula contains. Se uma dessas unidades utilizar (uses) uma unidade externa (como lxXlsSummary.pas) que não está na lista contains, o compilador Delphi executa uma vinculação estática implícita. Simplesmente encontra o ficheiro .pas no caminho de pesquisa, compila-o num .dcu e incorpora-o no .bpl resultante. A compilação é bem-sucedida, ocultando completamente a omissão.
A Abordagem C++Builder (MSBuild / .cbproj)
O sistema de compilação do C++Builder é muito mais rigoroso. Apenas gera ficheiros objeto de C++ (.obj) e cabeçalhos (.hpp) para as unidades Delphi explicitamente listadas no grupo de itens <DelphiCompile> do ficheiro .cbproj. Como lxXlsSummary.pas nunca foi explicitamente registado no ficheiro de projeto, não é criado nenhum lxXlsSummary.obj. Quando o vinculador tenta resolver as chamadas feitas por lxRead.obj, os símbolos estão em falta, conduzindo a um erro externo não resolvido.
Resolver Externos com Pragma Link e HPPEMIT
Se pretender garantir que uma unidade é adequadamente vinculada em C++ sem forçar o utilizador a adicionar manualmente o ficheiro .obj ao seu projeto, pode utilizar a diretiva {$HPPEMIT} do Delphi. Isto diz ao compilador Delphi para injetar uma diretiva #pragma link de C++ específica no ficheiro .hpp gerado.
unit lxXlsSummary;
interface
{$IFDEF WINDOWS}
// Inject a pragma link into the generated C++ header file
// This forces the C++ linker to include the corresponding .obj file
{$HPPEMIT '#pragma link "lxXlsSummary.obj"'}
{$ENDIF}
uses
SysUtils, Classes;
type
TXlsSummaryInfo = class(TObject)
public
Title: string;
Author: string;
CreateTime: TDateTime;
end;
function XlsReadSummaryInformation(const FileName: string): TXlsSummaryInfo;
implementation
function XlsReadSummaryInformation(const FileName: string): TXlsSummaryInfo;
begin
Result := TXlsSummaryInfo.Create;
// Metadata extraction logic here
end;
end.
Quando o C++Builder inclui lxXlsSummary.hpp, o compilador encontra o #pragma link e diz automaticamente ao vinculador (ILINK32/ILINK64) para resolver os símbolos de lxXlsSummary.obj.
A Regra de Ouro para a Manutenção de Componentes
Para evitar interromper totalmente as compilações em C++Builder, deve adotar uma política de registo rigorosa. Sempre que uma nova unidade Pascal for adicionada à sua biblioteca, deve ser explicitamente registada simultaneamente nos três tipos de ficheiros de projeto.
1. Atualizar o Projeto C++Builder (.cbproj / .bpk)
Abra o ficheiro .cbproj num editor de texto e adicione a nova unidade à lista de compilação, garantindo que fornece uma ordem de compilação única. Se utilizar versões mais antigas do C++Builder com ficheiros .bpk, certifique-se de que a tag <file containerid="PascalCompiler" designclass="" filename="lxXlsSummary.pas" formname="" localcommand="" unitname="lxXlsSummary"></file> é adicionada.
<DelphiCompile Include="lxXlsSummary.pas">
<BuildOrder>101</BuildOrder>
</DelphiCompile>
2. Atualizar o Pacote Delphi (.dpk)
Adicione a unidade à cláusula contains explícita. Isto garante que o compilador Delphi não tem de depender da vinculação implícita, que é geralmente considerada uma má prática.
package HotXLS;
{$R *.res}
{$ALIGN 8}
{$ASSERTIONS ON}
{$BOOLEVAL OFF}
requires
rtl,
vcl;
contains
lxRead in 'lxRead.pas',
lxXlsSummary in 'lxXlsSummary.pas';
end.
Validação de Integração Contínua
A defesa definitiva contra esta armadilha é a validação de CI/CD. Nunca confie exclusivamente numa compilação Delphi bem-sucedida antes de enviar um componente de dupla linguagem. Os seus scripts de compilação devem invocar o MSBuild ou as ferramentas de linha de comandos bcc32c nos projetos C++Builder (por exemplo, build-Win32-Lib-CB.cmd) e executar uma vinculação completa da demonstração e trial em C++. Apenas quando o vinculador C++ for bem-sucedido poderá ter a certeza de que todas as unidades Delphi estão corretamente registadas e expõem os seus símbolos ao ambiente de execução (runtime) do C++.
Nota: A compatibilidade de compiladores multiplataforma é rigorosamente mantida em todas as edições Delphi e C++Builder do Componente HotXLS VCL.