Когда вы сопровождаете VCL-компонент, написанный на Delphi, но используемый как пользователями Delphi, так и пользователями C++Builder, вы быстро понимаете, что две системы сборки обрабатывают зависимости совершенно по-разному. Модуль, который прекрасно компилируется в Delphi, может вызвать катастрофические ошибки компоновщика в C++Builder. Это расхождение — частая ловушка для авторов компонентов, особенно при добавлении новых внутренних модулей в существующую библиотеку
Ниже приведён подробный разбор того, почему это происходит, на реальных примерах из компонента HotXLS, и как сделать процесс кросс-компиляции неуязвимым с помощью явных включений, {$HPPEMIT} и pragma-линковки
Ловушка: неявная компиляция против явного включения
Предположим, вы создаёте новый модуль Delphi, lxXlsSummary.pas, для обработки метаданных документа и подключаете его через uses в вашем основном модуле разбора, lxRead.pas. Вы нажимаете «Compile» в IDE Delphi, сборка становится зелёной, и вы выпускаете обновление
На следующий день пользователи C++Builder сообщают об ошибке на этапе компоновки: Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj
Подход Delphi (dcc32)
Когда компилятор Delphi обрабатывает пакет (.dpk), он смотрит на модули, явно перечисленные в секции contains. Если один из этих модулей через uses подключает внешний модуль (например, lxXlsSummary.pas), которого нет в списке contains, компилятор Delphi выполняет неявную статическую линковку. Он просто находит файл .pas в пути поиска, компилирует его в .dcu и встраивает в итоговый .bpl. Сборка проходит успешно, полностью маскируя пропуск
Подход C++Builder (MSBuild / .cbproj)
Система сборки C++Builder намного строже. Она генерирует объектные файлы C++ (.obj) и заголовки (.hpp) только для тех модулей Delphi, которые явно перечислены в группе элементов <DelphiCompile> файла .cbproj. Поскольку lxXlsSummary.pas никогда не был явно зарегистрирован в файле проекта, файл lxXlsSummary.obj не создаётся. Когда компоновщик пытается разрешить вызовы, сделанные из lxRead.obj, символы отсутствуют, что приводит к ошибке неразрешённого внешнего символа
Разрешение внешних символов с помощью Pragma Link и HPPEMIT
Если вы хотите гарантировать, что модуль правильно линкуется в C++ без необходимости вручную добавлять файл .obj в проект пользователя, вы можете использовать директиву Delphi {$HPPEMIT}. Она указывает компилятору Delphi внедрить конкретную директиву C++ #pragma link в сгенерированный файл .hpp
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.
Когда C++Builder включает lxXlsSummary.hpp, компилятор встречает #pragma link и автоматически сообщает компоновщику (ILINK32/ILINK64) о необходимости разрешить символы из lxXlsSummary.obj
Золотое правило сопровождения компонентов
Чтобы полностью избежать поломки сборок C++Builder, вы должны принять строгую политику регистрации. Каждый раз, когда в вашу библиотеку добавляется новый модуль Pascal, он должен быть явно зарегистрирован одновременно во всех трёх типах файлов проектов
1. Обновите проект C++Builder (.cbproj / .bpk)
Откройте файл .cbproj в текстовом редакторе и добавьте новый модуль в список компиляции, обеспечив уникальный порядок сборки. Если вы используете более старые версии C++Builder с файлами .bpk, убедитесь, что добавлен тег <file containerid="PascalCompiler" designclass="" filename="lxXlsSummary.pas" formname="" localcommand="" unitname="lxXlsSummary"></file>
<DelphiCompile Include="lxXlsSummary.pas">
<BuildOrder>101</BuildOrder>
</DelphiCompile>
2. Обновите пакет Delphi (.dpk)
Добавьте модуль в явную секцию contains. Это гарантирует, что компилятору Delphi не придётся полагаться на неявную линковку, которая в любом случае считается плохой практикой
package HotXLS;
{$R *.res}
{$ALIGN 8}
{$ASSERTIONS ON}
{$BOOLEVAL OFF}
requires
rtl,
vcl;
contains
lxRead in 'lxRead.pas',
lxXlsSummary in 'lxXlsSummary.pas';
end.
Проверка через непрерывную интеграцию
Главная защита от этой ловушки — валидация CI/CD. Никогда не полагайтесь исключительно на успешную сборку Delphi перед выпуском двуязычного компонента. Ваши сценарии сборки должны вызывать MSBuild или инструменты командной строки bcc32c для проектов C++Builder (например, build-Win32-Lib-CB.cmd) и выполнять полную компоновку триальных и полных демонстраций C++. Только когда компоновщик C++ завершается успешно, вы можете быть уверены, что все модули Delphi корректно зарегистрированы и предоставляют свои символы среде выполнения C++
Примечание: совместимость между компиляторами строго поддерживается в редакциях Delphi и C++Builder HotXLS VCL Component