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