기술 문서

C++Builder를 위한 Delphi 구성 요소 크로스 컴파일: 미해결 외부 참조 피하기

Delphi 컴파일러가 암묵적으로 포함하는 유닛은 C++Builder의 링크 입력에 자동으로 나타나지 않습니다. 이 글은 공개 헤더와 구현 유닛의 경계를 정하고, {$HPPEMIT}와 pragma link로 필요한 객체 파일을 명시하는 유지 관리 규칙을 정리합니다

Delphi로 작성되었지만 Delphi와 C++Builder 사용자 모두가 사용하는 VCL 구성 요소를 유지 관리할 때 두 빌드 시스템이 종속성을 매우 다르게 처리한다는 사실을 금방 깨닫게 됩니다. Delphi에서는 완벽하게 컴파일되는 유닛이 C++Builder에서는 치명적인 링커 오류를 일으킬 수 있습니다. 이러한 차이는 특히 기존 라이브러리에 새로운 내부 유닛을 추가할 때 구성 요소 개발자가 자주 빠지는 함정입니다

이 기사에서는 HotXLS 구성 요소의 실제 사례를 사용하여 이러한 현상이 발생하는 이유를 자세히 설명하고, 명시적 포함, {$HPPEMIT} 및 pragma 링크를 사용하여 크로스 컴파일 프로세스를 완벽하게 보호하는 방법을 살펴봅니다

함정: 암묵적 컴파일 대 명시적 포함

문서 메타데이터를 처리하기 위해 새로운 Delphi 유닛인 lxXlsSummary.pas를 만들고 주 파싱 유닛인 lxRead.pasuses 절에 추가한다고 가정해 보겠습니다. 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에 포함시킵니다. 빌드가 성공하며 이러한 누락이 완전히 감춰집니다

HotXLS Delphi 컴포넌트 빌드 파이프라인 비교: Delphi dcc32는 목록에 없는 유닛을 암묵적으로 패키지에 컴파일해 빌드에 성공하는 반면, C++Builder MSBuild는 해당 객체 파일을 만들지 않아 ILINK32가 unresolved external로 중단
나열되지 않은 같은 유닛이 두 갈래로 갈라집니다. dcc32는 암묵적 정적 링킹으로 누락을 가리는 반면, 더 엄격한 C++Builder 링커는 이를 드러냅니다

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}
  // 생성된 C++ 헤더 파일에 pragma link를 삽입합니다
  // 이렇게 하면 C++ 링커가 해당 .obj 파일을 포함하도록 강제됩니다
  {$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;
  // 여기에 메타데이터 추출 로직
end;

end.

C++Builder가 lxXlsSummary.hpp를 포함할 때 컴파일러는 #pragma link를 만나고 자동으로 링커(ILINK32/ILINK64)에게 lxXlsSummary.obj의 기호를 해결하도록 지시합니다

Delphi HPPEMIT pragma 링크 메커니즘의 4열 흐름: 선언된 디렉티브가 dcc32로 하여금 링커 순서를 lxXlsSummary.hpp에 주입하게 하고, C++Builder는 include 시 이를 소비하며, ILINK32는 XlsReadSummaryInformation을 자동으로 해석
링커 순서는 Pascal 소스에 한 번 기록되고, 생성된 헤더 안을 이동하여, 수동 프로젝트 편집 없이 이후 모든 C++ 빌드에 도달합니다

구성 요소 유지 관리의 황금률

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 Delphi VCL Component의 Delphi 및 C++Builder 버전에서 엄격하게 유지 관리됩니다