Delphi로 작성되었지만 Delphi와 C++Builder 사용자 모두가 사용하는 VCL 구성 요소를 유지 관리할 때 두 빌드 시스템이 종속성을 매우 다르게 처리한다는 사실을 금방 깨닫게 됩니다. Delphi에서는 완벽하게 컴파일되는 유닛이 C++Builder에서는 치명적인 링커 오류를 일으킬 수 있습니다. 이러한 차이는 특히 기존 라이브러리에 새로운 내부 유닛을 추가할 때 구성 요소 개발자가 자주 빠지는 함정입니다
이 기사에서는 HotXLS 구성 요소의 실제 사례를 사용하여 이러한 현상이 발생하는 이유를 자세히 설명하고, 명시적 포함, {$HPPEMIT} 및 pragma 링크를 사용하여 크로스 컴파일 프로세스를 완벽하게 보호하는 방법을 살펴봅니다
함정: 암묵적 컴파일 대 명시적 포함
문서 메타데이터를 처리하기 위해 새로운 Delphi 유닛인 lxXlsSummary.pas를 만들고 주 파싱 유닛인 lxRead.pas의 uses 절에 추가한다고 가정해 보겠습니다. Delphi IDE에서 컴파일을 누르면 빌드가 성공적으로 완료되고 업데이트를 배포하게 됩니다
다음날 C++Builder 사용자로부터 링크 단계 중 Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj 오류가 발생한다는 보고가 들어옵니다
Delphi 방식 (dcc32)
Delphi 컴파일러가 패키지(.dpk)를 처리할 때 contains 절에 명시적으로 나열된 유닛을 찾습니다. 이 유닛 중 하나가 contains 목록에 없는 외부 유닛(예: lxXlsSummary.pas)을 uses 절로 사용할 경우 Delphi 컴파일러는 암묵적 정적 링크(implicit static linking)를 수행합니다. 검색 경로에서 .pas 파일을 찾아 .dcu로 컴파일하고 결과 .bpl에 포함시킵니다. 빌드가 성공하며 이러한 누락이 완전히 감춰집니다
C++Builder 방식 (MSBuild / .cbproj)
C++Builder의 빌드 시스템은 훨씬 엄격합니다. .cbproj 파일의 <DelphiCompile> 항목 그룹에 명시적으로 나열된 Delphi 유닛에 대해서만 C++ 객체 파일(.obj) 및 헤더(.hpp)를 생성합니다. lxXlsSummary.pas가 프로젝트 파일에 명시적으로 등록되지 않았기 때문에 lxXlsSummary.obj가 생성되지 않습니다. 링커가 lxRead.obj에 의한 호출을 해결하려고 시도할 때 기호가 누락되어 미해결 외부 오류(unresolved external error)가 발생합니다
Pragma Link 및 HPPEMIT로 외부 참조 해결
사용자가 프로젝트에 .obj 파일을 수동으로 추가하도록 강요하지 않고 C++에서 유닛이 올바르게 링크되도록 하려면 Delphi의 {$HPPEMIT} 지시문을 사용할 수 있습니다. 이는 Delphi 컴파일러에게 생성된 .hpp 파일에 특정 C++ #pragma link 지시문을 삽입하도록 지시합니다
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 파일을 열고 컴파일 목록에 새 유닛을 추가하여 고유한 빌드 순서를 지정해야 합니다. .bpk 파일을 사용하는 이전 C++Builder 버전의 경우 <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) 검증
이러한 함정에 대한 궁극적인 방어 수단은 CI/CD 검증입니다. 이중 언어 구성 요소를 출시하기 전에 Delphi 빌드 성공 여부에만 의존해서는 안 됩니다. 빌드 스크립트는 C++Builder 프로젝트(예: build-Win32-Lib-CB.cmd)에서 MSBuild 또는 bcc32c 명령줄 도구를 호출하고 C++ 평가판 및 전체 데모의 완벽한 링크를 실행해야 합니다. C++ 링커가 성공할 때만 모든 Delphi 유닛이 올바르게 등록되고 기호가 C++ 런타임에 노출되었음을 확신할 수 있습니다
참고: 크로스 플랫폼 컴파일러 호환성은 HotXLS VCL Component의 Delphi 및 C++Builder 버전에서 엄격하게 유지 관리됩니다