Artigo Técnico

Componentes Excel Delphi Compatíveis com C++Builder

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

Comparação de pipelines de compilação para o componente HotXLS em Delphi: o dcc32 do Delphi compila implicitamente uma unit não listada no pacote e termina com sucesso, enquanto o MSBuild do C++Builder nunca gera o seu ficheiro objeto e o ILINK32 aborta com um external não resolvido
A mesma unidade não listada segue dois caminhos divergentes: o dcc32 mascara a omissão através de ligação estática implícita, enquanto o linker mais rigoroso do C++Builder a expõe

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}
  // Injeta um pragma link no ficheiro de cabeçalho C++ gerado
  // Isto obriga o vinculador C++ a incluir o ficheiro .obj correspondente
  {$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;
  // Lógica de extração de metadados aqui
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

Fluxo de quatro colunas do mecanismo de ligação por pragma HPPEMIT em Delphi: uma diretiva declarada faz o dcc32 injetar a ordem do linker em lxXlsSummary.hpp, o C++Builder consome-a ao incluir, e o ILINK32 resolve XlsReadSummaryInformation automaticamente
A ordem do linker escreve-se uma vez no código-fonte Pascal, viaja dentro do cabeçalho gerado e chega a todos os builds C++ futuros sem edições manuais do projeto

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