Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
Tags
- FastAPI
- bpl
- superbase
- flyio
- Delphi
- wsl2
- vercel
- AWS
- shadcn-ui
- springboot
- kidznote
- Module
- Markdown
- edgestore
- stepzen
- dokerfile
- tessrect
- react
- svelte
- Expo
- TypeScript
- reactnative
- Book
- oraclecloude
- java
- spring
- Nextjs
- beautifullsoup
- docker
- WSLHostPatcher
Archives
- Today
- Total
Blog
[Delphi] BPL 설정 및 사용법 본문
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 패키지 프로젝트 생성 및 빌드
- 패키지 생성: Delphi 메뉴에서 File -> New -> Package 선택 후 프로젝트 명을
MyCustomPkg로 저장함. - 화면 및 유닛 추가: 프로젝트 매니저에서 패키지 우클릭 후 화면(
TForm) 또는 일반 유닛을 추가함. (예:openForm함수가 포함된 유닛) - 컴파일 및 빌드: 패키지 프로젝트 우클릭 후 Build를 실행함.
- 성공 시 프로젝트 출력 디렉토리에
MyCustomPkg.bpl(실행용 런타임 파일)과MyCustomPkg.dcp(컴파일 참조용 델파이 컴포넌트 패키지 파일)가 생성됨.
2단계: 메인 프로그램(.exe) 프로젝트 설정 (정적 로딩 예시)
메인 프로그램에서 BPL 유닛을 일반 유닛처럼 직접 참조(uses)하여 사용하기 위한 필수 설정임.
- 런타임 패키지 활성화: 메인 프로젝트가 열린 상태에서 Project -> Options -> Delphi Compiler -> Packages -> Runtime Packages 메뉴로 이동함.
- Link with runtime packages 항목을 True로 변경함.
- 패키지 등록: 바로 아래 Runtime packages 입력창 맨 뒤에 세미콜론(
;)을 붙이고 빌드해 둔 패키지 이름(MyCustomPkg)을 추가함.
- 예시: `vcl;rtl;vclimg;MyCustomPkg`
- 경로 지정: 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파일들을 동일 폴더에 배치하여 배포하는 것이 가장 안전함.