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] 데이터 전달 관리(ModuleLink) 본문

카테고리 없음

[Delphi] 데이터 전달 관리(ModuleLink)

ggi88 2026. 5. 27. 10:16

델파이(Delphi) 개발 시 화면(Form) 간에 독립성을 유지하면서 복잡한 데이터를 주고받거나, 함수의 매개변수가 5개, 10개 이상으로 늘어나는 '롱 파라미터 리스트(Long Parameter List)' 현상은 유지보수를 어렵게 만드는 대표적인 원인임.

이를 해결하기 위해 윈도우 메시지를 이용한 폼 간 비결합 통신은 물론, 일반 함수 호출 시에도 수십 개의 인자를 단 하나로 압축할 수 있는 타입 명시형 TModuleLink 클래스의 설계와 두 가지 실무 활용 예제를 정리함.


1. 아키텍처 개념 및 데이터 흐름

인자가 추가될 때마다 함수의 선언부를 고치거나 화면 간에 유닛을 직접 참조(uses)하는 방식은 결합도를 높여 관리가 힘들어짐.

  • 해결책: 모든 파라미터를 TModuleLink라는 단 하나의 객체에 키-밸류(Key-Value) 형태로 담고, 이 객체의 메모리 주소(포인터)만 전달하는 '파라미터 객체 패턴(Parameter Object Pattern)'을 적용함.
  • 메모리 효율: 아무리 많은 데이터를 담아도 전달되는 것은 오직 4바이트 또는 8바이트의 주소값 딱 하나이므로 메모리 복사 오버헤드가 없어 실행 속도가 극도로 빠름.

2. [공통 유닛] 타입 명시형 TModuleLink 소스코드 (uCommonLink.pas)

내부적으로 TStrings 파싱 엔진을 사용하되, 외부에는 타입을 명시한 메서드만 노출하여 안정성과 코드 자동완성 편의성을 확보함.

unit uCommonLink;

interface

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

const
  // 객체 간 통신에 사용할 사용자 정의 윈도우 메시지 ID
  WM_CUSTOM_COMMUNICATION = WM_USER + 2001;

type
  TModuleLink = class
  private
    FParams: TStrings;
  public
    constructor Create;
    destructor Destroy; override;

    // 1. 타입 명시형 데이터 저장 (Write)
    procedure WriteStr(const AKey, AValue: string);
    procedure WriteInt(const AKey: string; AValue: Integer);
    procedure WriteBool(const AKey: string; AValue: Boolean);
    procedure WriteFloat(const AKey: string; AValue: Double);
    procedure WriteDateTime(const AKey: string; AValue: TDateTime);

    // 2. 타입 명시형 데이터 추출 (Read - 안전 방어코드 포함)
    function ReadStr(const AKey: string; const ADefault: string = ''): string;
    function ReadInt(const AKey: string; const ADefault: Integer = 0): Integer;
    function ReadBool(const AKey: string; const ADefault: Boolean = False): Boolean;
    function ReadFloat(const AKey: string; const ADefault: Double = 0.0): Double;
    function ReadDateTime(const AKey: string; const ADefault: TDateTime = 0): TDateTime;

    // 3. 데이터 관리 기능
    procedure DeleteParam(const AKey: string);
    function ContainsKey(const AKey: string): Boolean;

    // 4. 메시지 전송 및 수신 헬퍼 엔진 (윈도우 메시지용)
    function SendSingle(ATargetHWnd: HWND): Boolean;
    procedure SendBroadcast(const ATargetList: TList<HWND>);
    class function ReceiveLink(var AMessage: TMessage): TModuleLink;

    property Params: TStrings read FParams;
  end;

implementation

{ TModuleLink }
constructor TModuleLink.Create; begin inherited; FParams := TStringList.Create; end;
destructor TModuleLink.Destroy; begin FParams.Free; inherited; end;

{ --- Write Methods --- }
procedure TModuleLink.WriteStr(const AKey, AValue: string); begin FParams.Values[AKey] := AValue; end;
procedure TModuleLink.WriteInt(const AKey: string; AValue: Integer); begin FParams.Values[AKey] := IntToStr(AValue); end;
procedure TModuleLink.WriteBool(const AKey: string; AValue: Boolean); begin FParams.Values[AKey] := BoolToStr(AValue, True); end;
procedure TModuleLink.WriteFloat(const AKey: string; AValue: Double); begin FParams.Values[AKey] := FloatToStr(AValue); end;
procedure TModuleLink.WriteDateTime(const AKey: string; AValue: TDateTime); begin FParams.Values[AKey] := DateTimeToStr(AValue); end;

{ --- Read Methods --- }
function TModuleLink.ReadStr(const AKey: string; const ADefault: string): string;
begin
  Result := FParams.Values[AKey]; if Result = '' then Result := ADefault;
end;

function TModuleLink.ReadInt(const AKey: string; const ADefault: Integer): Integer;
begin
  Result := StrToIntDef(FParams.Values[AKey], ADefault);
end;

function TModuleLink.ReadBool(const AKey: string; const ADefault: Boolean): Boolean;
begin
  Result := StrToBoolDef(FParams.Values[AKey], ADefault);
end;

function TModuleLink.ReadFloat(const AKey: string; const ADefault: Double): Double;
begin
  Result := StrToFloatDef(FParams.Values[AKey], ADefault);
end;

function TModuleLink.ReadDateTime(const AKey: string; const ADefault: TDateTime): TDateTime;
begin
  Result := StrToDateTimeDef(FParams.Values[AKey], ADefault);
end;

{ --- Management Methods --- }
procedure TModuleLink.DeleteParam(const AKey: string);
var Idx: Integer;
begin
  Idx := FParams.IndexOfName(AKey); if Idx >= 0 then FParams.Delete(Idx);
end;

function TModuleLink.ContainsKey(const AKey: string): Boolean;
begin
  Result := FParams.IndexOfName(AKey) >= 0;
end;

{ --- Transmission Methods --- }
function TModuleLink.SendSingle(ATargetHWnd: HWND): Boolean;
begin
  Result := False; if ATargetHWnd = 0 then Exit;
  Result := (Winapi.Windows.SendMessage(ATargetHWnd, WM_CUSTOM_COMMUNICATION, 0, LPARAM(Self)) = 1);
end;

procedure TModuleLink.SendBroadcast(const ATargetList: TList<HWND>);
var TargetHWnd: HWND;
begin
  if not Assigned(ATargetList) then Exit;
  for TargetHWnd in ATargetList do
    if (TargetHWnd <> 0) and IsWindow(TargetHWnd) then
      Winapi.Windows.SendMessage(TargetHWnd, WM_CUSTOM_COMMUNICATION, 0, LPARAM(Self));
end;

class function TModuleLink.ReceiveLink(var AMessage: TMessage): TModuleLink;
begin
  Result := nil; if AMessage.Msg = WM_CUSTOM_COMMUNICATION then Result := TModuleLink(AMessage.LParam);
end;

end.

3. 실무 활용 예제 예시


예제 A. 윈도우 메시지를 이용한 폼(Form)간 데이터 전송

두 폼이 서로의 존재를 직접 알지 못해도(uses 참조가 없어도), 핸들(HWND)만 알면 안전하게 복합 데이터를 비결합 통신으로 주고받을 수 있음.

1) 데이터를 보내는 쪽 (자식 폼 또는 팝업 폼)

procedure TChildForm.BtnSendToParentClick(Sender: TObject);
var
  Link: TModuleLink;
begin
  Link := TModuleLink.Create;
  try
    // 키-밸류 형식으로 전송할 데이터를 세팅
    Link.WriteStr('Command', 'POPUP_SELECTION');
    Link.WriteInt('SelectedID', 1024);
    Link.WriteStr('ItemName', '아메리카노');
    Link.WriteBool('IsTakeOut', True);

    // 기억해 둔 부모 폼의 핸들(FParentHandle)로 링크 전송
    Link.SendSingle(FParentHandle); 
  finally
    Link.Free; // 동기 전송(SendMessage)이므로 완료 후 즉시 해제
  end;
end;

2) 데이터를 받는 쪽 (부모 폼 또는 메인 폼)

// 부모 폼의 private 또는 protected 영역에 메시지 핸들러 선언 필요
// procedure WMCustomCommunication(var Message: TMessage); message WM_CUSTOM_COMMUNICATION;

procedure TParentForm.WMCustomCommunication(var Message: TMessage);
var
  Link: TModuleLink;
begin
  // 메시지 매개변수로부터 Link 객체 주소 복원
  Link := TModuleLink.ReceiveLink(Message);
  if not Assigned(Link) then Exit;

  if Link.ReadStr('Command') = 'POPUP_SELECTION' then
  begin
    // 타입별 함수를 사용해 캐스팅 없이 값 추출
    ShowMessage(Format('선택된 상품: [%d] %s (포장여부: %s)', 
                 [Link.ReadInt('SelectedID'), 
                  Link.ReadStr('ItemName'), 
                  BoolToStr(Link.ReadBool('IsTakeOut'), True)]));

    Message.Result := 1; // 성공 피드백 반환
  end;
end;

예제 B. 함수 매개변수(Parameter) 단일화를 통한 데이터 전달

함수의 요구 인자가 계속 늘어나는 '롱 파라미터 리스트' 현상을 방지함. 데이터가 추가되거나 삭제되어도 함수의 선언부(틀)를 변경할 필요가 없어 하위 호환성이 100% 유지됨.

1) 함수를 호출하는 쪽

procedure TMainForm.BtnSubmitClick(Sender: TObject);
var
  UserParam: TModuleLink;
begin
  UserParam := TModuleLink.Create;
  try
    // 함수에 보낼 수십 개의 파라미터를 Link 객체 하나에 축약
    UserParam.WriteStr('ID', 'delphi_master');
    UserParam.WriteStr('Name', '홍길동');
    UserParam.WriteInt('Age', 29);
    UserParam.WriteBool('IsActive', True);

    // 비즈니스 고도화로 새로 추가된 파라미터 (함수 선언은 고치지 않음)
    UserParam.WriteStr('UserGrade', 'VIP'); 

    // 인자 딱 하나만 넘겨서 비즈니스 함수 호출
    RegistUser(UserParam);
  finally
    UserParam.Free;
  end;
end;

2) 함수를 제공하는 쪽 (구현부)

// 파라미터 항목이 증감해도 프로시저의 선언부 틀은 평생 고칠 필요가 없음
procedure TMainForm.RegistUser(ALink: TModuleLink);
var
  sID, sName, sGrade: string;
  nAge: Integer;
begin
  if not Assigned(ALink) then Exit;

  // 타입 명시형 함수로 안전하게 데이터 획득
  sID   := ALink.ReadStr('ID');
  sName := ALink.ReadStr('Name');
  nAge  := ALink.ReadInt('Age', 20); // 데이터 누락 시 기본값 20세 방어코드 작동

  // 나중에 추가된 파라미터도 함수 내부에서 유연하게 대응 가능
  sGrade := ALink.ReadStr('UserGrade', 'General'); 

  // 이후 회원 등록 DB 로직 처리 진행...
end;

결론 및 정리

  • 폼간 통신 효율성: 유닛 소스코드 결합을 완전히 배제하고 오직 윈도우 핸들과 통신용 메시지만으로 유연한 데이터 제어가 가능함.
  • 함수 관리 효율성: 파라미터 추가 시 유발되는 도미노 식의 코드 수정 및 오버로딩(overload) 노가다를 차단하여 하위 호환성을 완벽하게 보장함.
  • TModuleLink 클래스는 프로젝트 전반에서 화면 간의 비결합 통신 매개체이자, 복잡한 비즈니스 데이터의 DTO(Data Transfer Object) 역할까지 동시에 수행할 수 있는 델파이 표준 범용 데이터 구조체임.