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:46

1. 아키텍처 전체 구조 레이아웃

전체적인 의존성 구조는 아래와 같이 철저히 단방향 하향 참조로 구성됨.

  • BaseCommon.bpl (레이어1): 서드파티 컴포넌트, 공통 유틸리티, DB 연결 정보, 그리고 모듈 간 통신을 위한 인터페이스(Interface)를 포함함.
  • Main.exe (레이어2): 구체적인 기능 화면을 직접 들고 있지 않는 가벼운 껍데기임. 화면을 언제 띄울지 제어하는 엔진 역할만 수행함.
  • A.bpl, B.bpl (레이어3): 실제 업무 화면(매출관리, 고객관리 등)과 상세 로직이 포함됨. 모듈 간에는 서로 절대 참조하지 않음.

2. 각 요소별 핵심 역할 및 내부 구현 방식

① BaseCommon.bpl (모든 패키지의 기반)

모든 컴포넌트(DevExpress, TMS 등)에 대한 참조 정보와 공통 기능을 가짐. 이 패키지가 깨지면 상위 모든 패키지가 작동하지 않으므로 변경 시 주의가 필요함.

  • 컴포넌트 참조 통합: 개별 BPL마다 서드파티 컴포넌트를 각각 포함하면 용량이 중복으로 커짐. BaseCommon 프로젝트 옵션의 Requires에 서드파티 패키지(예: dxCore.dcp, cxLibrary.dcp)를 등록하여 한곳에서 참조를 관리함.
  • 공통 인터페이스(Interface) 선언: A.bplB.bpl이 서로 소통하거나 Main.exe가 이들을 제어할 때 구체적인 폼 클래스 명을 쓰면 안 됨. 이를 위해 추상화된 약속(인터페이스)을 선언함.
// BaseCommon.bpl 내부 uInterfaces.pas
unit uInterfaces;

interface

uses System.Classes, Vcl.Forms;

type
  // 모든 업무 BPL 화면들이 반드시 구현해야 하는 공통 규격
  IBplForm = interface
    ['{7D4B6C1E-2A3A-4B5C-6D7E-8F9A0B1C2D3E}']
    procedure InitModule(const AUserData: string); // 모듈 초기화 데이터 전달
    function GetFormObject: TForm;                 // 폼 객체 자체를 반환받기 위함
  end;

implementation
end.

② A.bpl, B.bpl (독립된 비즈니스 모듈)

실제 UI 화면과 비즈니스 로직을 담고 있음. 프로젝트 설정의 Requires에 반드시 BaseCommon.dcp가 등록되어 있어야 함.

  • 구현 방식: BaseCommon에 선언된 인터페이스를 상속받아 화면을 구현하고, 메인에서 호출할 수 있도록 팩토리(Factory) 함수를 exports함.
// A.bpl 내부 uFormA.pas
unit uFormA;

interface

uses
  System.Classes, Vcl.Forms, Vcl.StdCtrls, uInterfaces; // BaseCommon의 인터페이스 참조

type
  TFormA = class(TForm, IBplForm)
    Button1: TButton;
  public
    procedure InitModule(const AUserData: string);
    function GetFormObject: TForm;
  end;

// 외부(Main.exe)에서 이 BPL을 호출할 창구 함수
function OpenModuleA(AOwner: TComponent): IBplForm; stdcall;

implementation

{$R *.dfm}

function OpenModuleA(AOwner: TComponent): IBplForm; stdcall;
begin
  Result := TFormA.Create(AOwner); // 인터페이스 형태로 반환
end;

exports
  OpenModuleA;

{ TFormA 구현 }
function TFormA.GetFormObject: TForm;
begin
  Result := Self;
end;

procedure TFormA.InitModule(const AUserData: string);
begin
  // 메인에서 넘겨준 로그인 유저 등의 데이터 처리
  Caption := AUserData + '님의 업무 화면 A';
end;

end.

③ Main.exe (중앙 통제 및 패키지 실행 엔진)

BaseCommon.dcp를 참조함. 실행 관련 핵심 로직(앞서 언급한 패키지 매니저 등)을 내장하고 있어, 사용자가 메뉴를 클릭할 때 해당하는 BPL을 동적으로 로드함.

  • 동작 매커니즘: Main.exeA.bpl이나 B.bpl이 프로젝트 내에 연결되어 있지 않음(uses 안 함). 오직 파일명과 함수명 문자열만 가지고 동적으로 호출함.
// Main.exe 내부 코드 일부
uses uInterfaces, uPackageManager; // BaseCommon의 인터페이스와 패키지 매니저만 참조

type
  TOpenModuleFunc = function(AOwner: TComponent): IBplForm; stdcall;

procedure TMainForm.MenuAClick(Sender: TObject);
var
  OpenFunc: TOpenModuleFunc;
  BplModule: IBplForm;
begin
  try
    // 1. 패키지 매니저를 통해 A.bpl에서 함수 포인터 획득
    @OpenFunc := TPackageManager.GetInstance.GetBplProc('A.bpl', 'OpenModuleA');

    if Assigned(OpenFunc) then
    begin
      // 2. 함수를 실행하여 인터페이스를 얻음
      BplModule := OpenFunc(Application);

      // 3. 인터페이스 규격대로 데이터 전달 및 공통 제어
      BplModule.InitModule('홍길동');

      // 4. 실제 Form 객체로 변환하여 화면에 출력
      BplModule.GetFormObject.Show;
    end;
  except
    on E: Exception do ShowMessage(E.Message);
  end;
end;

3. 이 구조가 주는 강력한 이점 (왜 이렇게 해야 하는가?)

  1. 완벽한 순환 참조(Circular Reference) 방지: * A.bpl에서 B.bpl의 특정 기능이나 팝업이 필요할 때, 직접 uses하면 결합도가 꼬임.
  • 이때 직접 참조하지 않고 BaseCommon에 인터페이스나 이벤트를 중재자(Mediator)로 등록해 두고 통신하면 패키지 간의 커플링이 완전히 해소됨.
  1. 배포 효율 극대화(부분 패치):
  • A.bpl 화면의 디자인이나 버그를 수정했을 때, 메인 프로그램 전체를 빌드하거나 배포할 필요가 없음.
  • 오직 A.bpl 파일(몇 백 KB ~ 몇 MB 수준)만 빌드해서 사용자 PC에 덮어쓰면 패치가 완료됨.
  1. 초기 구동 속도 및 메모리 최적화:
  • 메인 프로그램 실행 시에는 Main.exeBaseCommon.bpl만 메모리에 올라감.
  • 사용자가 'A 업무 메뉴'를 누를 때 비로소 A.bpl이 메모리에 로드되므로 시스템 자원을 효율적으로 사용함.
  1. 대규모 개발 팀 협업 용이:
  • 공통 파트 팀원은 BaseCommon.bpl만 관리하고, 영업 파트 개발자는 A.bpl, 인사 파트 개발자는 B.bpl만 독립적으로 개발하여 배포하므로 소스 충돌이 최소화됨.