Notice
Recent Posts
Recent Comments
Link
«   2026/06   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Archives
Today
Total
관리 메뉴

Blog

[Delphi] BPL 설정 및 사용법 본문

카테고리 없음

[Delphi] BPL 설정 및 사용법

ggi88 2026. 5. 27. 09:36

BPL(Borland Package Library) 모듈화 아키텍처 정리임


1. BPL(Borland Package Library) 개요

  • 정의: 델파이 전용 DLL. 공통 코드, 컴포넌트, 화면(Form)을 별도 파일로 분리하여 메인 프로그램(.exe) 용량 최적화 및 모듈별 독립 업데이트 가능.
  • 로딩 방식:
  • 정적 로딩 (Runtime Packages): 앱 시작 시 필요한 BPL을 메모리에 자동 로드. 구현이 단순하나 초기 로딩 속도와 메모리 소모 증가.
  • 동적 로딩 (Dynamic Loading): 특정 메뉴 실행 시에만 BPL을 메모리에 올리고 사용 후 해제. 대규모 프로젝트 표준 방식.

2. 실무 분리 개발의 핵심: OpenForm 패턴

BPL 모듈화 프로젝트에서 주로 사용되는 화면 호출 함수 형태임.

function OpenCustomerForm(AOwner: TComponent; ALink: IMyInterface): TForm; stdcall;
begin
  Result := TCustomerForm.Create(AOwner, ALink); 
end;

exports
  OpenCustomerForm;

주요 용도

  • 느슨한 결합(Loose Coupling): 메인 프로그램(.exe)이 BPL 내부 화면 구조를 몰라도 필요할 때 동적으로 화면 생성 가능.
  • 독립 배포: 특정 화면 버그 수정 시 전체 EXE 재빌드 없이 해당 BPL 파일만 새로 빌드하여 교체(패치) 가능.
  • 코드 매커니즘: Create(Application, ALink); 구조를 통해 메인 프로그램 종료 시 자동 해제(Owner)되도록 설정하고, 인터페이스(ALink)로 메인 세션 데이터를 화면에 안전하게 전달함.

3. Delphi IDE 내 BPL 프로젝트 설정 및 사용 예시

BPL 패키지를 만들고 메인 프로그램과 연결하는 델파이 IDE 환경 설정 단계임.

1단계: BPL 패키지 프로젝트 생성 및 빌드

  1. 패키지 생성: Delphi 메뉴에서 File -> New -> Package 선택 후 프로젝트 명을 MyCustomPkg로 저장함.
  2. 화면 및 유닛 추가: 프로젝트 매니저에서 패키지 우클릭 후 화면(TForm) 또는 일반 유닛을 추가함. (예: openForm 함수가 포함된 유닛)
  3. 컴파일 및 빌드: 패키지 프로젝트 우클릭 후 Build를 실행함.
  • 성공 시 프로젝트 출력 디렉토리에 MyCustomPkg.bpl(실행용 런타임 파일)과 MyCustomPkg.dcp(컴파일 참조용 델파이 컴포넌트 패키지 파일)가 생성됨.

2단계: 메인 프로그램(.exe) 프로젝트 설정 (정적 로딩 예시)

메인 프로그램에서 BPL 유닛을 일반 유닛처럼 직접 참조(uses)하여 사용하기 위한 필수 설정임.

  1. 런타임 패키지 활성화: 메인 프로젝트가 열린 상태에서 Project -> Options -> Delphi Compiler -> Packages -> Runtime Packages 메뉴로 이동함.
  2. Link with runtime packages 항목을 True로 변경함.
  3. 패키지 등록: 바로 아래 Runtime packages 입력창 맨 뒤에 세미콜론(;)을 붙이고 빌드해 둔 패키지 이름(MyCustomPkg)을 추가함.
  • 예시: `vcl;rtl;vclimg;MyCustomPkg`
  1. 경로 지정: Delphi Compiler -> Search Path에 아까 생성된 MyCustomPkg.dcp 파일이 위치한 경로를 추가함.

3단계: 정적 로딩 실제 사용 코드 예제

설정이 완료되면 메인 프로그램 소스코드에서 아무런 제약 없이 BPL 내의 화면과 함수를 불러와 쓸 수 있음.

unit MainFrm;

interface

uses
  Winapi.Windows, System.SysUtils, System.Classes, Vcl.Forms, Vcl.StdCtrls,
  MyCustomUnit; // 💡 BPL 내부에 존재하는 유닛을 일반 유닛처럼 바로 참조 가능

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  // BPL 내부에 정의된 openForm 함수를 정적으로 직접 호출
  OpenCustomerForm(Application, nil).Show;
end;

end.
  • 참고: 동적 로딩을 사용할 때는 위 2단계(IDE 옵션 설정) 과정이 필요 없으며, 소스코드 내에서 LoadPackage 및 앞서 소개한 TPackageManager를 이용해 처리함.

이렇게 환경 설정 및 정적 로딩 예시 단계를 명확히 짚어주면, 처음 BPL을 접하는 독자들도 헤매지 않고 실습을 따라 할 수 있음.


4. 구조적 확장을 위한 'BPL 패키지 매니저' 중앙 관리

BPL 모듈 증가 시 개별 화면마다 LoadPackage, UnloadPackage를 중복 작성하면 메모리 누수 위험이 커짐. 싱글톤(Singleton) 패턴 기반 패키지 매니저로 중앙 관리하는 것이 안정적임.

패키지 매니저 소스코드 (uPackageManager.pas)

unit uPackageManager;

interface

uses
  System.SysUtils, System.Classes, System.Generics.Collections, Winapi.Windows;

type
  TPackageManager = class
  private
    class var FInstance: TPackageManager;
    FPackages: TDictionary<string, HMODULE>; // 로드된 BPL 핸들 관리字典
    constructor Create;
  public
    destructor Destroy; override;
    class function GetInstance: TPackageManager;

    function LoadBpl(const ABplName: string): HMODULE;
    function GetBplProc(const ABplName, AProcName: string): Pointer;
    procedure UnloadBpl(const ABplName: string);
    procedure UnloadAll;
  end;

implementation

constructor TPackageManager.Create;
begin
  inherited;
  FPackages := TDictionary<string, HMODULE>.Create;
end;

destructor TPackageManager.Destroy;
begin
  UnloadAll;
  FPackages.Free;
  inherited;
end;

class function TPackageManager.GetInstance: TPackageManager;
begin
  if not Assigned(FInstance) then
    FInstance := TPackageManager.Create;
  Result := FInstance;
end;

function TPackageManager.LoadBpl(const ABplName: string): HMODULE;
var
  BplPath: string;
begin
  if FPackages.TryGetValue(ABplName, Result) then Exit; // 중복 로드 방지

  BplPath := ExtractFilePath(ParamStr(0)) + ABplName;
  if not FileExists(BplPath) then
    raise Exception.CreateFmt('BPL 파일 없음: %s', [ABplName]);

  Result := LoadPackage(BplPath);
  if Result <> 0 then
    FPackages.Add(ABplName, Result)
  else
    raise Exception.CreateFmt('BPL 로드 실패: %s', [ABplName]);
end;

function TPackageManager.GetBplProc(const ABplName, AProcName: string): Pointer;
var
  HPkg: HMODULE;
begin
  Result := nil;
  HPkg := LoadBpl(ABplName);

  if HPkg <> 0 then
  begin
    Result := GetProcAddress(HPkg, PWideChar(AProcName));
    if not Assigned(Result) then
      raise Exception.CreateFmt('%s 내 %s 함수 없음.', [ABplName, AProcName]);
  end;
end;

procedure TPackageManager.UnloadBpl(const ABplName: string);
var
  HPkg: HMODULE;
begin
  if FPackages.TryGetValue(ABplName, HPkg) then
  begin
    UnloadPackage(HPkg);
    FPackages.Remove(ABplName);
  end;
end;

procedure TPackageManager.UnloadAll;
var
  HPkg: HMODULE;
begin
  for HPkg in FPackages.Values do
  begin
    if HPkg <> 0 then UnloadPackage(HPkg);
  end;
  FPackages.Clear;
end;

initialization
finalization
  if Assigned(TPackageManager.FInstance) then
    TPackageManager.FInstance.Free;

end.

메인 프로그램 호출 예시

중앙 관리 매니저 도입으로 메인 코드 단순화 가능.

uses uPackageManager;

type
  TOpenFormFunc = function(AOwner: TComponent; ALink: Pointer): TForm; stdcall;

procedure TMainForm.BtnOpenCustomerClick(Sender: TObject);
var
  OpenForm: TOpenFormFunc;
  CustForm: TForm;
begin
  try
    // 매니저를 통해 BPL 및 함수명으로 포인터 즉시 획득
    @OpenForm := TPackageManager.GetInstance.GetBplProc('CustomerModule.bpl', 'OpenCustomerForm');

    if Assigned(OpenForm) then
    begin
      CustForm := OpenForm(Application, nil);
      CustForm.Show;
    end;
  except
    on E: Exception do ShowMessage('오류: ' + E.Message);
  end;
end;

실무 트러블슈팅 (Q&A)

  • Q1. 화면을 닫아도 메모리가 해제되지 않음.
  • A. openForm 내부에서 Owner를 Application으로 지정했기 때문임. BPL 내부 폼의 OnClose 이벤트에 Action := caFree; 설정을 추가하여 닫힐 때 즉시 해제되도록 조치해야 함.
  • Q2. BPL 언로드 시 Access Violation 에러 발생.
  • A. UnloadPackage 호출 시점에 해당 BPL 내부 화면이 아직 메모리에 남아있기 때문임. 화면 객체가 완전히 해제된 상태에서만 패키지를 언로드해야 함.
  • Q3. 배포 방식.
  • A. 메인 .exe 파일과 빌드된 .bpl 파일들을 동일 폴더에 배치하여 배포하는 것이 가장 안전함.