기술 문서

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에 포함시킵니다. 빌드가 성공하며 이러한 누락이 완전히 감춰집니다

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 버전에서 엄격하게 유지 관리됩니다