디자인 타임에 THotPDF를 폼에 놓는 것은 빠른 프로토타입 제작에는 좋지만, 컴포넌트를 폼의 수명에 묶어두게 되며, 이는 프로덕션 코드에서 원하는 경우가 거의 없습니다. 버튼 클릭당 한 번 실행되는 보고서 생성기, 밤새 수행되는 내보내기를 일괄 처리하는 서비스 스레드, 폼이 전혀 없는 도우미 클래스: 이 각각의 상황에서 컴포넌트가 정확히 하나의 PDF 작업 기간 동안만 존재했다가 사라지기를 원할 것입니다. 이는 런타임 할당을 의미하며, 첫 줄을 작성하기 전에 이해할 가치가 있는 두 가지, 즉 누가 객체를 소유하는지와 무언가 잘못되었을 때 정리가 어떻게 실행되는지를 변경합니다
VCL의 소유자(Owner) 의미론
모든 VCL 컴포넌트 생성자는 TComponent* 타입의 Owner 매개변수를 사용합니다. this(폼)를 전달하면 새 객체가 폼의 소유된 컴포넌트 목록에 등록되므로 컴포넌트가 아직 살아 있는 동안 폼이 파괴되면 VCL이 컴포넌트를 자동으로 해제합니다. nullptr을 전달한다는 것은 소유자가 없음을 의미합니다. 즉, 포인터에 대한 전적인 책임을 지며, 명시적 delete 이전에 예외가 스택을 풀면(unwind) 아무것도 여러분을 위해 포인터를 정리해주지 않습니다
단일 함수 내에서 완료되는 일회성 내보내기의 경우 두 가지 선택 모두 효과가 있지만 두 가지 오류 모드(failure modes)가 다릅니다. 소유자로 this를 사용하는 경우 결국 폼이 닫히는 한 누수(leak)는 불가능합니다. nullptr을 사용하는 경우 포인터가 반드시 __finally 블록에 도달해야 합니다. 실제로 임시로 사용하기 위한 객체의 경우 nullptr 더하기 __finally 패턴이 수명 경계를 한눈에 볼 수 있게 하고 폼에 일시적으로 의도된 소유된 객체가 누적되는 것을 방지하므로 약간 더 깔끔합니다
예외에 안전한 구조
PDF 생성은 API와 아무런 관련이 없는 이유로 실패할 수 있습니다: 출력 디렉토리가 읽기 전용이거나, 글꼴 파일이 누락되었거나, 스트림이 조기에 플러시되거나, 호출자가 제공한 데이터가 길이 제한에 도달할 수 있습니다. 원인이 무엇이든 정리(cleanup) 경로는 실행되어야 합니다. 이를 보장하는 관용적인 C++Builder 방식은 try/__finally입니다:
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#pragma package(smart_init)
#pragma link "HPDFDoc"
#pragma resource "*.dfm"
TForm1 *Form1;
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
void __fastcall TForm1::Button1Click(TObject *Sender)
{
THotPDF* Pdf = new THotPDF(nullptr);
try
{
Pdf->FileName = "output.pdf";
Pdf->Compression = cmFlateDecode;
Pdf->FontEmbedding = true;
Pdf->BeginDoc();
Pdf->CurrentPage->SetFont("Arial", TFontStyles(), 12);
Pdf->CurrentPage->TextOut(72, 720, 0, L"Hello from C++Builder");
Pdf->EndDoc();
}
__finally
{
delete Pdf;
}
}
이 목록에서 몇 가지 사항을 짚고 넘어갈 가치가 있습니다. 소유자는 nullptr로 수명을 명시적으로 만듭니다. Compression 및 FontEmbedding은 BeginDoc 이전에 설정됩니다: 둘 다 HotPDF가 문서를 열 때 커밋하는 문서 수준 옵션이며 이후에 할당하는 것은 효과가 없습니다. TextOut은 페이지의 왼쪽 하단 모서리에서 측정한 포인트 단위의 좌표를 사용하고 Y는 위로 증가합니다. 72, 720 쌍은 1인치 왼쪽 여백이 있는 레터 크기 페이지의 왼쪽 상단 근처에 텍스트를 배치합니다. __finally 블록의 delete Pdf는 BeginDoc, 그리기 또는 EndDoc이 예외를 발생시키든 아니든 실행됩니다
delete 이후에 Pdf의 메서드를 호출하지 마세요. 포인터가 멤버 변수에 저장된 경우, 삭제 직후에 nullptr로 설정하여 이후 실수로 액세스하면 조용한 손상이 아닌 깔끔한 충돌(crash)이 발생하도록 하세요
프로젝트 구성
C++Builder는 포함 경로(include path), 라이브러리 경로 및 pragma 지시문의 조합을 통해 THotPDF를 찾습니다. 생성된 헤더는 HotPDF 소스 디렉토리의 HPDFDoc.pas와 함께 존재합니다. 해당 디렉토리를 Project > Options > C++ Compiler > Include path에 추가하세요. #pragma link "HPDFDoc" 지시문은 수동으로 프로젝트 파일에 나열하지 않고도 컴파일된 유닛을 가져오도록 링커에 지시합니다. 정적 링킹 대신 런타임 패키지를 사용하는 경우 HotPDF 디자인 및 런타임 패키지를 먼저 설치하세요. pragma는 여전히 적용됩니다
유닛 이름 HPDFDoc를 변경하지 않은 상태로 유지하세요. C++Builder는 파스칼 유닛 이름에서 헤더 이름을 파생하므로 파일 이름을 바꾸거나 pragma에서 경로 별칭을 사용하면 조용히 조회(lookup)가 중단됩니다
범위 지정(Scoping) 및 다중 문서 작업
사용자 작업에 의해 트리거되는 단일 내보내기의 경우 버튼 처리기로 범위가 지정된 로컬 변수가 정답입니다: 생성되고 사용되며 하나의 호출 프레임 내에서 파괴되고, 나중에 코드를 읽는 누구에게나 의도가 분명합니다. 사용자가 설정을 변경할 때마다 문서를 다시 작성하는 인쇄 미리 보기 패널과 같이 동일한 폼이 지속적인 워크플로를 구동할 때 디자인 타임 대안이 정당화됩니다. 이 경우 컴포넌트를 활성 상태로 유지하고 BeginDoc/EndDoc을 반복적으로 호출하는 것이 힙 객체를 반복적으로 할당하고 해제하는 것보다 덜 파괴적입니다
순차적으로 많은 문서를 생성하는 일괄 작업의 경우 문서당 하나의 THotPDF 범위를 지정하는 것은 할당 오버헤드를 감수할 가치가 있습니다. 상태를 전달할 객체가 없으면 상태는 문서 간에 전달되지 않으며, 이는 디버그할 필요가 없는 일종의 간헐적인 버그입니다. 할당, 생성, 삭제, 반복하세요
여러 HotPDF 데모에 나타나는 한 가지 속성은 AutoLaunch로, 생성된 파일을 EndDoc 직후에 시스템 PDF 뷰어에서 엽니다. 이는 레이아웃의 첫 번째 초안을 작성하는 동안 유용합니다. 프로덕션에서는 이를 건너뛰세요: 명시적으로 출력 경로를 열고, 파일이 존재하는지 0이 아닌 크기인지 확인하고, 결과를 기록하고, 호출하는 워크플로가 뷰어 관련 여부를 결정하도록 하세요. 일괄 작업에서 AutoLaunch는 문서당 하나의 뷰어 창을 시작하고 뷰어가 닫힐 때까지 대기하는 일부 시스템의 프로세스를 차단합니다
여기에 표시된 THotPDF 컴포넌트 및 모든 그리기 호출은 델파이 및 C++Builder용 HotPDF 컴포넌트의 일부입니다