Artigo Técnico

Compilação Cruzada de Componentes Delphi para C++Builder: Evitando Externals Não Resolvidos

Ao manter um componente VCL escrito em Delphi mas consumido tanto por usuários de Delphi quanto de C++Builder, você rapidamente percebe que os dois sistemas de build tratam as dependências de forma muito diferente. Uma unidade que compila perfeitamente no Delphi pode causar erros catastróficos de vinculador (linker) no C++Builder. Essa discrepância é uma armadilha frequente para autores de componentes, especialmente ao adicionar novas unidades internas a uma biblioteca existente

Aqui está um detalhamento minucioso de por que isso acontece, usando exemplos do mundo real do componente HotXLS, e como blindar seu processo de compilação cruzada usando inclusões explícitas, {$HPPEMIT} e vinculação de pragma

A Armadilha: Compilação Implícita vs. Inclusão Explícita

Suponha que você crie uma nova unidade Delphi, lxXlsSummary.pas, para lidar com metadados de documentos, e você a usa (uses) na sua unidade de análise principal, lxRead.pas. Você clica em compilar no IDE do Delphi, o build fica verde e você envia a atualização

No dia seguinte, seus usuários do C++Builder relatam um erro durante a fase de vinculação: Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj

O Jeito Delphi (dcc32)

Quando o compilador Delphi processa um pacote (.dpk), ele analisa as unidades listadas explicitamente na cláusula contains. Se uma dessas unidades usa (uses) uma unidade externa (como lxXlsSummary.pas) que não está na lista contains, o compilador Delphi executa a vinculação estática implícita. Ele simplesmente encontra o arquivo .pas no caminho de pesquisa, compila-o em um .dcu e o incorpora no .bpl resultante. O build é bem-sucedido, mascarando completamente a omissão

O Jeito C++Builder (MSBuild / .cbproj)

O sistema de build do C++Builder é muito mais rigoroso. Ele gera arquivos de objeto C++ (.obj) e cabeçalhos (.hpp) apenas para as unidades Delphi listadas explicitamente no grupo de itens <DelphiCompile> do arquivo .cbproj. Como lxXlsSummary.pas nunca foi registrado explicitamente no arquivo do projeto, nenhum lxXlsSummary.obj é criado. Quando o vinculador tenta resolver as chamadas feitas por lxRead.obj, os símbolos estão ausentes, levando a um erro de external não resolvido

Resolvendo Externals com Pragma Link e HPPEMIT

Se você deseja garantir que uma unidade seja vinculada corretamente em C++ sem forçar o usuário a adicionar manualmente o arquivo .obj ao seu projeto, você pode usar a diretiva {$HPPEMIT} do Delphi. Ela instrui o compilador Delphi a injetar uma diretiva C++ #pragma link específica no arquivo .hpp gerado

unit lxXlsSummary;

interface

{$IFDEF WINDOWS}
  // Injeta um pragma link no arquivo de cabeçalho C++ gerado
  // Isso força o vinculador C++ a incluir o arquivo .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 automaticamente diz ao vinculador (ILINK32/ILINK64) para resolver os símbolos a partir de lxXlsSummary.obj

A Regra de Ouro para a Manutenção de Componentes

Para evitar quebrar completamente os builds do C++Builder, você deve adotar uma política de registro estrita. Sempre que uma nova unidade Pascal for adicionada à sua biblioteca, ela deverá ser registrada explicitamente em todos os três tipos de arquivo de projeto simultaneamente

1. Atualize o Projeto C++Builder (.cbproj / .bpk)

Abra o arquivo .cbproj em um editor de texto e adicione a nova unidade à lista de compilação, garantindo que você forneça uma ordem de build exclusiva. Se estiver usando versões mais antigas do C++Builder com arquivos .bpk, certifique-se de que a tag <file containerid="PascalCompiler" designclass="" filename="lxXlsSummary.pas" formname="" localcommand="" unitname="lxXlsSummary"></file> seja adicionada

<DelphiCompile Include="lxXlsSummary.pas">
  <BuildOrder>101</BuildOrder>
</DelphiCompile>

2. Atualize o Pacote Delphi (.dpk)

Adicione a unidade à cláusula contains explícita. Isso garante que o compilador Delphi não tenha que depender da vinculação implícita, que de qualquer forma é geralmente considerada uma prática ruim

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 por Integração Contínua

A defesa definitiva contra essa armadilha é a validação de CI/CD. Nunca confie apenas em um build Delphi bem-sucedido antes de enviar um componente de linguagem dupla. Seus scripts de build devem invocar o MSBuild ou as ferramentas de linha de comando bcc32c nos projetos C++Builder (por exemplo, build-Win32-Lib-CB.cmd) e executar um link completo das demonstrações de avaliação e completas em C++. Somente quando o vinculador C++ é bem-sucedido você pode ter certeza de que todas as unidades Delphi estão registradas corretamente e expondo seus símbolos ao runtime do C++

Nota: A compatibilidade do compilador multiplataforma é estritamente mantida nas edições Delphi e C++Builder do HotXLS VCL Component