Technical Article

Compilación cruzada de componentes de Delphi para C++Builder: cómo evitar errores externos no resueltos

Al mantener un componente VCL escrito en Delphi pero consumido tanto por usuarios de Delphi como de C++Builder, uno se da cuenta rápidamente de que los dos sistemas de compilación tratan las dependencias de manera muy diferente. Una unidad que se compila perfectamente en Delphi podría causar errores catastróficos en el enlazador en C++Builder. Esta discrepancia es una trampa frecuente para los autores de componentes, particularmente al agregar nuevas unidades internas a una biblioteca existente.

A continuación, se presenta un desglose detallado de por qué sucede esto (usando ejemplos del mundo real del componente HotXLS) y cómo hacer que su proceso de compilación cruzada sea a prueba de fallos mediante el uso de inclusiones explícitas, {$HPPEMIT} y la vinculación de pragmas.

La trampa: compilación implícita frente a inclusión explícita

Supongamos que crea una nueva unidad de Delphi, lxXlsSummary.pas, para manejar los metadatos del documento, y hace un uses de la misma en su unidad de análisis principal, lxRead.pas. Pulsa compilar en el IDE de Delphi, la compilación se muestra en verde y envía la actualización.

Al día siguiente, sus usuarios de C++Builder informan de un error durante la fase de vinculación: Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj.

Al estilo Delphi (dcc32)

Cuando el compilador de Delphi procesa un paquete (.dpk), examina las unidades enumeradas explícitamente en la cláusula contains. Si una de esas unidades hace un uses de una unidad externa (como lxXlsSummary.pas) que no está en la lista contains, el compilador de Delphi realiza una vinculación estática implícita. Simplemente encuentra el archivo .pas en la ruta de búsqueda, lo compila en un .dcu y lo integra en el .bpl resultante. La compilación tiene éxito, ocultando por completo la omisión.

Al estilo C++Builder (MSBuild / .cbproj)

El sistema de compilación de C++Builder es mucho más estricto. Solo genera archivos de objetos de C++ (.obj) y encabezados (.hpp) para las unidades de Delphi enumeradas explícitamente en el grupo de elementos <DelphiCompile> del archivo .cbproj. Debido a que lxXlsSummary.pas nunca se registró explícitamente en el archivo del proyecto, no se crea ningún lxXlsSummary.obj. Cuando el enlazador intenta resolver las llamadas realizadas por lxRead.obj, faltan los símbolos, lo que genera un error externo no resuelto.

Resolución de externos con Pragma Link y HPPEMIT

Si desea asegurarse de que una unidad esté vinculada correctamente en C++ sin obligar al usuario a agregar manualmente el archivo .obj a su proyecto, puede usar la directiva {$HPPEMIT} de Delphi. Esto le indica al compilador de Delphi que inyecte una directiva #pragma link de C++ 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 encuentra el #pragma link y automáticamente le indica al enlazador (ILINK32/ILINK64) que resuelva los símbolos de lxXlsSummary.obj.

La regla de oro para el mantenimiento de componentes

Para evitar interrumpir por completo las compilaciones de C++Builder, debe adoptar una estricta política de registro. Siempre que se agregue una nueva unidad Pascal a su biblioteca, debe registrarse explícitamente en los tres tipos de archivos de proyecto simultáneamente.

1. Actualizar 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 proporcionar un orden de compilación único. Si utiliza versiones anteriores de C++Builder con archivos `.bpk`, asegúrese de agregar la etiqueta `<file containerid="PascalCompiler" designclass="" filename="lxXlsSummary.pas" formname="" localcommand="" unitname="lxXlsSummary"></file>`.

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

2. Actualizar el paquete Delphi (.dpk)

Agregue la unidad a la cláusula explícita contains. Esto garantiza que el compilador de Delphi no tenga que depender de la vinculación implícita, que de todos modos se considera una mala práctica en general.

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 de integración continua

La máxima defensa contra esta trampa es la validación CI/CD. Nunca confíe únicamente en una compilación exitosa de Delphi antes de enviar un componente bilingüe. Sus scripts de compilación deben invocar MSBuild o las herramientas de línea de comandos bcc32c en los proyectos de C++Builder (por ejemplo, build-Win32-Lib-CB.cmd) y ejecutar un enlace completo de la versión de prueba de C++ y las demostraciones completas. Solo cuando el enlazador de C++ tiene éxito, puede estar seguro de que todas las unidades de Delphi están registradas correctamente y exponen sus símbolos al entorno de ejecución de C++.

Nota: La compatibilidad del compilador multiplataforma se mantiene estrictamente en las ediciones para Delphi y C++Builder del componente HotXLS VCL.