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

Comparación del pipeline de compilación del componente HotXLS para Delphi: dcc32 de Delphi compila implícitamente una unit no listada dentro del paquete y compila con éxito, mientras el MSBuild de C++Builder nunca genera su archivo objeto y ILINK32 aborta con un external sin resolver
La misma unidad no listada toma dos caminos divergentes: dcc32 enmascara la omisión mediante la vinculación estática implícita, mientras que el enlazador más estricto de C++Builder la expone

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}
  // Inyecta un pragma link en el archivo de encabezado C++ generado
  // Esto obliga al enlazador de C++ a incluir el .obj correspondiente
  {$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 extracción de metadatos aquí
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

Flujo de cuatro columnas del mecanismo de enlace de pragma HPPEMIT en Delphi: una directiva declarada hace que dcc32 inyecte el orden del enlazador en lxXlsSummary.hpp, C++Builder lo consume al incluirlo, e ILINK32 resuelve XlsReadSummaryInformation automáticamente
El orden del enlazador se escribe una vez en el fuente Pascal, viaja dentro del header generado y llega a cada build futura de C++ sin ediciones manuales del proyecto

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 Delphi VCL Component