Artículo técnico

Delphi para C++Builder: cómo evitar externos sin resolver

Cuando se mantiene un componente VCL escrito en Delphi y usado tanto desde Delphi como desde C++Builder, queda claro muy rápido que ambos compiladores resuelven las dependencias de manera distinta. Una unidad que compila sin problemas en Delphi puede terminar en errores de enlace en C++Builder. Ese desfase suele aparecer justo cuando se agregan unidades internas nuevas a una biblioteca que ya estaba en producción

Este artículo desglosa por qué ocurre eso, con ejemplos tomados del componente HotXLS, y muestra cómo proteger el flujo de compilación con inclusiones explícitas, {$HPPEMIT} y directivas de enlace

El problema: compilación implícita frente a inclusión explícita

Suponga que crea una nueva unidad de Delphi, lxXlsSummary.pas, para manejar metadatos de documentos, y la agrega con uses en la unidad principal de análisis, lxRead.pas. Usted compila en el IDE de Delphi, todo queda en verde y publica la actualización

Al día siguiente, quienes usan C++Builder reportan un fallo en la fase de enlace: Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj

Cómo lo ve Delphi (dcc32)

Cuando el compilador de Delphi procesa un paquete (.dpk), toma en cuenta las unidades que aparecen de forma explícita en la cláusula contains. Si una de esas unidades usa otra unidad externa, como lxXlsSummary.pas, que no está en esa lista, Delphi hace una vinculación estática implícita. El archivo .pas se toma de la ruta de búsqueda, se compila a .dcu y se incorpora al .bpl final. El resultado parece correcto y oculta por completo la omisión

Cómo lo ve C++Builder (MSBuild / .cbproj)

C++Builder es más estricto. Solo genera archivos objeto de C++ (.obj) y encabezados (.hpp) para las unidades Delphi que aparecen explícitamente en el grupo de elementos <DelphiCompile> del archivo .cbproj. Como lxXlsSummary.pas nunca quedó registrado de forma explícita en el proyecto, no se crea ningún lxXlsSummary.obj. Cuando el enlazador intenta resolver las llamadas emitidas por lxRead.obj, los símbolos no están y aparece el error de externo no resuelto

Cómo resolver externos con pragma link y HPPEMIT

Si quiere que una unidad quede enlazada correctamente en C++ sin obligar a cada usuario a agregar manualmente el archivo .obj a su proyecto, puede usar la directiva {$HPPEMIT} de Delphi. Eso hace que el compilador inserte una directiva C++ #pragma link específica en el archivo .hpp generado

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.

Cuando C++Builder incluye lxXlsSummary.hpp, el compilador detecta el #pragma link y le indica de inmediato al enlazador (ILINK32/ILINK64) que resuelva los símbolos de lxXlsSummary.obj

La regla básica para mantener componentes

Para no romper las compilaciones de C++Builder, conviene seguir una política estricta de registro. Cada vez que se agrega una nueva unidad Pascal a la biblioteca, debe registrarse explícitamente en los tres tipos de archivo de proyecto al mismo tiempo

1. Actualice el proyecto de C++Builder (.cbproj / .bpk)

Abra el archivo .cbproj en un editor de texto y agregue la nueva unidad a la lista de compilación, asegurándose de definir un orden de compilación único. Si trabaja con versiones anteriores de C++Builder que usan archivos .bpk, agregue la etiqueta <file containerid="PascalCompiler" designclass="" filename="lxXlsSummary.pas" formname="" localcommand="" unitname="lxXlsSummary"></file>

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

2. Actualice el paquete de Delphi (.dpk)

Agregue la unidad a la cláusula contains explícita. Así Delphi no tiene que depender de la vinculación implícita, que por lo general se considera una mala práctica

package HotXLS;

{$R *.res}
{$ALIGN 8}
{$ASSERTIONS ON}
{$BOOLEVAL OFF}

requires
  rtl,
  vcl;

contains
  lxRead in 'lxRead.pas',
  lxXlsSummary in 'lxXlsSummary.pas';

end.

Validación continua

La mejor defensa contra este problema es validar en CI/CD. No conviene confiar solo en que Delphi compile bien antes de publicar un componente para dos lenguajes. Los scripts de compilación deben invocar MSBuild o las herramientas de línea de comandos bcc32c sobre los proyectos de C++Builder (por ejemplo, build-Win32-Lib-CB.cmd) y ejecutar un enlace completo de la versión de prueba y de las demos completas de C++. Solo cuando el enlazador de C++ termina bien puede darse por hecho que todas las unidades de Delphi quedaron registradas y exponen sus símbolos al entorno de ejecución de C++

Nota: La compatibilidad de compilación multiplataforma se mantiene de forma estricta en las ediciones Delphi y C++Builder del HotXLS VCL Component