Skip to content

Delphi 치트시트

빠른 애플리케이션 개발을 위한 Object Pascal 방언.

01

프로그램 구조 & 기본

프로그램 구조 & 유닛

Delphi 프로그램은 'program'으로 시작하여 'end.'(마침표)로 끝납니다. {$APPTYPE CONSOLE}은 콘솔 앱으로 표시하는 컴파일러 지시문입니다. 'uses'는 유닛(모듈)을 임포트합니다 — System.SysUtils에 Format, IntToStr 등이 있습니다. 유닛은 인터페이스 섹션(공개 선언)과 구현 섹션(코드)을 가집니다. WriteLn은 줄바꿈과 함께 텍스트를 출력하고; Write는 없이 출력합니다. ReadLn은 입력을 읽습니다(또는 일시 중지). 메인 begin..end 블록이 프로그램의 진입점입니다.

delphi
program Demo;          // program keyword starts a console app

{$APPTYPE CONSOLE}      // compiler directive: console application

uses                    // import units (like #include or import)
  System.SysUtils,      // utilities (Format, IntToStr, etc.)
  System.Classes;       // TList, TStrings, etc.

// a unit has interface (declarations) and implementation (bodies)
// unit Math;
// interface
//   function Add(A, B: Integer): Integer;
// implementation
//   function Add(A, B: Integer): Integer;
//   begin Result := A + B; end;
// end.

var
  Name: string;
begin
  Name := 'Alice';
  WriteLn('Hello, ', Name, '!');    // WriteLn = output with newline
  Write('No newline');              // Write = output without newline
  ReadLn;                           // wait for Enter (pause)
end.

변수, 타입 & 상수

Delphi는 강력한 타입입니다. 일반적인 타입: Integer(32비트), Int64(64비트), Double(64비트 부동소수점), Extended(x86에서 80비트 부동소수점), Single(32비트 부동소수점), string(유니코드, 참조 카운트됨), Char(WideChar, 2바이트), Boolean, Byte(0-255). TDateTime은 실제로 Double입니다(1899-12-30 이후 일수). 상수는 'const'를 사용 — 타입이 있는 상수는 타입을 가지고, 타입이 없는 것은 유연합니다. 하위 범위 타입(0..150)은 값을 제한합니다. 열거형(TDay)은 명명된 상수를 정의합니다. Format()은 sprintf와 같습니다: %s(문자열), %d(정수), %f(부동소수점).

delphi
var
  Name: string = 'Alice';        // string (managed, reference-counted)
  Age: Integer = 30;             // 32-bit signed integer
  BigNum: Int64 = 9223372036854775807;  // 64-bit
  Pi: Double = 3.14159;          // 64-bit float (IEEE 754)
  E: Extended = 2.71828;         // 80-bit float (x87)
  Rate: Single = 0.5;            // 32-bit float
  Ch: Char = 'A';                // 2-byte Unicode char (WideChar)
  IsDev: Boolean = True;         // True/False
  Bytes: Byte = 255;             // 0..255
  Date: TDateTime;               // date/time (double)

// constants
const
  MaxRetries = 3;                // untyped constant
  Pi: Double = 3.14159265;       // typed constant
  Greeting: string = 'Hello';

// type aliases
type
  TAge = 0..150;                 // subrange type
  TDay = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);  // enumeration

begin
  Date := Now;                   // current date/time
  WriteLn(Format('%s is %d', [Name, Age]));   // formatted output
  WriteLn('Pi = ', Pi:0:2);      // 3.14 (width:decimals)
end;

연산자 & 표현식

Delphi는 할당에 :=를 사용하고 동등성에 =를 사용합니다(C와 같은 언어와 반대). div는 정수 나눗셈; /는 실수 나눗셈(항상 Extended/Double 반환). mod는 나머지입니다. and/or/not/xor은 부울(논리)과 정수(비트) 모두에서 작동합니다 — 컨텍스트가 결정합니다. shl/shr은 비트 시프트입니다. Inc/Dec는 효율적인 인플레이스 증가/감소입니다(A := A + 1 작성 피하기). 문자열 연결은 +를 사용합니다. Power()는 System.Math에 있습니다. := vs = 구분은 초보자 오류의 #1 원인입니다.

delphi
var
  A, B: Integer;
  X, Y: Double;
  S1, S2: string;
begin
  A := 10; B := 3;
  WriteLn(A + B);    // 13 (addition)
  WriteLn(A - B);    // 7
  WriteLn(A * B);    // 30
  WriteLn(A div B);  // 3 (integer division)
  WriteLn(A mod B);  // 1 (remainder)
  WriteLn(A / B);    // 3.33 (real division, always Extended)

  X := 2.0; Y := 3.0;
  WriteLn(X + Y);    // 5.0
  WriteLn(Power(X, Y)); // 8.0 (needs System.Math)

  // comparison (return Boolean)
  WriteLn(A > B);    // TRUE
  WriteLn(A = B);    // FALSE (= is equality, not assignment!)
  WriteLn(A <> B);   // TRUE (not equal)
  WriteLn(A >= B);   // TRUE

  // logical operators
  WriteLn(True and False);  // FALSE
  WriteLn(True or False);   // TRUE
  WriteLn(not True);        // FALSE
  WriteLn(True xor False);  // TRUE

  // string concatenation
  S1 := 'Hello'; S2 := 'World';
  WriteLn(S1 + ', ' + S2 + '!');  // Hello, World!

  // bitwise (on integers)
  WriteLn(5 and 3);  // 1
  WriteLn(5 or 3);   // 7
  WriteLn(5 shl 1);  // 10 (shift left = *2)
  WriteLn(5 shr 1);  // 2 (shift right = /2)

  // Inc and Dec (modify in place)
  Inc(A);      // A := A + 1
  Inc(A, 5);   // A := A + 5
  Dec(A);      // A := A - 1
end;

입력, 출력 & 서식

Format()은 Delphi의 sprintf입니다 — %s(문자열), %d(정수), %f(부동소수점), %x(16진수), %m(통화), 너비/정밀도 수정자를 사용합니다. WriteLn(value:width:decimals)은 부동소수점을 직접 포맷합니다. ReadLn은 변수로 입력을 읽습니다. StrToInt/StrToFloat는 문자열을 숫자로 변환합니다(실패 시 EConvertError 발생); TryStrToInt는 부울을 반환하며 더 안전합니다. IntToStr/FloatToStr은 숫자를 문자열로 변환합니다. FormatDateTime은 날짜를 포맷합니다(yyyy, mm, dd, hh, nn, ss). FloatToStrF는 정밀한 제어를 제공합니다(ffFixed, ffCurrency, ffExponent).

delphi
var
  Name: string;
  Age: Integer;
  Salary: Double;
begin
  // console output
  WriteLn('Hello, World!');        // with newline
  Write('No newline');             // without
  WriteLn;                         // just a newline

  // formatted output with Format (like sprintf)
  WriteLn(Format('Name: %s, Age: %d', ['Alice', 30]));
  WriteLn(Format('Pi: %.4f', [3.14159]));      // Pi: 3.1416
  WriteLn(Format('Hex: %x', [255]));            // Hex: FF
  WriteLn(Format('Pad: %10d', [42]));           // right-aligned
  WriteLn(Format('Left: %-10d|', [42]));        // left-aligned
  WriteLn(Format('Money: %m', [1234.56]));      // currency

  // WriteLn with format specifiers (width:decimals)
  WriteLn(3.14159:0:2);    // 3.14
  WriteLn(42:5);           //    42 (width 5)

  // console input
  Write('Enter your name: ');
  ReadLn(Name);
  Write('Enter your age: ');
  ReadLn(Age);

  // type conversion functions
  Salary := StrToFloat('50000.50');
  Age := StrToInt('30');
  WriteLn(IntToStr(42));              // '42'
  WriteLn(FloatToStr(3.14));          // '3.14'
  WriteLn(FloatToStrF(3.14159, ffFixed, 8, 2));  // '3.14'
  WriteLn(FormatDateTime('yyyy-mm-dd', Now));    // '2024-06-18'

  // TryStrToInt (safe parsing)
  if TryStrToInt('123', Age) then
    WriteLn('Parsed: ', Age);
end;

유닛, 범위 & 가시성

유닛은 Delphi의 모듈입니다. 인터페이스 섹션은 공개된 것을 선언하고(사용자에게 보임); 구현은 코드를 포함하며 private 타입/변수를 가질 수 있습니다. initialization/finalization 섹션은 유닛 로드/언로드 시 실행됩니다(유닛의 생성자/소멸자처럼). 인터페이스에 선언된 변수는 전역이고; 구현에서는 유닛 private입니다. 인터페이스의 타입은 공개이고; 구현에서는 private입니다. 이 두 섹션 설계는 유닛 수준에서 캡슐화를 강제합니다. 'uses' 절은 다른 유닛을 임포트합니다 — UnitName.Identifier로 이름 충돌을 해결하세요.

delphi
// Unit declaration
unit MathHelper;

interface

uses
  System.SysUtils;

// public types (visible to users of the unit)
type
  TCalculator = class
  public
    function Add(A, B: Integer): Integer;
  end;

// public constants
const
  Pi = 3.14159265358979;

// public variables
var
  Counter: Integer;

// public function declarations
function Multiply(A, B: Integer): Integer;

implementation

// private types (only visible inside this unit's implementation)
type
  TInternal = record
    Value: Integer;
  end;

// private variables
var
  InternalCount: Integer;

// function body (implementation)
function Multiply(A, B: Integer): Integer;
begin
  Result := A * B;
end;

function TCalculator.Add(A, B: Integer): Integer;
begin
  Result := A + B;
end;

initialization
  // runs when the unit loads
  Counter := 0;
  InternalCount := 0;

finalization
  // runs when the unit unloads (cleanup)
  // free resources here

end.
02

제어 흐름

If...Then...Else

If...Then...Else는 Delphi의 조건문입니다. 중요: 'else' 앞에 세미콜론이 없습니다 — 세미콜론은 명령문을 끝내고, else는 if의 일부입니다. 다중 명령문 분기의 경우 begin..end로 감싸세요 (else 앞에도 세미콜론 없음). and/or/not은 논리 연산자입니다(정수에서는 비트). 괄호로 조건을 그룹화하세요: (A > 0) and (B > 0). else 앞 세미콜론 누락 규칙은 초보자를 위한 가장 일반적인 Delphi 구문 오류입니다.

delphi
var
  Score: Integer;
  Grade: string;
begin
  Score := 85;

  // simple if
  if Score >= 60 then
    WriteLn('Pass');

  // if-else (no semicolon before 'else'!)
  if Score >= 90 then
    Grade := 'A'
  else if Score >= 80 then
    Grade := 'B'
  else if Score >= 70 then
    Grade := 'C'
  else
    Grade := 'F';

  WriteLn('Grade: ', Grade);

  // multi-statement if (needs begin..end)
  if Score > 50 then
  begin
    WriteLn('Passed');
    WriteLn('Congratulations');
  end
  else
  begin
    WriteLn('Failed');
    WriteLn('Try again');
  end;

  // nested with logical operators
  if (Score >= 0) and (Score <= 100) then
    WriteLn('Valid score');
end;

Case (Switch) 문

Case는 Delphi의 switch입니다 — 서수 타입(Integer, Char, 열거형, 하위 범위)에서 작동합니다. 각 분기는 단일 값, 쉼표로 구분된 목록('D', 'F'), 또는 범위(1..5)일 수 있습니다. else 절은 기본값입니다. Case는 fall-through하지 않습니다(C와 달리). 다중 명령문 분기의 경우 begin..end를 사용하세요. 이산 값에 대해 연결된 if-else보다 Case가 더 깔끔합니다. 문자열에 직접 case할 수 없습니다(if-else나 조회 사용).

delphi
var
  Grade: Char;
  Day: Integer;
begin
  Grade := 'B';

  // case on ordinal (integer, char, enum)
  case Grade of
    'A': WriteLn('Excellent');
    'B': WriteLn('Good');
    'C': WriteLn('Average');
    'D', 'F': WriteLn('Poor');     // multiple values
  else
    WriteLn('Invalid grade');       // default (else clause)
  end;

  // case with ranges
  Day := 3;
  case Day of
    1..5: WriteLn('Weekday');       // range
    6, 7: WriteLn('Weekend');
  else
    WriteLn('Invalid day');
  end;

  // case with enums
  type TColor = (Red, Green, Blue);
  var C: TColor;
  C := Green;
  case C of
    Red: WriteLn('Stop');
    Green: WriteLn('Go');
    Blue: WriteLn('Relax');
  end;

  // case with multi-statement branches
  case Grade of
    'A': begin
           WriteLn('Excellent');
           WriteLn('Keep it up');
         end;
    'B': WriteLn('Good');
  end;
end;

For 루프 (To, Downto, In)

For...to는 오름차순으로 반복하고; For...downto는 내림차순입니다. 루프 변수는 루프 내에서 수정할 수 없습니다. For...in(현대 Delphi)은 배열, 문자열(문자별), 집합 및 열거 가능한 모든 것을 반복합니다. Break는 루프를 종료하고; Continue는 다음 반복으로 건너뜁니다. 내장된 step은 없습니다 — 조건문이나 while 루프를 사용하세요. 루프 변수는 루프 후에 정의되지 않습니다(값에 의존하지 마세요). 컬렉션에는 For...in이 선호됩니다(더 깔끔하고, 인덱스 오류 없음).

delphi
var
  i: Integer;
  Fruits: array of string;
  S: string;
begin
  // for...to (ascending)
  for i := 1 to 5 do
    WriteLn(i);          // 1 2 3 4 5

  // for...downto (descending)
  for i := 5 downto 1 do
    WriteLn(i);          // 5 4 3 2 1

  // for with step (no built-in step — use a while or compute)
  for i := 0 to 9 do
    if i mod 2 = 0 then
      WriteLn(i);        // 0 2 4 6 8

  // nested loops
  for i := 1 to 3 do
    for j := 1 to 3 do
      Write(i * j, ' ');
  WriteLn;

  // for...in (iterates collections, modern Delphi)
  Fruits := ['apple', 'banana', 'cherry'];
  for S in Fruits do
    WriteLn(S);

  // for...in on string (iterates characters)
  for S in 'Hello' do
    Write(S, ' ');       // H e l l o
  WriteLn;

  // for...in on set
  type TDigits = set of 1..5;
  var D: TDigits := [1, 3, 5];
  for i in D do
    WriteLn(i);          // 1 3 5

  // Break and Continue
  for i := 1 to 100 do
  begin
    if i > 10 then Break;       // exit loop
    if i mod 2 = 0 then Continue; // skip to next iteration
    WriteLn(i);                  // 1 3 5 7 9
  end;
end;

While & Repeat...Until

While은 본체 전에 테스트합니다(실행되지 않을 수 있음); repeat...until은 후에 테스트합니다(항상 최소 한 번 실행). 중요: while은 조건이 TRUE인 동안 계속하고; repeat은 조건이 TRUE가 되면 중지합니다(반대 논리!). repeat...until은 begin..end가 필요 없습니다(본질적으로 블록). '0회 이상'에는 while을, '1회 이상'에는 repeat을 사용하세요. Break는 종료하고; Continue는 테스트로 건너뜁니다. 복잡한 종료 조건이 있는 루프에는 Break와 함께 while True가 일반적인 관용구입니다.

delphi
var
  Count: Integer;
  Line: string;
begin
  // while: test BEFORE (may never run)
  Count := 0;
  while Count < 3 do
  begin
    WriteLn(Count);
    Inc(Count);
  end;
  // 0 1 2

  // repeat...until: test AFTER (runs at least once)
  Count := 0;
  repeat
    WriteLn(Count);
    Inc(Count);
  until Count >= 3;
  // 0 1 2

  // KEY DIFFERENCE: while continues while TRUE; until stops when TRUE
  // while X < 3 do ...  ==  repeat ... until X >= 3

  // repeat doesn't need begin..end (it's already a block)
  Count := 5;
  repeat
    WriteLn(Count);
    Dec(Count);
  until Count = 0;

  // reading input until a condition
  repeat
    Write('Enter "quit" to stop: ');
    ReadLn(Line);
  until (Line = 'quit') or (Line = 'exit');

  // infinite loop with Break
  while True do
  begin
    WriteLn('Running...');
    if SomeCondition then Break;
  end;

  // Continue in while
  Count := 0;
  while Count < 10 do
  begin
    Inc(Count);
    if Count mod 2 = 0 then Continue;
    WriteLn(Count);   // 1 3 5 7 9
  end;
end;

With...Do & Goto

With...Do는 변수 반복 없이 레코드/객체의 멤버에 접근합니다 — 초기화와 장황함 감소에 유용합니다. 중첩된 With는 피하세요(멤버가 어느 객체에 속하는지 모호). Goto는 레이블로 점프합니다 — 현대 Delphi에서는 거의 사용되지 않음(Break/Continue/Exit 선호); 'label'로 레이블을 선언하세요. Exit는 프로시저를 즉시 떠나고; Exit(value)는 함수에서 값을 반환합니다(현대 구문). With는 과도하게 사용하면 코드 가독성이 떨어질 수 있습니다 — 간단한 경우에만 신중하게 사용하세요.

delphi
type
  TPerson = record
    Name: string;
    Age: Integer;
    Email: string;
  end;

var
  P: TPerson;
  i: Integer;
label
  RetryPoint;   // declare a label for Goto
begin
  // With...Do: access record/object members without repeating the name
  with P do
  begin
    Name := 'Alice';
    Age := 30;
    Email := '[email protected]';
    WriteLn(Name, ' is ', Age);   // instead of P.Name, P.Age
  end;

  // With on a function result
  with TStringList.Create do
  try
    Add('line 1');
    Add('line 2');
    SaveToFile('output.txt');
  finally
    Free;
  end;

  // nested With (avoid — ambiguous)
  with P, TStringList.Create do
  try
    Add(Name);   // Name could be P.Name or TStringList.Name
  finally
    Free;
  end;

  // Goto (rarely used — prefer structured alternatives)
  i := 0;
RetryPoint:
  Inc(i);
  WriteLn('Attempt ', i);
  if i < 3 then
    Goto RetryPoint;
  WriteLn('Done after ', i, ' attempts');

  // Exit: leave the current procedure/function
  if P.Age < 0 then
  begin
    WriteLn('Invalid age');
    Exit;          // return immediately
  end;

  // Exit with a value (for functions)
  // Exit(42);  // returns 42 from the function
end;
03

문자열 & 텍스트 처리

문자열 타입 & 연산

Delphi의 기본 문자열은 UnicodeString(UTF-16, 참조 카운트됨, 복사 시 쓰기)입니다. 문자열은 1-인덱스입니다(S[1]이 첫 번째 문자) — C 프로그래머를 위한 일반적인 버그 원인. Length()는 문자 수를 반환합니다. Pos()는 부분 문자열을 찾습니다(없으면 0 반환, -1이 아님). Copy()는 부분 문자열을 추출합니다(시작, 개수). StringReplace는 교체합니다(모든 발생에 rfReplaceAll). Trim/TrimLeft/TrimRight는 공백을 제거합니다. Split/Join은 현대 메서드입니다(TArray<string>). 대소문자 구분 없는 비교에는 SameText를 사용하세요.

delphi
var
  S: string;           // UnicodeString (default, UTF-16, reference-counted)
  A: AnsiString;       // 8-bit string (legacy, codepage-aware)
  W: WideString;       // COM-compatible (not reference-counted)
  SB: StringBuilder;   // mutable, for heavy concatenation
begin
  S := 'Hello, World';

  // length and indexing (1-indexed!)
  WriteLn(Length(S));        // 12
  WriteLn(S[1]);             // 'H' (first char — 1-indexed!)
  WriteLn(S[Length(S)]);     // 'd' (last char)

  // case conversion
  WriteLn(UpperCase(S));     // HELLO, WORLD
  WriteLn(LowerCase(S));     // hello, world

  // searching
  WriteLn(Pos('World', S));  // 8 (1-indexed position, 0 if not found)
  WriteLn(Pos('xyz', S));    // 0

  // substring
  WriteLn(Copy(S, 1, 5));    // 'Hello' (Start, Length)
  WriteLn(Copy(S, 8, 5));    // 'World'

  // modify (creates new string — strings are immutable-ish)
  S := StringReplace(S, 'World', 'Delphi', [rfReplaceAll]);
  WriteLn(S);                // Hello, Delphi

  // trim
  WriteLn(Trim('  hi  '));        // 'hi'
  WriteLn(TrimLeft('  hi  '));    // 'hi  '
  WriteLn(TrimRight('  hi  '));   // '  hi'

  // split
  var Parts: TArray<string>;
  Parts := 'a,b,c'.Split([',']);
  WriteLn(Length(Parts));    // 3

  // join
  WriteLn(string.Join('-', Parts));  // 'a-b-c'

  // comparison
  WriteLn('abc' = 'abc');    // TRUE (case-sensitive)
  WriteLn(AnsiCompareText('ABC', 'abc'));  // 0 (case-insensitive)
  WriteLn(SameText('ABC', 'abc'));         // TRUE
end;

문자열 서식 & 변환

Format()은 Delphi의 sprintf입니다: %d(정수), %f(부동소수점), %s(문자열), %x(16진수), %m(통화), 너비/정밀도 수정자. FloatToStrF는 정밀한 제어를 제공합니다(ffFixed, ffCurrency, ffNumber, ffExponent). FormatDateTime은 날짜를 포맷합니다: yyyy(4자리 연도), mm(월), dd(일), hh(시), nn(분), ss(초), dddd(전체 요일 이름), mmmm(전체 월 이름). StrToInt/StrToFloat는 잘못된 입력 시 EConvertError를 발생시키고; TryStrToInt는 부울을 반환합니다(더 안전). 사용자 입력에는 항상 Try...를 사용하세요.

delphi
var
  N: Integer := 42;
  F: Double := 3.14159;
  S: string;
  D: TDateTime := Now;
begin
  // Format (like sprintf)
  S := Format('Integer: %d', [N]);              // 'Integer: 42'
  S := Format('Float: %f', [F]);                // 'Float: 3.14'
  S := Format('Float: %.4f', [F]);              // 'Float: 3.1416'
  S := Format('Hex: %x', [N]);                  // 'Hex: 2a'
  S := Format('String: %s', ['Hello']);         // 'String: Hello'
  S := Format('Padded: %10d', [N]);             // '        42'
  S := Format('Left: %-10d|', [N]);             // '42        |'
  S := Format('Multiple: %s=%d, %.2f', ['x', N, F]);

  // FloatToStrF (precise float formatting)
  S := FloatToStrF(F, ffFixed, 8, 2);           // '3.14'
  S := FloatToStrF(F, ffCurrency, 8, 2);        // '$3.14'
  S := FloatToStrF(1234567, ffNumber, 10, 0);   // '1,234,567'

  // date/time formatting
  S := FormatDateTime('yyyy-mm-dd', D);         // '2024-06-18'
  S := FormatDateTime('hh:nn:ss', D);           // '14:30:00'
  S := FormatDateTime('dddd, mmmm d, yyyy', D); // 'Tuesday, June 18, 2024'

  // string to number (throws on invalid)
  N := StrToInt('123');
  F := StrToFloat('3.14');
  D := StrToDateTime('2024-06-18');

  // safe parsing (TryStrTo...)
  if TryStrToInt('123', N) then
    WriteLn('Parsed: ', N);
  if TryStrToInt('abc', N) then
    WriteLn('Valid')
  else
    WriteLn('Invalid number');

  // IntToStr, FloatToStr
  WriteLn(IntToStr(42));
  WriteLn(FloatToStr(3.14));
end;

StringBuilder & TStringList

StringBuilder(가변)는 큰 문자열을 빌드하는 루프에 효율적입니다 — 새 문자열 생성 대신 Append가 제자리에서 수정합니다. TStringList는 Delphi의 스위스 군용 칼: 정렬, 검색, 키=값 쌍 보유(Values[]), 파일 로드/저장(항목당 한 줄), 구분된 텍스트 분할(CommaText, DelimitedText)이 가능한 문자열 목록입니다. TStringList는 문자열(S[1])과 달리 0-인덱스입니다(SL[0]). 항상 try..finally로 Free를 감싸세요. Delphi에서 텍스트 파일과 간단한 설정을 처리하는 가장 일반적인 방법입니다.

delphi
uses
  System.SysUtils, System.Classes;

var
  sb: StringBuilder;
  SL: TStringList;
  i: Integer;
begin
  // StringBuilder: efficient concatenation (mutable)
  sb := StringBuilder.Create;
  try
    for i := 1 to 1000 do
      sb.Append('Line ').Append(i).AppendLine;  // chainable
    WriteLn(sb.ToString);
  finally
    sb.Free;
  end;

  // TStringList: versatile string collection
  SL := TStringList.Create;
  try
    // add items
    SL.Add('apple');
    SL.Add('banana');
    SL.Add('cherry');
    WriteLn(SL.Count);          // 3
    WriteLn(SL[0]);             // 'apple' (0-indexed!)

    // sort and find
    SL.Sort;
    SL.Sorted := True;          // auto-sort on Add
    idx := SL.IndexOf('banana');  // find (returns -1 if not found)

    // comma-separated text
    SL.CommaText := 'red,green,blue';   // split into items
    WriteLn(SL.CommaText);               // 'blue,green,red'

    // key=value pairs
    SL.Clear;
    SL.Values['name'] := 'Alice';
    SL.Values['age'] := '30';
    WriteLn(SL.Values['name']);  // 'Alice'

    // file I/O (one line per item)
    SL.SaveToFile('items.txt');
    SL.LoadFromFile('items.txt');

    // delimited text
    SL.Delimiter := ';';
    SL.DelimitedText := 'a;b;c';
  finally
    SL.Free;
  end;
end;

Char 연산 & 인코딩

Char는 2바이트 유니코드 문자입니다. Ord()는 코드 포인트를 가져오고; Char()는 변환합니다. IsDigit/IsLetter/IsWhiteSpace/IsUpper/IsLower은 문자를 분류합니다. ToUpper/ToLower는 대소문자를 변환합니다. TEncoding.UTF8.GetBytes는 문자열을 바이트 배열로 변환합니다(파일 I/O와 네트워킹에 필수) — UTF-8은 문자당 1-4바이트를 사용합니다. TEncoding.Unicode는 UTF-16 LE입니다(항상 2바이트/문자). Base64(TNetEncoding.Base64)는 바이너리 데이터를 전송을 위해 텍스트로 인코딩합니다. String과 Char는 내부적으로 UTF-16입니다; 파일 저장과 네트워크 프로토콜을 위해 UTF-8로 변환하세요.

delphi
var
  C: Char;
  S: string;
  Bytes: TBytes;
  i: Integer;
begin
  C := 'A';
  WriteLn(Ord(C));              // 65 (ASCII/Unicode code point)
  WriteLn(Char(66));            // 'B' (code point to char)

  // char classification
  WriteLn(IsDigit('5'));        // TRUE
  WriteLn(IsLetter('A'));       // TRUE
  WriteLn(IsWhiteSpace(' '));   // TRUE
  WriteLn(IsUpper('A'));        // TRUE
  WriteLn(IsLower('a'));        // TRUE

  // case conversion
  WriteLn(ToUpper('a'));        // 'A'
  WriteLn(ToLower('A'));        // 'a'

  // iterate characters
  S := 'Hello';
  for C in S do
    Write(C, '(', Ord(C), ') ');
  WriteLn;   // H(72) e(101) l(108) l(108) o(111)

  // string <-> bytes (encoding)
  Bytes := TEncoding.UTF8.GetBytes('Hello');
  WriteLn(Length(Bytes));       // 5 (ASCII chars are 1 byte in UTF-8)
  Bytes := TEncoding.UTF8.GetBytes('héllo');
  WriteLn(Length(Bytes));       // 6 (é is 2 bytes in UTF-8)

  S := TEncoding.UTF8.GetString(Bytes);  // back to string

  // other encodings
  Bytes := TEncoding.ASCII.GetBytes('Hello');
  Bytes := TEncoding.Unicode.GetBytes('Hello');  // UTF-16 LE (2 bytes/char)

  // Base64 encoding (for binary in text)
  uses System.NetEncoding;
  var B64: string := TNetEncoding.Base64.EncodeBytesToString(Bytes);
  var Decoded: TBytes := TNetEncoding.Base64.DecodeStringToBytes(B64);

  // char arrays
  var Chars: array[0..4] of Char;
  Chars[0] := 'H'; Chars[1] := 'e'; Chars[2] := 'l'; Chars[3] := 'l'; Chars[4] := 'o';
  S := String(Chars);   // convert char array to string
end;

정규 표현식

System.RegularExpressions는 패턴 매칭을 위한 TRegex를 제공합니다. IsMatch는 테스트하고; Match는 첫 번째를 찾고; Matches는 모두를 찾습니다. Groups는 괄호로 부분을 캡처합니다 — Groups[1], Groups[2]로 접근(1-인덱스). Replace는 일치를 대체합니다($1, $2는 그룹 참조). Split은 패턴에서 분할합니다. 일반적인 정규식: \d(숫자), \w(단어 문자), \s(공백), +(1개 이상), *(0개 이상), {n}(정확히 n), ^/$(시작/끝). roCompiled는 반복 사용을 위해 컴파일합니다. 이메일, 전화번호 등 사용자 입력을 항상 정규식으로 검증하세요.

delphi
uses
  System.RegularExpressions;

var
  Input, Pattern: string;
  Match: TMatch;
  Matches: TMatchCollection;
  Result: string;
begin
  Input := 'Phone: 123-456-7890, Zip: 10001';

  // check if matches
  if TRegEx.IsMatch(Input, 'd{3}-d{3}-d{4}') then
    WriteLn('Found a phone number');

  // find first match
  Match := TRegEx.Match(Input, 'd{5}');
  if Match.Success then
    WriteLn('Zip: ', Match.Value);   // '10001'

  // find all matches
  Matches := TRegEx.Matches(Input, 'd+');
  for Match in Matches do
    WriteLn(Match.Value);   // 123, 456, 7890, 10001

  // capture groups
  Match := TRegEx.Match('2024-06-18', '(d{4})-(d{2})-(d{2})');
  if Match.Success then
  begin
    WriteLn(Match.Groups[1].Value);  // '2024' (year)
    WriteLn(Match.Groups[2].Value);  // '06' (month)
    WriteLn(Match.Groups[3].Value);  // '18' (day)
  end;

  // replace
  Result := TRegEx.Replace(Input, 'd', 'X');
  // 'Phone: XXX-XXX-XXXX, Zip: XXXXX'

  // replace with match reference ($1, $2)
  Result := TRegEx.Replace('John Doe', '(w+) (w+)', '$2, $1');
  // 'Doe, John'

  // split
  var Parts: TArray<string>;
  Parts := TRegEx.Split('a,b;;c', '[,;]+');

  // common patterns
  Pattern := '^[w.-]+@[w.-]+.w+$';   // email
  Pattern := '^https?://[w./-]+$';       // URL
  Pattern := '^d{3}-d{3}-d{4}$';       // US phone

  // compiled regex (faster for repeated use)
  var Regex := TRegEx.Create('d+', [roCompiled]);
  try
    Match := Regex.Match(Input);
  finally
    Regex.Free;
  end;
end;
04

배열, 레코드 & 컬렉션

정적 & 동적 배열

정적 배열은 컴파일 타임에 사용자 정의 인덱스 범위로 고정 크기를 가집니다(array[0..4] 또는 array[1..7]). 동적 배열(array of T)은 SetLength로 크기 조정이 가능합니다 — 0-인덱스이고 참조 카운트됩니다. High()는 마지막 인덱스를 반환하고; Length()는 개수를 반환합니다. 기존 동적 배열의 SetLength는 크기를 조정합니다(커지는 경우 기존 값 보존). 해제하려면 nil로 설정하세요. 동적 배열 리터럴은 [1, 2, 3]을 사용합니다. 다차원 동적 배열은 '배열의 배열'(들쭉날쭉) — 각 행이 다른 길이를 가질 수 있습니다.

delphi
var
  // static array (fixed size, compile-time)
  Nums: array[0..4] of Integer;        // 5 elements, indices 0..4
  Matrix: array[0..2, 0..2] of Double; // 3x3 2D array
  Days: array[1..7] of string;         // 1-indexed (custom range)

  // dynamic array (resizable at runtime)
  Dyn: array of Integer;
  Dyn2D: array of array of Integer;    // jagged 2D
  i, j: Integer;
begin
  // static array
  Nums[0] := 10;
  Nums[1] := 20;
  for i := 0 to High(Nums) do   // High(Nums) = 4
    WriteLn(Nums[i]);
  WriteLn(Length(Nums));         // 5

  // dynamic array
  SetLength(Dyn, 5);             // allocate 5 elements (0-indexed)
  Dyn[0] := 1;
  Dyn[1] := 2;
  for i := 0 to High(Dyn) do
    WriteLn(Dyn[i]);

  SetLength(Dyn, 10);            // resize (preserves existing values)
  WriteLn(Length(Dyn));          // 10

  Dyn := nil;                    // free memory

  // dynamic array literal (modern Delphi)
  Dyn := [1, 2, 3, 4, 5];
  WriteLn(Length(Dyn));          // 5

  // 2D dynamic array
  SetLength(Dyn2D, 3);           // 3 rows
  for i := 0 to 2 do
  begin
    SetLength(Dyn2D[i], 3);      // 3 cols per row
    for j := 0 to 2 do
      Dyn2D[i][j] := i * 3 + j;
  end;

  // array slice (Open Array)
  WriteLn(Length(Dyn));          // 5
end;

레코드 (구조체)

레코드는 값 타입입니다(할당 시 복사, 스택 할당) — C의 구조체처럼. 현대 Delphi 레코드는 메서드, 속성, 가시성(private/public)을 가질 수 있습니다. 레코드는 해제할 필요가 없습니다(힙 할당 없음). 변형 레코드(case...of)는 필드가 메모리를 공유하는 공용체를 만듭니다 — 타입 태그에 유용. 작고 가벼운 데이터(점, 좌표, 설정)에는 레코드를 사용하세요. 상속이나 다형성이 필요한 큰 객체에는 클래스를 사용하세요. 레코드는 더 빠르지만(힙 할당 없음) 상속될 수 없습니다.

delphi
type
  // simple record (like a struct)
  TPoint = record
    X, Y: Integer;
  end;

  // record with methods (modern Delphi)
  TPerson = record
    Name: string;
    Age: Integer;
    // methods
    function Greet: string;
    procedure Birthday;
    // properties
    property IsAdult: Boolean read GetIsAdult;
  private
    function GetIsAdult: Boolean;
  end;

  // variant record (union — fields share memory)
  TValue = record
    case IsInt: Boolean of
      True: (IntVal: Integer);
      False: (FloatVal: Double);
  end;

// implementing record methods
function TPerson.Greet: string;
begin
  Result := 'Hi, I am ' + Name;
end;

procedure TPerson.Birthday;
begin
  Inc(Age);
end;

function TPerson.GetIsAdult: Boolean;
begin
  Result := Age >= 18;
end;

var
  P: TPerson;
  Pt: TPoint;
  V: TValue;
begin
  // record assignment copies all fields (value type)
  P.Name := 'Alice';
  P.Age := 30;
  WriteLn(P.Greet);       // Hi, I am Alice
  P.Birthday;
  WriteLn(P.Age);         // 31
  WriteLn(P.IsAdult);     // TRUE

  // record constructors (modern Delphi)
  P := TPerson.Create;    // zero-initializes
  P.Name := 'Bob';

  // variant record
  V.IsInt := True;
  V.IntVal := 42;
  WriteLn(V.IntVal);      // 42
  V.IsInt := False;
  V.FloatVal := 3.14;
  WriteLn(V.FloatVal);    // 3.14
end;

집합 & 열거형

집합은 Delphi의 고유한 기능 — 열거형이나 하위 범위의 값 컬렉션(최대 256 요소). 연산자: +(합집합), -(차집합), *(교집합), =(동등성), <=(부분집합), in(멤버십). Include/Exclude는 효율적인 단일 요소 추가/제거입니다. 집합은 비트맵으로 저장됩니다(매우 빠름). 일반적인 용도: TFontStyles(fsBold, fsItalic), 검증을 위한 Char 집합(['0'..'9']), 요일. 열거형은 서수 타입 — Low()에서 High()로 반복, GetEnumName으로 문자열로 변환. 집합은 플래그 조합을 우아하고 타입 안전하게 만듭니다.

delphi
type
  // enumeration
  TDay = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
  TColor = (Red, Green, Blue);

  // set type (collection of enum values)
  TDays = set of TDay;
  TColors = set of TColor;
  TChars = set of Char;     // set of characters

var
  Weekdays: TDays;
  MyColors: TColors;
  D: TDay;
  Digits: TChars;
  C: Char;
begin
  // set operations
  Weekdays := [Mon, Tue, Wed, Thu, Fri];
  MyColors := [Red, Blue];

  // add and remove
  Include(Weekdays, Sat);    // Weekdays := Weekdays + [Sat]
  Exclude(Weekdays, Sun);    // Weekdays := Weekdays - [Sun]

  // set operators
  Weekdays := Weekdays + [Sat];     // union
  Weekdays := Weekdays - [Sat];     // difference
  Weekend := [Sat, Sun];
  if Weekdays * Weekend = [] then   // intersection is empty
    WriteLn('No overlap');

  // membership test
  if Mon in Weekdays then
    WriteLn('Monday is a weekday');

  // iterate enum
  for D := Low(TDay) to High(TDay) do
    WriteLn(D);              // 0 1 2 3 4 5 6 (ord values)

  // convert enum to string
  WriteLn(GetEnumName(TypeInfo(TDay), Ord(Mon)));  // 'Mon'

  // set of Char (common for validation)
  Digits := ['0'..'9'];
  C := '5';
  if C in Digits then
    WriteLn('Is a digit');

  // set comparison
  if MyColors = [Red, Blue] then
    WriteLn('Equal sets');
  if [Red] <= MyColors then     // subset
    WriteLn('Red is included');
end;

TList, TDictionary & 제네릭

System.Generics.Collections는 타입 안전 컬렉션을 제공합니다: TList<T>(동적 배열), TDictionary<K,V>(해시 맵), TQueue<T>(FIFO), TStack<T>(LIFO), THashSet<T>(고유 요소). 모두 제네릭입니다(컴파일 타임 타입 검사, 캐스트 없음). TList는 Add/Remove/Delete/Sort/Contains/IndexOf를 가집니다. TDictionary는 Add/Remove/TryGetValue/Keys/Values를 가집니다. TObjectList<T>는 객체를 소유합니다(자동으로 해제) — 목록이 객체 수명을 관리해야 할 때 사용하세요. 항상 try..finally로 Free를 감싸세요(이들은 레코드가 아닌 객체입니다).

delphi
uses
  System.Generics.Collections;

var
  Nums: TList<Integer>;
  Ages: TDictionary<string, Integer>;
  Unique: THashSet<string>;
  Queue: TQueue<string>;
  Stack: TStack<Integer>;
  i: Integer;
  K: string;
  V: Integer;
begin
  // TList<T>: dynamic array (generic)
  Nums := TList<Integer>.Create;
  try
    Nums.Add(1);
    Nums.Add(2);
    Nums.AddRange([3, 4, 5]);
    WriteLn(Nums.Count);          // 5
    WriteLn(Nums[0]);             // 1 (0-indexed)
    Nums[0] := 100;
    Nums.Remove(2);               // by value
    Nums.Delete(0);               // by index
    Nums.Sort;                    // sort in place
    Nums.Reverse;
    if Nums.Contains(3) then
      WriteLn('Found');
    for i in Nums do
      WriteLn(i);
  finally
    Nums.Free;
  end;

  // TDictionary<TKey, TValue>: hash map
  Ages := TDictionary<string, Integer>.Create;
  try
    Ages.Add('Alice', 30);
    Ages.Add('Bob', 25);
    Ages['Eve'] := 28;            // add or update
    if Ages.TryGetValue('Alice', V) then
      WriteLn('Alice is ', V);    // 30
    for K in Ages.Keys do
      WriteLn(K);
    for V in Ages.Values do
      WriteLn(V);
    Ages.Remove('Bob');
  finally
    Ages.Free;
  end;

  // TQueue<T> (FIFO) and TStack<T> (LIFO)
  Queue := TQueue<string>.Create;
  try
    Queue.Enqueue('first');
    Queue.Enqueue('second');
    WriteLn(Queue.Dequeue);       // 'first'
  finally
    Queue.Free;
  end;

  // TObjectList<T> (owns its objects — frees them on Clear/Free)
  // uses System.Generics.Collections;
  // var People: TObjectList<TPerson>;
  // People := TObjectList<TPerson>.Create;
  // People.Add(TPerson.Create);  // freed when People is freed
end;

배열 알고리즘 & 정렬

TArray는 배열 연산을 위한 유틸리티 클래스입니다: Sort(선택적 사용자 정의 IComparer), BinarySearch(정렬된 배열에서 빠른 검색), Reverse, Copy. TComparer<T>.Construct는 인라인으로 비교 함수를 생성합니다(익명 메서드). 필드별 정렬은 사용자 정의 비교자가 필요합니다. BinarySearch는 부울과 찾은 인덱스를 반환합니다 — 배열이 먼저 정렬되어야 합니다. 복잡한 검색의 경우 Break가 있는 선형 루프가 단순하고 명확합니다. TArray.Sort는 퀵정렬입니다(평균 O(n log n)).

delphi
uses
  System.Generics.Collections, System.Generics.Defaults;

var
  Nums: TArray<Integer>;
  People: TArray<TPerson>;
  i: Integer;
begin
  // sort an array
  Nums := TArray<Integer>.Create(5, 3, 1, 4, 2);
  TArray.Sort<Integer>(Nums);
  // Nums = [1, 2, 3, 4, 5]

  // sort descending (custom comparer)
  TArray.Sort<Integer>(Nums, TComparer<Integer>.Construct(
    function(const L, R: Integer): Integer
    begin
      Result := R - L;   // reverse comparison
    end));

  // binary search (array must be sorted)
  TArray.Sort<Integer>(Nums);
  var Found: Boolean := TArray.BinarySearch<Integer>(Nums, 3, i);
  if Found then
    WriteLn('Found at index ', i);

  // sort array of records by a field
  type TPerson = record Name: string; Age: Integer; end;
  SetLength(People, 3);
  People[0] := TPerson.Create('Alice', 30);
  People[1] := TPerson.Create('Bob', 25);
  People[2] := TPerson.Create('Carol', 28);

  TArray.Sort<TPerson>(People, TComparer<TPerson>.Construct(
    function(const L, R: TPerson): Integer
    begin
      Result := L.Age - R.Age;   // sort by age
    end));

  for i := 0 to High(People) do
    WriteLn(People[i].Name, ': ', People[i].Age);

  // reverse an array
  TArray.Reverse<Integer>(Nums);

  // copy an array
  var Copy: TArray<Integer>;
  Copy := Copy(Nums, 0, Length(Nums));

  // find with a predicate
  var FoundIdx: Integer := -1;
  for i := 0 to High(People) do
    if People[i].Age > 28 then
    begin
      FoundIdx := i;
      Break;
    end;
end;
05

프로시저, 함수 & 매개변수

프로시저 & 함수

프로시저(반환값 없음)와 함수(값 반환)는 Delphi의 서브루틴입니다. Result 변수가 반환값입니다 — 할당하세요(함수는 끝나면 반환). Exit()는 값과 함께 즉시 반환합니다(현대 구문). 전방 선언은 본체가 정의되기 전에 함수를 호출할 수 있게 합니다(상호 재귀에 유용). 함수는 레코드, 배열, 객체를 포함한 모든 타입을 반환할 수 있습니다. 값 없는 Exit는 프로시저를 떠납니다. Result 변수는 암시적으로 선언되고 반환 타입과 일치합니다.

delphi
// Procedure: performs an action (no return value)
procedure Greet(Name: string);
begin
  WriteLn('Hello, ', Name, '!');
end;

// Function: returns a value
function Add(A, B: Integer): Integer;
begin
  Result := A + B;          // Result is the return variable
  // Exit(42);              // alternative: return immediately with 42
end;

// Function with multiple return paths
function Classify(Score: Integer): string;
begin
  if Score >= 90 then
    Exit('A');              // return immediately
  if Score >= 80 then
    Exit('B');
  Result := 'F';            // default return
end;

// Function returning a record
function MakePoint(X, Y: Integer): TPoint;
begin
  Result.X := X;
  Result.Y := Y;
end;

// forward declaration (use before full definition)
function Calc(X: Integer): Integer; forward;

procedure Demo;
var
  Sum: Integer;
  P: TPoint;
begin
  Greet('Alice');               // procedure call
  Sum := Add(3, 4);             // function call
  WriteLn(Sum);                 // 7
  WriteLn(Classify(85));        // B
  P := MakePoint(3, 4);
  WriteLn(Calc(10));
end;

function Calc(X: Integer): Integer;
begin
  Result := X * 2;
end;

매개변수: Const, Var, Out, Default

const: 읽기 전용 매개변수(문자열/배열 복사도 방지 — 효율적). var: 참조 전달(호출자의 변수 수정 — C#의 ref처럼). out: 출력 전용(호출자가 초기화하지 않음; 함수가 설정). 기본 매개변수는 마지막이어야 합니다. 열린 배열 매개변수(array of T)는 모든 배열이나 리터럴 [1,2,3]을 받습니다 — 효율성을 위해 const를 사용하세요. 문자열과 배열에는 const가 선호됩니다(복사 없음); 호출자의 값을 수정해야 할 때만 var를 사용하세요. 열린 배열은 소스 배열의 범위와 관계없이 0-인덱스입니다.

delphi
// const: can't be modified (also efficient for strings/arrays)
procedure Show(const S: string);
begin
  WriteLn(S);
  // S := 'new';  // ERROR: can't modify const
end;

// var: pass by reference (can modify caller's variable)
procedure Swap(var A, B: Integer);
var
  Temp: Integer;
begin
  Temp := A;
  A := B;
  B := Temp;
end;

// out: output-only parameter (caller doesn't need to initialize)
procedure GetValues(out X, Y: Integer);
begin
  X := 10;
  Y := 20;
end;

// default parameters (must be at the end)
function Power(Base: Double; Exp: Integer = 2): Double;
begin
  Result := Power(Base, Exp);   // Math.Power
end;

// open array parameter (accepts any array)
function Sum(const Values: array of Integer): Integer;
var
  i: Integer;
begin
  Result := 0;
  for i := 0 to High(Values) do
    Result := Result + Values[i];
end;

// 'const' for open arrays (efficient — no copy)
procedure ShowAll(const Items: array of string);
var
  S: string;
begin
  for S in Items do
    WriteLn(S);
end;

var
  X, Y: Integer;
  Nums: array[0..4] of Integer;
begin
  Swap(X, Y);              // var: modifies X and Y
  GetValues(X, Y);         // out: sets X and Y
  WriteLn(Power(3));       // 9 (Exp defaults to 2)
  WriteLn(Power(2, 10));   // 1024
  WriteLn(Sum([1, 2, 3, 4, 5]));   // 15 (open array literal)
  ShowAll(['a', 'b', 'c']);
end;

오버로딩 & 기본 매개변수

오버로딩은 여러 루틴이 다른 매개변수 목록으로 같은 이름을 공유할 수 있게 합니다 — 컴파일러가 가장 일치하는 것을 선택합니다. 'overload' 지시문이 필요합니다. 오버로딩은 다른 이름을 발명하는 것(AddInt, AddDouble)보다 깔끔합니다. 기본 매개변수는 대안입니다 — 호출자가 생략할 수 있습니다. 로직이 타입별로 다를 때 오버로딩을 선호하고; 선택적 값에는 기본값을 사용하세요. 모호성(동일하게 일치하는 두 오버로드)은 컴파일 오류입니다. 오버로드는 매개변수 개수나 타입이 달라야 합니다(반환 타입만으로는 충분하지 않음).

delphi
// overloading: same name, different parameters
function Add(A, B: Integer): Integer; overload;
begin
  Result := A + B;
end;

function Add(A, B: Double): Double; overload;
begin
  Result := A + B;
end;

function Add(A, B, C: Integer): Integer; overload;
begin
  Result := A + B + C;
end;

function Add(const Values: array of Integer): Integer; overload;
var
  i: Integer;
begin
  Result := 0;
  for i := 0 to High(Values) do
    Result := Result + Values[i];
end;

// default parameters (alternative to some overloads)
function CreateRect(Left, Top: Integer; Width: Integer = 100;
  Height: Integer = 50): TRect;
begin
  Result := Rect(Left, Top, Left + Width, Top + Height);
end;

var
  R: TRect;
begin
  WriteLn(Add(1, 2));           // 3 (Integer overload)
  WriteLn(Add(1.5, 2.5));       // 4.0 (Double overload)
  WriteLn(Add(1, 2, 3));        // 6 (3-arg overload)
  WriteLn(Add([1, 2, 3, 4]));   // 10 (array overload)

  R := CreateRect(10, 20);              // uses defaults: 100x50
  R := CreateRect(10, 20, 200);         // Width=200, Height=50
  R := CreateRect(10, 20, 200, 100);    // all specified
end;

익명 메서드 & 클로저

익명 메서드(클로저)는 'reference to' 타입에 할당된 인라인 함수/프로시저입니다. 그들은 둘러싸는 범위의 변수를 캡처합니다(클로저). 'reference to function'/'reference to procedure'가 대리자 타입입니다. 익명 메서드는 함수형 프로그래밍을 가능하게 합니다: 고차 함수(Apply는 함수를 취함), 클로저(MakeMultiplier는 Factor를 기억하는 함수 반환), 사용자 정의 비교자(TComparer<T>.Construct). 제네릭 정렬, 이벤트 처리기, 콜백에 필수적입니다. 캡처된 변수는 힙 할당됩니다(둘러싸는 함수보다 오래 삽니다).

delphi
type
  TMathFunc = reference to function(X: Integer): Integer;
  TNotifyProc = reference to procedure(Msg: string);

// function that takes a function
function Apply(Func: TMathFunc; Values: array of Integer): Integer;
var
  i: Integer;
begin
  Result := 0;
  for i := 0 to High(Values) do
    Result := Result + Func(Values[i]);
end;

// function that returns a function (closure)
function MakeMultiplier(Factor: Integer): TMathFunc;
begin
  Result := function(X: Integer): Integer
           begin
             Result := X * Factor;   // captures Factor
           end;
end;

var
  Double: TMathFunc;
  Triple: TMathFunc;
begin
  // anonymous method (inline function)
  Double := function(X: Integer): Integer
            begin
              Result := X * 2;
            end;

  WriteLn(Double(21));    // 42

  // use with higher-order functions
  WriteLn(Apply(Double, [1, 2, 3, 4]));   // 20 (2+4+6+8)

  // closure: captures the Factor variable
  Triple := MakeMultiplier(3);
  WriteLn(Triple(5));     // 15

  // anonymous procedure
  var Log: TNotifyProc := procedure(Msg: string)
    begin
      WriteLn('[LOG] ', Msg);
    end;
  Log('Hello');

  // use with TList.Sort (custom comparison)
  var Nums: TList<Integer>;
  Nums := TList<Integer>.Create;
  try
    Nums.AddRange([5, 3, 1, 4, 2]);
    Nums.Sort(TComparer<Integer>.Construct(
      function(const L, R: Integer): Integer
      begin
        Result := L - R;
      end));
  finally
    Nums.Free;
  end;
end;

재귀 & 헬퍼 루틴

재귀는 자신을 호출하는 함수입니다 — 종료를 위한 기본 케이스가 필요합니다. 팩토리얼과 피보나치가 고전적인 예입니다. 꼬리 재귀(재귀 호출이 마지막 연산인 경우)는 컴파일러가 최적화할 수 있습니다. 중첩 프로시저/함수는 다른 루틴 내에 선언되고 그 변수에 접근할 수 있습니다(어휘 범위 지정) — 외부에 보일 필요 없는 헬퍼에 유용. 깊은 재귀로 스택 오버플로우를 주의하세요(큰 입력에는 반복 사용). 메모이제이션(결과 캐싱)은 피보나치 같은 재귀 알고리즘을 가속할 수 있습니다.

delphi
// classic recursion
function Factorial(N: Integer): Integer;
begin
  if N <= 1 then
    Result := 1
  else
    Result := N * Factorial(N - 1);
end;

// tail recursion (compiler may optimize)
function SumRange(N: Integer; Acc: Integer = 0): Integer;
begin
  if N = 0 then
    Result := Acc
  else
    Result := SumRange(N - 1, Acc + N);
end;

// Fibonacci (naive — exponential time)
function Fib(N: Integer): Integer;
begin
  if N < 2 then
    Result := N
  else
    Result := Fib(N - 1) + Fib(N - 2);
end;

// nested procedure (helper with access to outer variables)
procedure ProcessData(Data: array of Integer);
var
  Total: Integer;

  procedure SumAll;     // nested, sees Total and Data
  var
    i: Integer;
  begin
    Total := 0;
    for i := 0 to High(Data) do
      Total := Total + Data[i];
  end;

  function Average: Double;   // nested function
  begin
    if Length(Data) = 0 then
      Result := 0
    else
      Result := Total / Length(Data);
  end;

begin
  SumAll;               // calls nested procedure
  WriteLn('Sum: ', Total);
  WriteLn('Avg: ', Average:0:2);
end;

begin
  WriteLn(Factorial(5));     // 120
  WriteLn(SumRange(10));     // 55
  WriteLn(Fib(10));          // 55
  ProcessData([1, 2, 3, 4, 5]);
end;
06

클래스 & OOP

클래스 정의, 생성자 & 소멸자

클래스는 참조 타입입니다(힙 할당, 포인터로 접근). Create가 생성자이고; Destroy가 소멸자입니다(항상 override; Free에 의해 호출). 'inherited'는 기본 클래스의 메서드를 호출합니다. 필드는 관례상 F 접두사를 사용합니다. 속성(property X: Type read GetX write SetX)은 제어된 접근을 제공합니다 — 호출자는 P.Age를 사용하지만 setter가 검증합니다. 가시성: private(이전 Delphi에서는 유닛 전용; strict private가 진정한 private), protected(하위 클래스), public(모두), published(RTTI, 폼/인스펙터용). 객체 생성은 항상 try..finally로 감싸서 Free가 호출되도록 보장하세요.

delphi
type
  TPerson = class
  private
    FName: string;          // private field (convention: F prefix)
    FAge: Integer;
    procedure SetAge(Value: Integer);   // setter for validation
  protected
    // visible to subclasses
    function GetDescription: string; virtual;
  public
    constructor Create(Name: string; Age: Integer);   // constructor
    destructor Destroy; override;                      // destructor
    // properties (with getters/setters)
    property Name: string read FName;                  // read-only
    property Age: Integer read FAge write SetAge;      // validated
    property Description: string read GetDescription;
    // method
    function Greet: string; virtual;
  end;

constructor TPerson.Create(Name: string; Age: Integer);
begin
  inherited Create;         // call base constructor (TObject.Create)
  FName := Name;
  FAge := Age;
end;

destructor TPerson.Destroy;
begin
  // free owned objects here
  inherited;                // call base destructor
end;

procedure TPerson.SetAge(Value: Integer);
begin
  if (Value < 0) or (Value > 150) then
    raise ERangeError.Create('Invalid age');
  FAge := Value;
end;

function TPerson.GetDescription: string;
begin
  Result := Format('%s (%d)', [FName, FAge]);
end;

function TPerson.Greet: string;
begin
  Result := 'Hi, I am ' + FName;
end;

var
  P: TPerson;
begin
  P := TPerson.Create('Alice', 30);
  try
    WriteLn(P.Greet);          // Hi, I am Alice
    WriteLn(P.Description);    // Alice (30)
    P.Age := 31;               // uses setter
    // P.Age := 200;           // raises ERangeError
  finally
    P.Free;                    // calls destructor
  end;
end;

속성 & 인덱스 속성

속성은 getter/setter로 필드 접근을 캡슐화합니다. 읽기 전용 속성은 'read' 지정자만 가집니다. 'default' 지시문은 인덱스 속성을 기본으로 만듭니다 — L.Items[i] 대신 L[i]가 작동. 속성은 직접 필드 접근(read FCount)이나 메서드 접근(read GetItem write SetItem)을 가질 수 있습니다(검증/계산용). 인덱스 속성은 배열 같은 구문을 가능하게 합니다. Published 속성(published 섹션)은 RTTI와 폼 디자이너에 보입니다. 속성은 Delphi에서 데이터를 안전하게 노출하는 방법입니다 — 공개 필드보다 항상 선호하세요.

delphi
type
  TList = class
  private
    FItems: array of Integer;
    FCount: Integer;
    function GetItem(Index: Integer): Integer;
    procedure SetItem(Index: Integer; Value: Integer);
  public
    constructor Create;
    destructor Destroy; override;
    procedure Add(Value: Integer);
    // default array property (enables List[i] syntax)
    property Items[Index: Integer]: Integer read GetItem write SetItem; default;
    property Count: Integer read FCount;
  end;

constructor TList.Create;
begin
  inherited Create;
  FCount := 0;
end;

destructor TList.Destroy;
begin
  SetLength(FItems, 0);
  inherited;
end;

function TList.GetItem(Index: Integer): Integer;
begin
  if (Index < 0) or (Index >= FCount) then
    raise ERangeError.Create('Index out of range');
  Result := FItems[Index];
end;

procedure TList.SetItem(Index: Integer; Value: Integer);
begin
  if (Index < 0) or (Index >= FCount) then
    raise ERangeError.Create('Index out of range');
  FItems[Index] := Value;
end;

procedure TList.Add(Value: Integer);
begin
  Inc(FCount);
  SetLength(FItems, FCount);
  FItems[FCount - 1] := Value;
end;

var
  L: TList;
begin
  L := TList.Create;
  try
    L.Add(10);
    L.Add(20);
    WriteLn(L[0]);    // 10 (default property — no need for L.Items[0])
    L[1] := 99;       // uses setter
    WriteLn(L.Count); // 2
  finally
    L.Free;
  end;
end;

상속 & 다형성

상속: TDog = class(TAnimal)은 TDog이 TAnimal에서 상속받음을 의미. 'virtual'은 다형성을 위한 메서드를 표시하고; 'override'는 하위 클래스에서 교체합니다. 런타임에 실제 객체의 메서드가 실행됩니다(가상 디스패치) — TDog을 보유한 TAnimal 참조에서 Speak를 호출하면 TDog.Speak가 호출. 정적 메서드(Move)는 객체가 아닌 변수의 타입에 의해 결정. 'inherited'는 기본 메서드를 호출. 생성자는 가상일 수 있습니다(팩토리 패턴). 다형성에는 virtual/override를; 동작이 고정된 경우 정적 메서드를 사용. 생성한 객체는 항상 해제하세요.

delphi
type
  TAnimal = class
  public
    constructor Create; virtual;       // virtual constructor (factory pattern)
    function Speak: string; virtual;   // virtual: can be overridden
    function Move: string;             // static: can't be overridden
  end;

  TDog = class(TAnimal)
  public
    constructor Create; override;
    function Speak: string; override;  // override the virtual method
  end;

  TCat = class(TAnimal)
  public
    function Speak: string; override;
  end;

constructor TAnimal.Create;
begin
  inherited;
end;

function TAnimal.Speak: string;
begin
  Result := '...';
end;

function TAnimal.Move: string;
begin
  Result := 'Moving';
end;

constructor TDog.Create;
begin
  inherited Create;    // call TAnimal.Create
  WriteLn('Dog created');
end;

function TDog.Speak: string;
begin
  Result := 'Woof';
end;

function TCat.Speak: string;
begin
  Result := 'Meow';
end;

// polymorphism: array of base class, different behaviors
var
  Animals: array of TAnimal;
  i: Integer;
begin
  SetLength(Animals, 3);
  Animals[0] := TDog.Create;
  Animals[1] := TCat.Create;
  Animals[2] := TAnimal.Create;

  for i := 0 to High(Animals) do
  begin
    WriteLn(Animals[i].Speak);   // Woof, Meow, ... (virtual dispatch)
    WriteLn(Animals[i].Move);    // Moving, Moving, Moving (static)
  end;

  for i := 0 to High(Animals) do
    Animals[i].Free;
end;

추상 메서드 & 클래스 메서드

추상 클래스(class abstract)는 인스턴스화할 수 없습니다 — 하위 클래스를 위한 계약을 정의. 추상 메서드(virtual; abstract)는 구현이 없습니다 — 하위 클래스가 반드시 override해야 함. 이것은 모든 도형이 Area/Perimeter를 제공하도록 강제. 클래스 메서드(class function/procedure)는 인스턴스가 필요 없음 — TShape.ShapeCount로 호출. 클래스 변수(class var)는 모든 인스턴스에서 공유. 템플릿 메서드 패턴: TShape.Describe는 추상 Area/Perimeter를 호출, 하위 클래스가 채움. 추상 메서드는 'what'을 정의; 하위 클래스는 'how'를 정의.

delphi
type
  TShape = class abstract        // can't be instantiated directly
  public
    function Area: Double; virtual; abstract;   // must be overridden
    function Perimeter: Double; virtual; abstract;
    procedure Describe; virtual;
    // class method (no instance needed)
    class function ShapeCount: Integer; static;
    class var FCount: Integer;    // class variable (shared)
  end;

  TCircle = class(TShape)
  private
    FRadius: Double;
  public
    constructor Create(Radius: Double);
    function Area: Double; override;
    function Perimeter: Double; override;
  end;

  TRectangle = class(TShape)
  private
    FWidth, FHeight: Double;
  public
    constructor Create(W, H: Double);
    function Area: Double; override;
    function Perimeter: Double; override;
  end;

class function TShape.ShapeCount: Integer;
begin
  Result := FCount;
end;

procedure TShape.Describe;
begin
  WriteLn(Format('Area: %.2f, Perimeter: %.2f', [Area, Perimeter]));
end;

constructor TCircle.Create(Radius: Double);
begin
  inherited Create;
  FRadius := Radius;
  Inc(FCount);
end;

function TCircle.Area: Double;
begin
  Result := Pi * FRadius * FRadius;
end;

function TCircle.Perimeter: Double;
begin
  Result := 2 * Pi * FRadius;
end;

var
  S: TShape;
begin
  // TShape.Create;  // ERROR: abstract class can't be instantiated
  S := TCircle.Create(5);
  try
    S.Describe;          // Area: 78.54, Perimeter: 31.42
    WriteLn(TShape.ShapeCount);  // class method (no instance)
  finally
    S.Free;
  end;
end;

인터페이스 & 다중 상속

인터페이스는 순수 계약입니다(필드 없음, 구현 없음) — Delphi에서 타입의 다중 상속을 달성하는 방법. 클래스는 많은 인터페이스를 구현할 수 있습니다(TButton은 IComparable, IDrawable, IDisposable 구현). 인터페이스는 QueryInterface/as 캐스트를 위한 GUID를 가질 수 있습니다. TInterfacedObject는 참조 카운팅을 제공 — 마지막 인터페이스 참조가 범위를 벗어나면 객체가 자동으로 해제됩니다(Free 호출 금지!). 디커플링에 인터페이스를 사용: 코드는 TButton이 아닌 IDrawable에 의존. 'as' 연산자는 인터페이스로 캐스트합니다(지원되지 않으면 예외 발생). 인터페이스는 Delphi의 COM 지원과 현대 플러그인 아키텍처의 근간입니다.

delphi
type
  // interface: pure contract (no implementation, no fields)
  IComparable = interface
    function CompareTo(Other: TObject): Integer;
  end;

  IDrawable = interface
    procedure Draw;
  end;

  // interfaces have GUIDs (for QueryInterface / as operator)
  IDisposable = interface
    ['{12345678-1234-1234-1234-123456789012}']
    procedure Dispose;
  end;

  // class implementing multiple interfaces
  TButton = class(TInterfacedObject, IComparable, IDrawable, IDisposable)
  private
    FLabel: string;
  public
    constructor Create(ALabel: string);
    function CompareTo(Other: TObject): Integer;
    procedure Draw;
    procedure Dispose;
  end;

constructor TButton.Create(ALabel: string);
begin
  FLabel := ALabel;
end;

function TButton.CompareTo(Other: TObject): Integer;
begin
  Result := CompareText(FLabel, (Other as TButton).FLabel);
end;

procedure TButton.Draw;
begin
  WriteLn('Drawing button: ', FLabel);
end;

procedure TButton.Dispose;
begin
  WriteLn('Disposing ', FLabel);
end;

var
  Btn: TButton;
  Drawable: IDrawable;
  Comp: IComparable;
begin
  Btn := TButton.Create('OK');
  Btn.Draw;

  // assign to interface variable (reference counting!)
  Drawable := Btn as IDrawable;
  Drawable.Draw;

  Comp := Btn;
  WriteLn(Comp.CompareTo(Btn));   // 0

  // TInterfacedObject uses reference counting
  // when the last interface reference is released, the object is freed
  // (don't call Free on interface-referenced objects!)
end;
07

예외 & 오류 처리

Try...Except...Finally

try...except는 예외를 잡습니다(C#의 try/catch처럼). 각 'on E: ExceptionType do'는 특정 예외를 처리. try...finally는 예외와 관계없이 정리가 실행되도록 보장합니다(예외 처리 없음 — Free 호출에 사용). 패턴은 try...try...except...finally입니다(처리는 내부 except, 정리는 외부 finally). 'raise'(단독)는 현재 예외를 재발생. Exception이 기본 클래스; EFileNotFoundException, EInOutError는 하위 클래스. 항상 가장 구체적인 예외를 먼저, Exception(기본)을 마지막에 두세요. 빈 except를 두지 마세요(오류를 조용히 삼킴).

delphi
var
  F: TextFile;
  S: string;
begin
  // try...except: catch exceptions
  try
    AssignFile(F, 'nonexistent.txt');
    Reset(F);
    ReadLn(F, S);
    CloseFile(F);
  except
    on E: EFileNotFoundException do
      WriteLn('File not found: ', E.Message);
    on E: EInOutError do
      WriteLn('I/O error: ', E.Message);
    on E: Exception do   // catch-all (must be last)
      WriteLn('Unexpected: ', E.ClassName, ': ', E.Message);
  end;

  // try...finally: cleanup (always runs, even on exception)
  var SL: TStringList;
  SL := TStringList.Create;
  try
    SL.LoadFromFile('data.txt');
    WriteLn(SL.Text);
  finally
    SL.Free;   // ALWAYS runs, even if an exception occurred
  end;

  // combined: try...try...except...finally
  SL := TStringList.Create;
  try
    try
      SL.LoadFromFile('data.txt');
    except
      on E: Exception do
      begin
        WriteLn('Error loading: ', E.Message);
        SL.Clear;   // fallback
      end;
    end;
    WriteLn(SL.Text);
  finally
    SL.Free;
  end;

  // re-raise
  try
    RiskyOperation;
  except
    on E: Exception do
    begin
      WriteLn('Logging: ', E.Message);
      raise;   // re-raise the same exception
    end;
  end;
end;

예외 발생 & 사용자 정의 예외

Raise는 예외를 생성합니다: raise ExceptionType.Create('message'). CreateFmt는 Format + Create와 같습니다. 사용자 정의 예외는 Exception(또는 특정 하위 클래스)에서 상속하고 추가 데이터(TransactionId)를 가질 수 있습니다. 래핑할 때 SetInner나 생성자 매개변수를 통해 원본을 보존하세요. 사용자 정의 예외는 호출자가 특정 오류 타입을 잡을 수 있게 합니다: ERangeError와 별도로 ETransactionError를 잡으세요. 항상 의미 있는 메시지를 포함. 일반적인 내장: ERangeError, EDivByZero, EConvertError, EFileNotFoundException, EAccessViolation, EListError.

delphi
uses
  System.SysUtils;

// raise built-in exceptions
procedure CheckAge(Age: Integer);
begin
  if Age < 0 then
    raise ERangeError.CreateFmt('Age cannot be negative: %d', [Age]);
  if Age > 150 then
    raise ERangeError.Create('Age unrealistic');
end;

// raise with inner exception (wrapping)
function LoadConfig(Path: string): string;
begin
  try
    Result := TFile.ReadAllText(Path);
  except
    on E: Exception do
      raise EConfigError.Create('Config load failed').SetInner(E);
  end;
end;

// custom exception class
type
  ETransactionError = class(Exception)
  private
    FTransactionId: string;
  public
    constructor Create(const Msg, TxnId: string);
    property TransactionId: string read FTransactionId;
  end;

constructor ETransactionError.Create(const Msg, TxnId: string);
begin
  inherited Create(Msg);
  FTransactionId := TxnId;
end;

// using the custom exception
procedure ProcessPayment(Amount: Double; TxnId: string);
begin
  if Amount <= 0 then
    raise ETransactionError.Create('Amount must be positive', TxnId);
  // ... process
end;

var
  E: Exception;
begin
  try
    CheckAge(-5);
  except
    on E: ERangeError do
      WriteLn('Range error: ', E.Message);
  end;

  try
    ProcessPayment(-100, 'TXN-001');
  except
    on E: ETransactionError do
      WriteLn('Transaction ', E.TransactionId, ' failed: ', E.Message);
  end;
end;

어설션 & 디버깅

Assert는 조건을 검사하고 거짓이면 EAssertionFailed를 발생 — 불변식(항상 참이어야 하는 조건)에 사용. 어설션은 {$C-}로 비활성화(또는 release 빌드에서 제거) — 입력 검증에 사용하지 마세요(예외 사용). OutputDebugString은 IDE의 이벤트 로그에 로깅합니다(파일 I/O 없음). TStopwatch는 정확하게 경과 시간을 측정. Exception.StackTrace는 디버그 정보가 필요(.map 파일 또는 JCLDebug/FastMM). {$IFDEF DEBUG}는 디버그 전용 코드를 활성화. 내부 논리 오류에는 어설션을 사용하고 사용자/외부 오류에는 예외를 사용하세요.

delphi
uses
  System.SysUtils, System.Diagnostics;

var
  Age: Integer;
  SW: TStopwatch;
begin
  // Assert: checks a condition (only in {$C+} / debug builds)
  Age := 30;
  Assert(Age >= 0, 'Age should be non-negative');
  // Assert(Age < 0, 'This will raise EAssertionFailed');

  // {$C+} / {$C-}: enable/disable assertions
  {$C-}   // disable assertions (release builds)
  Assert(False, 'This won''t fire');
  {$C+}   // re-enable

  // OutputDebugString (visible in IDE debugger)
  OutputDebugString('Processing started');

  // TStopwatch for timing
  SW := TStopwatch.StartNew;
  Sleep(100);
  SW.Stop;
  WriteLn(Format('Elapsed: %d ms', [SW.ElapsedMilliseconds]));

  // raise with stack trace (uses System.DebugUtils / JCLDebug)
  try
    raise Exception.Create('Test error');
  except
    on E: Exception do
    begin
      WriteLn(E.Message);
      WriteLn(E.StackTrace);   // needs debug info / map file
    end;
  end;

  // conditional compilation
  {$IFDEF DEBUG}
  WriteLn('Debug build');
  {$ELSE}
  WriteLn('Release build');
  {$ENDIF}

  // Trace (simple logging)
  {$IFDEF DEBUG}
  WriteLn('[TRACE] Entering ProcessData');
  {$ENDIF}
end;

예외 처리 패턴

일반적인 예외 패턴: (1) 재시도 루프 — 실패할 수 있는 작업을 while 루프 내의 try/except로 감싸고 MaxRetries 후 재발생. (2) 폴백 값 — 특정 예외(EConvertError)를 잡고 기본값 반환; 진정으로 예상되는 예외만 삼키기. (3) 리소스 보호 — Create/Free를 항상 try/finally로 감싸서 예외 발생 시 객체가 해제되도록(이것이 가장 중요한 Delphi 관용구). (4) 다중 리소스 — try/finally 블록 중첩; 각 리소스를 자체 보호 블록 내에서 획득. (5) 검증 — 설명 메시지와 함께 특정 예외 타입(EArgumentException, ERangeError)을 일찍 발생. Exception을 잡고 조용히 계속하지 마세요 — 최소한 로깅. 정리에는 try/finally를, 진정한 복구에는 try/except를 선호.

delphi
uses
  System.SysUtils, System.Classes;

// Pattern 1: Retry with backoff
function DownloadWithRetry(const URL: string; MaxRetries: Integer): string;
var
  Attempt: Integer;
  Done: Boolean;
begin
  Attempt := 0;
  Done := False;
  while (not Done) and (Attempt < MaxRetries) do
  begin
    Inc(Attempt);
    try
      Result := DoDownload(URL);   // may raise EDownloadError
      Done := True;
    except
      on E: Exception do
      begin
        if Attempt >= MaxRetries then
          raise;   // re-raise after final attempt
        Sleep(Attempt * 500);   // exponential-ish backoff
      end;
    end;
  end;
end;

// Pattern 2: Fallback / default value
function SafeReadInt(const SL: TStringList; const Key: string; Default: Integer): Integer;
begin
  try
    Result := StrToInt(SL.Values[Key]);
  except
    on EConvertError do
      Result := Default;   // swallow and use default
  end;
end;

// Pattern 3: Resource protection (always Free)
procedure ProcessFile(const Path: string);
var
  SL: TStringList;
begin
  SL := TStringList.Create;
  try
    SL.LoadFromFile(Path);
    Transform(SL);
    SL.SaveToFile(Path + '.bak');
  finally
    SL.Free;   // guaranteed cleanup
  end;
end;

// Pattern 4: Acquire multiple resources safely
procedure CopyFile(const Src, Dst: string);
var
  SrcList, DstList: TStringList;
begin
  SrcList := TStringList.Create;
  try
    SrcList.LoadFromFile(Src);
    DstList := TStringList.Create;
    try
      DstList.Assign(SrcList);
      DstList.SaveToFile(Dst);
    finally
      DstList.Free;
    end;
  finally
    SrcList.Free;
  end;
end;

// Pattern 5: Validation with multiple checks
procedure ValidateUser(const Name: string; Age: Integer);
begin
  if Name = '' then
    raise EArgumentException.Create('Name required');
  if Length(Name) > 50 then
    raise EArgumentException.Create('Name too long');
  if (Age < 0) or (Age > 150) then
    raise ERangeError.CreateFmt('Invalid age: %d', [Age]);
end;

로깅 & 오류 보고

프로덕션 로거는 다음이 필요: (1) 스레드 안전 — TCriticalSection은 쓰기를 직렬화(여러 스레드가 동시에 로깅할 수 있음). (2) 심각도 수준 — TLogLevel enum으로 필터링(예: 프로덕션에서 llDebug 억제). (3) 포맷된 출력 — 줄당 DateTime + 수준 + 메시지, 나중에 파싱 가능. (4) 각 쓰기 후 플러시 — 크래시에서 로그가 생존(AV에서 플러시되지 않은 버퍼링된 쓰기는 손실). (5) 예외 로깅 — LogException은 ClassName + Message + 컨텍스트를 캡처. 로그 후 재발생 패턴은 오류를 기록하지만 상위 계층이 처리하게 둡니다. 고성능 로깅의 경우 잠금 없는 큐나 외부 라이브러리(Log4Delphi 같은)를 고려. 파일 핸들을 닫기 위해 finally에서 항상 로거를 Free하세요.

delphi
uses
  System.SysUtils, System.Classes, System.IOUtils, System.SyncObjs;

type
  TLogLevel = (llDebug, llInfo, llWarning, llError, llFatal);

  TLogger = class
  private
    FLock: TCriticalSection;
    FFile: TextFile;
    FMinLevel: TLogLevel;
    function LevelToStr(L: TLogLevel): string;
  public
    constructor Create(const LogPath: string; MinLevel: TLogLevel);
    destructor Destroy; override;
    procedure Log(Level: TLogLevel; const Msg: string); overload;
    procedure Log(Level: TLogLevel; const Fmt: string; const Args: array of const); overload;
    procedure LogException(E: Exception; const Context: string);
  end;

constructor TLogger.Create(const LogPath: string; MinLevel: TLogLevel);
begin
  FLock := TCriticalSection.Create;
  FMinLevel := MinLevel;
  AssignFile(FFile, LogPath);
  if FileExists(LogPath) then
    Append(FFile)
  else
    Rewrite(FFile);
end;

destructor TLogger.Destroy;
begin
  CloseFile(FFile);
  FLock.Free;
  inherited;
end;

function TLogger.LevelToStr(L: TLogLevel): string;
begin
  case L of
    llDebug:   Result := 'DEBUG';
    llInfo:    Result := 'INFO';
    llWarning: Result := 'WARN';
    llError:   Result := 'ERROR';
    llFatal:   Result := 'FATAL';
  end;
end;

procedure TLogger.Log(Level: TLogLevel; const Msg: string);
begin
  if Level < FMinLevel then Exit;
  FLock.Enter;
  try
    WriteLn(FFile, Format('%s [%s] %s', [DateTimeToStr(Now), LevelToStr(Level), Msg]));
    Flush(FFile);   // ensure written to disk
  finally
    FLock.Leave;
  end;
end;

procedure TLogger.Log(Level: TLogLevel; const Fmt: string; const Args: array of const);
begin
  Log(Level, Format(Fmt, Args));
end;

procedure TLogger.LogException(E: Exception; const Context: string);
begin
  Log(llError, '%s: %s: %s', [Context, E.ClassName, E.Message]);
end;

// Usage
var
  Logger: TLogger;
begin
  Logger := TLogger.Create('app.log', llInfo);
  try
    Logger.Log(llInfo, 'Application started');
    try
      RiskyOperation;
    except
      on E: Exception do
      begin
        Logger.LogException(E, 'RiskyOperation');
        raise;   // log and re-raise
      end;
    end;
  finally
    Logger.Free;
  end;
end;
08

파일 I/O & 스트림

텍스트 파일 (레거시 & 현대)

두 가지 접근: 레거시(AssignFile/Reset/Rewrite/ReadLn/WriteLn/CloseFile)는 고전 Pascal — 단순한 텍스트 I/O에 좋지만 오류 발생이 쉬움(기본적으로 예외 없음). 현대(System.IOUtils의 TFile)는 더 깔끔: WriteAllText, ReadAllText, ReadAllLines, AppendAllText, Exists. TFile 메서드는 오류 시 예외 발생(try...except 사용). 큰 파일의 경우 StreamReader/StreamWriter 사용(한 줄씩, 낮은 메모리). 항상 파일을 닫으세요(레거시는 CloseFile, 또는 try..finally 사용). 새 코드에는 TFile이 선호됩니다 — 더 안전하고 일관적입니다.

delphi
uses
  System.SysUtils, System.Classes, System.IOUtils;

// LEGACY: AssignFile / ReadLn / WriteLn (Pascal-style)
var
  F: TextFile;
  Line: string;
begin
  // write
  AssignFile(F, 'output.txt');
  Rewrite(F);                // create/overwrite
  try
    WriteLn(F, 'Hello, File!');
    WriteLn(F, 'Second line');
  finally
    CloseFile(F);
  end;

  // append
  AssignFile(F, 'output.txt');
  Append(F);
  try
    WriteLn(F, 'Appended line');
  finally
    CloseFile(F);
  end;

  // read line by line
  AssignFile(F, 'output.txt');
  Reset(F);                  // open for reading
  try
    while not EOF(F) do
    begin
      ReadLn(F, Line);
      WriteLn(Line);
    end;
  finally
    CloseFile(F);
  end;
end;

// MODERN: TFile (System.IOUtils)
var
  Content: string;
  Lines: TArray<string>;
begin
  // write all text
  TFile.WriteAllText('output.txt', 'Hello, World!');

  // append
  TFile.AppendAllText('log.txt', 'New entry' + sLineBreak);

  // read all text
  Content := TFile.ReadAllText('output.txt');

  // read all lines
  Lines := TFile.ReadAllLines('data.csv');
  for Line in Lines do
    WriteLn(Line);

  // write all lines
  TFile.WriteAllLines('nums.txt', ['one', 'two', 'three']);

  // file exists?
  if TFile.Exists('data.txt') then
    WriteLn('Found');
end;

파일 & CSV를 위한 TStringList

TStringList는 텍스트 파일과 단순한 CSV를 처리하는 가장 쉬운 방법입니다. LoadFromFile/SaveToFile은 전체 파일을 읽고/씁니다(항목당 한 줄). CommaText는 쉼표로 구분된 값을 분할/결합; DelimitedText는 사용자 정의 Delimiter를 사용. Values[]는 키=값 쌍을 처리(단순한 INI 파일처럼). Sorted=True는 자동 정렬; Find는 이진 검색(정렬된 목록에서 IndexOf보다 빠름). Duplicates는 중복 추가 시 동작 제어(dupIgnore, dupAccept, dupError). 복잡한 CSV(쉼표가 있는 따옴표 필드)의 경우 전용 CSV 파서를 사용. TStringList는 0-인덱스입니다.

delphi
uses
  System.Classes;

var
  SL: TStringList;
  i: Integer;
begin
  SL := TStringList.Create;
  try
    // load a text file (one line per item)
    SL.LoadFromFile('data.txt');

    // iterate lines
    for i := 0 to SL.Count - 1 do
      WriteLn(SL[i]);

    // add and save
    SL.Add('New line');
    SL.SaveToFile('output.txt');

    // CSV handling (CommaText)
    SL.Clear;
    SL.CommaText := 'Alice,30,NYC';
    WriteLn(SL[0]);   // Alice
    WriteLn(SL[1]);   // 30
    WriteLn(SL[2]);   // NYC

    // custom delimiter
    SL.Clear;
    SL.Delimiter := '|';
    SL.DelimitedText := 'a|b|c';

    // key=value pairs (INI-style)
    SL.Clear;
    SL.Values['name'] := 'Alice';
    SL.Values['age'] := '30';
    WriteLn(SL.Values['name']);   // Alice
    SL.SaveToFile('config.ini');

    // sorted list (auto-sorts on Add)
    SL.Clear;
    SL.Sorted := True;
    SL.Add('cherry');
    SL.Add('apple');
    SL.Add('banana');
    // SL is now: apple, banana, cherry

    // find (binary search — list must be sorted)
    if SL.Find('banana', i) then
      WriteLn('Found at ', i);

    // duplicate handling
    SL.Duplicates := dupIgnore;   // ignore duplicates (sorted only)
    SL.Duplicates := dupError;    // raise on duplicates
  finally
    SL.Free;
  end;
end;

스트림 & 바이너리 I/O

TFileStream은 저수준 바이트 I/O입니다(Read/Write 버퍼, 검색을 위한 Position). TBinaryWriter/Reader는 타입화된 값을 쓰기/읽기(Int32, Double, String, Boolean) — 읽기 순서는 쓰기 순서와 일치해야 함. TStreamReader/Writer는 인코딩(UTF-8, ASCII, Unicode)으로 텍스트 처리 — 비-ASCII 문자가 있는 텍스트 파일에 사용. 모든 스트림은 해제되어야 함(try..finally). fmCreate는 생성/덮어쓰기; fmOpenRead는 읽기 전용 열기; fmOpenWrite는 쓰기용 열기. 큰 파일의 경우 LoadFromFile(전체 파일 로드) 대신 StreamReader로 한 줄씩 읽으세요(낮은 메모리).

delphi
uses
  System.Classes, System.SysUtils;

var
  FS: TFileStream;
  BR: TBinaryReader;
  BW: TBinaryWriter;
  SR: TStreamReader;
  SW: TStreamWriter;
  Buffer: TBytes;
  i: Integer;
begin
  // TFileStream: low-level file access
  FS := TFileStream.Create('data.bin', fmCreate);   // fmCreate, fmOpenRead, fmOpenWrite
  try
    // write bytes
    SetLength(Buffer, 4);
    Buffer[0] := 1; Buffer[1] := 2; Buffer[2] := 3; Buffer[3] := 4;
    FS.Write(Buffer[0], Length(Buffer));

    // read
    FS.Position := 0;   // rewind
    SetLength(Buffer, 4);
    FS.Read(Buffer[0], 4);
  finally
    FS.Free;
  end;

  // TBinaryWriter / TBinaryReader (typed binary I/O)
  BW := TBinaryWriter.Create('data.bin');
  try
    BW.Write(42);          // Integer
    BW.Write(3.14);        // Double
    BW.Write('Hello');     // length-prefixed string
    BW.Write(True);        // Boolean
  finally
    BW.Free;
  end;

  BR := TBinaryReader.Create('data.bin');
  try
    WriteLn(BR.ReadInt32);    // 42
    WriteLn(BR.ReadDouble);   // 3.14
    WriteLn(BR.ReadString);   // Hello
    WriteLn(BR.ReadBoolean);  // TRUE
  finally
    BR.Free;
  end;

  // TStreamReader / TStreamWriter (text, with encoding)
  SW := TStreamWriter.Create('utf8.txt', False, TEncoding.UTF8);
  try
    SW.WriteLine('Hello, UTF-8!');
    SW.WriteLine('héllo wörld');
  finally
    SW.Free;
  end;

  SR := TStreamReader.Create('utf8.txt', TEncoding.UTF8);
  try
    while not SR.EndOfStream do
      WriteLn(SR.ReadLine);
  finally
    SR.Free;
  end;
end;

디렉토리 & 경로 연산

System.IOUtils는 현대 파일 연산을 위한 TPath, TFile, TDirectory를 제공. TPath.Combine은 경로를 안전하게 조인(크로스 플랫폼). TPath.GetTempFileName은 고유한 임시 파일을 생성. TDirectory.GetFiles은 검색 패턴과 재귀 검색(soAllDirectories)을 지원. TFile.Copy/Move/Delete는 단순한 파일 연산. TFileInfo는 파일 메타데이터(크기, 타임스탬프)를 제공. 경로에는 항상 문자열 연결 대신 TPath 메서드를 사용(구분자를 올바르게 처리). 이 클래스들은 Windows, macOS, Linux에서 작동(FireMonkey/FMX).

delphi
uses
  System.IOUtils, System.SysUtils;

var
  Files: TArray<string>;
  Dirs: TArray<string>;
  Path: string;
  i: Integer;
begin
  // TPath (cross-platform path handling)
  Path := TPath.Combine('folder', 'sub', 'file.txt');  // folder/sub/file.txt
  WriteLn(TPath.GetFileName('C:\temp\data.txt'));     // data.txt
  WriteLn(TPath.GetExtension('photo.JPG'));             // .JPG
  WriteLn(TPath.GetFileNameWithoutExtension('data.txt')); // data
  WriteLn(TPath.GetDirectoryName('C:\temp\data.txt'));  // C:\temp
  WriteLn(TPath.GetFullPath('data.txt'));               // absolute path

  // temp files
  WriteLn(TPath.GetTempFileName);   // creates a temp file
  WriteLn(TPath.GetTempPath);       // temp directory

  // special folders
  WriteLn(TPath.GetDocumentsPath);
  WriteLn(TPath.GetHomePath);

  // TDirectory
  TDirectory.CreateDirectory('backup\2024\june');

  Files := TDirectory.GetFiles('C:\temp', '*.txt');
  for i := 0 to High(Files) do
    WriteLn(Files[i]);

  // recursive search
  Files := TDirectory.GetFiles('C:\temp', '*.*', TSearchOption.soAllDirectories);

  Dirs := TDirectory.GetDirectories('C:\temp');
  for i := 0 to High(Dirs) do
    WriteLn(Dirs[i]);

  if TDirectory.Exists('old') then
    TDirectory.Delete('old', True);   // recursive delete

  // TFile operations
  TFile.Copy('source.txt', 'dest.txt', True);   // overwrite
  TFile.Move('old.txt', 'new.txt');
  TFile.Delete('unwanted.txt');

  // file info
  var Info: TFileInfo := TFileInfo.Create('data.txt');
  try
    WriteLn(Info.Length);          // size in bytes
    WriteLn(Info.CreationTime);
    WriteLn(Info.LastWriteTime);
    WriteLn(Info.Extension);
  finally
    Info.Free;
  end;
end;

INI 파일 & JSON

TIniFile은 INI 설정 파일을 읽고/씁니다([괄호]의 섹션, 키=값). ReadString/ReadInteger/ReadBool은 기본값을 가집니다(키가 없으면 반환). INI 파일은 단순하고 사람이 읽을 수 있는 설정 — 사용자 기본 설정에 좋습니다. 구조화된 데이터의 경우 JSON(System.JSON)을 사용. TJSONObject는 JSON 객체를 빌드/파싱; TJSONArray는 배열. AddPair는 키-값 추가; GetValue<T>는 타입화된 값을 검색. ParseJSONValue는 JSON 문자열을 파싱. JSON은 API, 복잡한 설정, 데이터 교환에 이상적. REST 클라이언트의 경우 TRESTClient나 Indy 컴포넌트를 사용하세요.

delphi
uses
  System.IniFiles, System.JSON, System.SysUtils;

// INI files (simple config)
var
  Ini: TIniFile;
begin
  Ini := TIniFile.Create('config.ini');
  try
    // write
    Ini.WriteString('User', 'Name', 'Alice');
    Ini.WriteInteger('User', 'Age', 30);
    Ini.WriteBool('User', 'Active', True);
    Ini.WriteDateTime('Session', 'LastLogin', Now);

    // read (with defaults)
    WriteLn(Ini.ReadString('User', 'Name', 'Unknown'));   // Alice
    WriteLn(Ini.ReadInteger('User', 'Age', 0));            // 30
    WriteLn(Ini.ReadBool('User', 'Active', False));        // TRUE

    // read a whole section
    var SL: TStringList;
    SL := TStringList.Create;
    try
      Ini.ReadSection('User', SL);
      // SL = ['Name', 'Age', 'Active']
    finally
      SL.Free;
    end;
  finally
    Ini.Free;
  end;
end;

// JSON (System.JSON)
var
  Obj: TJSONObject;
  Arr: TJSONArray;
  JSON: string;
  i: Integer;
begin
  // build JSON
  Obj := TJSONObject.Create;
  try
    Obj.AddPair('name', 'Alice');
    Obj.AddPair('age', TJSONNumber.Create(30));
    Obj.AddPair('active', TJSONBool.Create(True));

    var Hobbies := TJSONArray.Create;
    Hobbies.Add('reading').Add('coding');
    Obj.AddPair('hobbies', Hobbies);

    JSON := Obj.ToJSON;
    // {"name":"Alice","age":30,"active":true,"hobbies":["reading","coding"]}
  finally
    Obj.Free;
  end;

  // parse JSON
  Obj := TJSONObject.ParseJSONValue(JSON) as TJSONObject;
  try
    WriteLn(Obj.GetValue<string>('name'));        // Alice
    WriteLn(Obj.GetValue<Integer>('age'));         // 30
    Arr := Obj.GetValue<TJSONArray>('hobbies');
    for i := 0 to Arr.Count - 1 do
      WriteLn(Arr.Items[i].Value);                 // reading, coding
  finally
    Obj.Free;
  end;
end;
09

VCL 컴포넌트 심층

폼 & 컴포넌트 수명 주기

VCL 폼은 엄격한 수명 주기를 따릅니다: OnCreate(리소스 할당, 초기화) → OnShow(폼이 보임) → OnActivate → OnResize → OnPaint → ... → OnCloseQuery(닫기 취소 가능) → OnClose → OnDestroy(리소스 해제). 리소스 관리를 위해 OnCreate와 OnDestroy를 항상 짝지으세요. OnCloseQuery로 닫기를 방지할 수 있습니다(CanClose := False 설정). Sender는 이벤트를 발생시킨 컴포넌트. 컴포넌트는 자식을 소유 — 폼을 해제하면 모든 컴포넌트가 자동으로 해제됩니다.

delphi
type
  TMainForm = class(TForm)
    Edit1: TEdit;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    FData: TStringList;
  public
    property Data: TStringList read FData;
  end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  FData := TStringList.Create;     // allocate in OnCreate
  Caption := 'My App v1.0';
end;

procedure TMainForm.FormShow(Sender: TObject);
begin
  Edit1.SetFocus;                  // focus when form is visible
end;

procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  CanClose := MessageDlg('Close?', mtConfirmation, [mbYes, mbNo], 0) = mrYes;
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  FData.Free;                      // free in OnDestroy (pairs with OnCreate)
end;

일반 VCL 컨트롤

VCL은 풍부한 컨트롤 세트를 제공: TEdit(한 줄 텍스트), TMemo(여러 줄 텍스트), TLabel(편집 불가 텍스트), TButton, TCheckBox, TRadioButton, TComboBox(드롭다운), TListBox(선택 가능한 목록). TStrings는 기본 컬렉션(Lines, Items는 TStrings). ItemIndex는 항목 선택(0-기반, -1 = 없음). ComboBox 스타일: csDropDown(편집 가능), csDropDownList(읽기 전용). RadioGroup은 ItemIndex와 함께 라디오 버튼을 그룹화. Sorted는 항목 자동 정렬. PasswordChar는 TEdit에서 입력을 마스킹.

delphi
// Edit, Memo, Label, Button, CheckBox, RadioButton
Edit1.Text := 'Hello';
Edit1.MaxLength := 50;
Edit1.PasswordChar := '*';         // mask input

Memo1.Lines.Add('Line 1');         // TStrings collection
Memo1.Lines.LoadFromFile('notes.txt');
Memo1.WordWrap := True;
Memo1.ScrollBars := ssVertical;

// ComboBox & ListBox
ComboBox1.Items.Add('Option A');
ComboBox1.ItemIndex := 0;          // select first
ComboBox1.Style := csDropDownList; // read-only selection

ListBox1.Items.Add('Item 1');
ListBox1.Sorted := True;
ShowMessage(ListBox1.Items[ListBox1.ItemIndex]);

// CheckBox & RadioButton
if CheckBox1.Checked then
  ShowMessage('Checked');
RadioGroup1.Items.Add('Red');
RadioGroup1.Items.Add('Green');
RadioGroup1.ItemIndex := 0;

StringGrid & DBGrid

TStringGrid는 스프레드시트 같은 그리드로 표 형식 데이터를 표시. Cells[Col, Row]는 개별 셀에 접근(0-인덱스). FixedRows/FixedCols는 스크롤되지 않는 헤더 생성. ColWidths/RowHeights는 크기 사용자 정의. goEditing(편집 가능 셀), goColSizing(열 크기 조정), goRowSelect 같은 옵션이 동작을 활성화. OnDrawCell은 Canvas로 사용자 정의 렌더링 허용. TDBGrid는 TDataSource를 통해 데이터셋(TTable, TQuery)에 직접 연결 — 데이터베이스 레코드를 자동으로 표시하고 편집. 데이터베이스 데이터에는 TDBGrid를, 메모리 내 데이터에는 TStringGrid를 사용.

delphi
// TStringGrid - spreadsheet-like grid
StringGrid1.RowCount := 5;
StringGrid1.ColCount := 4;
StringGrid1.FixedRows := 1;        // header row
StringGrid1.FixedCols := 0;

// set headers
StringGrid1.Cells[0, 0] := 'Name';
StringGrid1.Cells[1, 0] := 'Age';
StringGrid1.Cells[2, 0] := 'City';

// populate data
StringGrid1.Cells[0, 1] := 'Alice';
StringGrid1.Cells[1, 1] := '30';
StringGrid1.Cells[2, 1] := 'NYC';

// customize appearance
StringGrid1.ColWidths[0] := 120;
StringGrid1.RowHeights[0] := 30;
StringGrid1.Options := StringGrid1.Options + [goEditing, goColSizing];

// onDrawCell for custom rendering
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
  Rect: TRect; State: TGridDrawState);
begin
  if ARow = 0 then
    StringGrid1.Canvas.Font.Style := [fsBold];
  StringGrid1.Canvas.TextRect(Rect, Rect.Left + 4, Rect.Top + 2,
    StringGrid1.Cells[ACol, ARow]);
end;

TTreeView & TListView

TTreeView는 TTreeNode 객체로 계층적(트리) 데이터를 표시. AddChild는 중첩 노드 생성. Expand(True)는 재귀적으로 확장. GetNext는 깊이 우선 탐색; GetNextSibling은 레벨별 탐색. BeginUpdate/EndUpdate는 성능을 위해 변경을 일괄 처리. TListView는 다양한 보기 스타일로 항목 표시: vsIcon, vsSmallIcon, vsList, vsReport(열). Caption이 첫 번째 열; SubItems가 후속 열. 둘 다 OnGetNodeData/OnData 이벤트를 통해 대규모 데이터셋을 위한 owner-data(가상) 모드를 지원.

delphi
// TTreeView - hierarchical data
var
  RootNode, ChildNode: TTreeNode;
begin
  TreeView1.Items.BeginUpdate;
  try
    TreeView1.Items.Clear;
    RootNode := TreeView1.Items.Add(nil, 'Root');
    ChildNode := TreeView1.Items.AddChild(RootNode, 'Child 1');
    TreeView1.Items.AddChild(RootNode, 'Child 2');
    TreeView1.Items.AddChild(ChildNode, 'Grandchild');
    RootNode.Expand(True);         // expand all children
  finally
    TreeView1.Items.EndUpdate;
  end;

  // iterate
  var Node := TreeView1.Items.GetFirstNode;
  while Node <> nil do
  begin
    ShowMessage(Node.Text);
    Node := Node.GetNext;          // depth-first traversal
  end;
end;

// TListView - report view with columns
ListView1.ViewStyle := vsReport;
ListView1.Columns.Add.Caption := 'Name';
ListView1.Columns.Add.Caption := 'Size';
var Item := ListView1.Items.Add;
Item.Caption := 'file.txt';
Item.SubItems.Add('1.2 KB');

대화상자 & 공통 컴포넌트

Delphi는 표준 대화상자 컴포넌트를 제공: TOpenDialog/TSaveDialog(파일 선택), TOpenPictureDialog(이미지 미리보기), TColorDialog, TFontDialog, TPrintDialog. Execute는 사용자가 OK를 클릭하면 True 반환. Filter는 파일 타입 패턴 설정('Description|*.ext'). MessageDlg는 타입(mtInformation, mtWarning, mtError, mtConfirmation)과 버튼 세트([mbYes, mbNo, mbOK, mbCancel])로 모달 메시지 박스 표시. InputBox/InputQuery는 사용자 텍스트 입력 받기. TPageControl은 TTabSheet 페이지로 탭 인터페이스 관리. 모든 대화상자는 폼에 배치된 비시각적 컴포넌트입니다.

delphi
// File dialogs
if OpenDialog1.Execute then
  ShowMessage('Selected: ' + OpenDialog1.FileName);

if SaveDialog1.Execute then
  ShowMessage('Save to: ' + SaveDialog1.FileName);

OpenDialog1.Filter := 'Text files (*.txt)|*.txt|All files (*.*)|*.*';
OpenDialog1.DefaultExt := 'txt';
OpenDialog1.Options := [ofFileMustExist, ofAllowMultiSelect];

// Color & Font dialogs
if ColorDialog1.Execute then
  Panel1.Color := ColorDialog1.Color;

if FontDialog1.Execute then
  Label1.Font := FontDialog1.Font;

// Message dialogs
case MessageDlg('Delete file?', mtWarning, [mbYes, mbNo, mbCancel], 0) of
  mrYes: DeleteFile('temp.txt');
  mrNo: ShowMessage('Cancelled');
end;

// InputBox & InputQuery
var Name := InputBox('Login', 'Enter name:', 'guest');
var Value: string;
if InputQuery('Settings', 'Port:', Value) then
  ShowMessage('Port: ' + Value);

// TPageControl (tabs)
var TabSheet := TTabSheet.Create(PageControl1);
TabSheet.PageControl := PageControl1;
TabSheet.Caption := 'Tab 1';
10

이벤트 기반 프로그래밍

이벤트 & 이벤트 처리기

Delphi의 이벤트는 메서드 포인터입니다(procedure of object). TNotifyEvent는 표준 이벤트 타입: procedure(Sender: TObject) of object. 이벤트는 속성 — 디자인 타임(객체 검사기)이나 런타임에 처리기 할당. 이벤트 처리기를 호출하기 전에 항상 Assigned()를 확인(할당되지 않으면 nil일 수 있음). Sender는 이벤트를 발생시킨 객체. 사용자 정의 이벤트는 'of object'를 사용하여 인스턴스 메서드에 바인딩. var 매개변수(OnKeyPress의 var Key: Char 같은)는 처리기가 값을 수정할 수 있게 — Key := #0으로 입력을 억제.

delphi
// Event type declaration
type
  TNotifyEvent = procedure(Sender: TObject) of object;
  TKeyPressEvent = procedure(Sender: TObject; var Key: Char) of object;

  TCounter = class
  private
    FValue: Integer;
    FOnChange: TNotifyEvent;
    FOnThresholdReached: TThresholdEvent;
  public
    property Value: Integer read FValue write SetValue;
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
  end;

procedure TCounter.SetValue(const NewValue: Integer);
begin
  if FValue <> NewValue then
  begin
    FValue := NewValue;
    if Assigned(FOnChange) then       // check before calling
      FOnChange(Self);                // trigger event
  end;
end;

// assigning handler at runtime
Counter1.OnChange := CounterChangeHandler;

procedure TForm1.CounterChangeHandler(Sender: TObject);
begin
  Label1.Caption := 'Value: ' + IntToStr((Sender as TCounter).Value);
end;

대리자 & 메서드 포인터

메서드 포인터('of object')는 메서드 주소와 객체 인스턴스를 모두 전달 — Self에 대한 클로저. 일반 프로시저 포인터('of object' 없는)는 독립형 함수를 가리킴. 메서드 포인터는 콜백, 전략 패턴, 이벤트 시스템을 가능하게. Op := Calc.Add 할당은 참조를 저장; Op(10, 20) 호출은 Calc 인스턴스에서 Calc.Add를 호출. 익명 메서드(reference to function)는 클로저 의미론을 가진 현대적 대안. 메서드 포인터는 Delphi의 이벤트 기반 VCL/FMX 아키텍처의 근간입니다.

delphi
type
  TMathFunc = function(X, Y: Integer): Integer of object;

  TCalculator = class
    function Add(X, Y: Integer): Integer;
    function Subtract(X, Y: Integer): Integer;
    function Multiply(X, Y: Integer): Integer;
  end;

function TCalculator.Add(X, Y: Integer): Integer;
begin
  Result := X + Y;
end;

// store and invoke method reference
var
  Calc: TCalculator;
  Op: TMathFunc;
begin
  Calc := TCalculator.Create;
  try
    Op := Calc.Add;           // method pointer
    ShowMessage(IntToStr(Op(10, 20)));  // 30

    Op := Calc.Subtract;
    ShowMessage(IntToStr(Op(10, 20)));  // -10
  finally
    Calc.Free;
  end;
end;

// regular procedure pointers (not of object)
type
  TSimpleFunc = function(X: Integer): Integer;
function DoubleIt(X: Integer): Integer;
begin
  Result := X * 2;
end;
var F: TSimpleFunc := DoubleIt;

익명 메서드 & 클로저

익명 메서드(reference to function/procedure)는 둘러싸는 범위의 변수를 캡처하는 인라인 클로저. 'reference to' 타입은 메서드 포인터의 현대적 대안 — 참조로 변수를 캡처, 캡처된 변수의 변경이 클로저에 영향. 이것은 함수형 패턴을 가능하게: map/filter/reduce, 콜백, 지연 실행. TFunc<T,TResult>와 TProc<T>는 System.SysUtils의 제네릭 별칭. 익명 메서드는 병렬 프로그래밍(PPL)과 현대 Delphi 관용구에 필수. 캡처된 변수는 선언 범위보다 오래 삽니다.

delphi
type
  TFuncInt = reference to function(X: Integer): Integer;
  TProcStr = reference to procedure(const S: string);

procedure Apply(const Func: TFuncInt; Values: array of Integer);
var
  I: Integer;
begin
  for I := 0 to High(Values) do
    WriteLn(Func(Values[I]));
end;

var
  Multiplier: Integer;
  Double: TFuncInt;
begin
  Multiplier := 2;
  // anonymous method captures Multiplier (closure)
  Double := function(X: Integer): Integer
    begin
      Result := X * Multiplier;
    end;

  Apply(Double, [1, 2, 3, 4, 5]);   // 2, 4, 6, 8, 10

  Multiplier := 3;
  Apply(Double, [1, 2, 3]);          // 3, 6, 9 (captures by reference!)

  // anonymous procedure
  var Log: TProcStr := procedure(const S: string)
    begin
      WriteLn('[LOG] ' + S);
    end;
  Log('Hello');
end;

메시지 처리 & Windows 메시지

VCL은 Windows 메시지로 구축. 'message' 지시문은 특정 메시지 처리(WM_LBUTTONDOWN, WM_KEYDOWN 등). 메시지 레코드(TWMMouse, TWMKeyDown)는 TMessage의 타입화된 오버레이. 기본 처리가 발생하도록 항상 inherited 호출(메시지를 억제하지 않는 한). WndProc는 디스패치 전에 모든 메시지를 가로챔 — 횡단 관심사에 신중하게 사용. PostMessage는 비동기(즉시 반환); SendMessage는 동기(처리기 대기). WM_USER + N은 사용자 정의 메시지 정의. 이것은 Windows 이벤트 기반 모델의 기초입니다.

delphi
type
  TMyForm = class(TForm)
  private
    procedure WMMouseDown(var Msg: TWMMouse); message WM_LBUTTONDOWN;
    procedure WMKeyDown(var Msg: TWMKeyDown); message WM_KEYDOWN;
    procedure WMNCHitTest(var Msg: TWMNCHitTest); message WM_NCHITTEST;
  protected
    procedure WndProc(var Message: TMessage); override;
  end;

// handle specific Windows message
procedure TMyForm.WMMouseDown(var Msg: TWMMouse);
begin
  inherited;                       // call default handler
  ShowMessage(Format('Click at %d, %d', [Msg.XPos, Msg.YPos]));
end;

// intercept all messages
procedure TMyForm.WndProc(var Message: TMessage);
begin
  if Message.Msg = WM_CLOSE then
  begin
    if MessageDlg('Close?', mtConfirmation, [mbYes, mbNo], 0) = mrNo then
      Exit;                       // swallow the message
  end;
  inherited WndProc(Message);     // pass to default
end;

// post/send custom messages
const
  WM_MYMESSAGE = WM_USER + 100;

PostMessage(Handle, WM_MYMESSAGE, 0, 0);   // async, returns immediately
SendMessage(Handle, WM_MYMESSAGE, 0, 0);   // sync, waits for handler

애플리케이션 이벤트 & 유휴 처리

TApplicationEvents는 앱 수준 이벤트를 중앙 집중화: OnIdle(메시지 큐가 비어 있을 때 발생), OnException(전역 예외 처리기), OnMinimize/OnRestore, OnHint(상태 표시줄 힌트), OnMessage(모든 Windows 메시지). Done := False인 OnIdle은 타이트 루프 생성; 신중하게 사용. TTimer는 간격(밀리초)으로 OnTimer 발생 — 메시지 기반이므로 차단 작업 중에 발생하지 않음. Application.ProcessMessages는 긴 작업 중 메시지 큐를 펌프('응답 없음' 방지)하지만 재진입 버그를 일으킬 수 있음. TThread.Queue/Synchronize는 백그라운드 스레드에서 메인 스레드로 UI 업데이트를 마샬링.

delphi
type
  TForm1 = class(TForm)
    ApplicationEvents1: TApplicationEvents;
    procedure AppIdle(Sender: TObject; var Done: Boolean);
    procedure AppException(Sender: TObject; E: Exception);
    procedure AppMinimize(Sender: TObject);
  end;

// OnIdle - runs when app has no pending messages
procedure TForm1.AppIdle(Sender: TObject; var Done: Boolean);
begin
  Label1.Caption := 'Idle...';
  Done := True;                    // False = keep calling Idle
end;

// global exception handler
procedure TForm1.AppException(Sender: TObject; E: Exception);
begin
  LogError(E.Message);
  ShowMessage('Error: ' + E.Message);
end;

// TTimer - periodic events
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  StatusBar1.Panels[0].Text := TimeToStr(Now);
end;

// TThread.Queue / TThread.Synchronize - marshal to main thread
TThread.Queue(nil,
  procedure
  begin
    Label1.Caption := 'Updated from background';
  end);

// ProcessMessages - pump message queue
while LongOperationRunning do
begin
  DoChunk;
  Application.ProcessMessages;     // keep UI responsive
end;
11

FireDAC으로 데이터베이스 접근

연결 & 쿼리 기본

FireDAC은 Delphi의 현대적인 범용 데이터 접근 프레임워크로 SQLite, PostgreSQL, MySQL, SQL Server, Oracle, InterBase 등을 지원. TFDConnection은 데이터베이스 연결 관리(DriverName과 Params 설정). TFDQuery는 매개변수(:param 구문)로 SQL 실행 — SQL 인젝션 방지를 위해 항상 매개변수 사용. ExecSQL은 INSERT/UPDATE/DELETE/DDL 실행(결과 집합 없음); Open은 SELECT 실행(커서 반환). FieldByName('col').AsString/AsInteger는 값 읽기. Next/Prev/First/Last로 탐색; Eof는 끝 표시. FireDAC은 이전 dbExpress와 BDE 기술을 대체.

delphi
uses
  FireDAC.Comp.Client, FireDAC.Comp.DataSet, FireDAC.Stan.Param;

var
  FDConn: TFDConnection;
  Query: TFDQuery;
begin
  FDConn := TFDConnection.Create(nil);
  Query := TFDQuery.Create(nil);
  try
    // connection string (SQLite example)
    FDConn.DriverName := 'SQLite';
    FDConn.Params.Database := 'app.db';
    FDConn.Connected := True;

    Query.Connection := FDConn;

    // execute non-query (DDL/DML)
    Query.ExecSQL('CREATE TABLE IF NOT EXISTS users ' +
      '(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)');

    // parameterized insert (prevents SQL injection)
    Query.SQL.Text := 'INSERT INTO users (name, age) VALUES (:name, :age)';
    Query.ParamByName('name').AsString := 'Alice';
    Query.ParamByName('age').AsInteger := 30;
    Query.ExecSQL;

    // select with parameters
    Query.SQL.Text := 'SELECT * FROM users WHERE age > :minAge';
    Query.ParamByName('minAge').AsInteger := 18;
    Query.Open;

    while not Query.Eof do
    begin
      ShowMessage(Query.FieldByName('name').AsString);
      Query.Next;
    end;
  finally
    Query.Free;
    FDConn.Free;
  end;
end;

TFDTable & 라이브 바인딩

TFDTable은 전체 테이블 열기(SELECT * FROM tablename) — 단순한 CRUD에 편리하지만 큰 테이블에는 TFDQuery보다 덜 효율적. 데이터셋 탐색: First/Next/Prior/Last/MoveBy. Locate는 필드 값으로 검색(찾으면 True 반환). 편집: Append/Insert(새 행) 또는 Edit(기존), 필드 설정, Post(커밋) 또는 Cancel(되돌리기). Filter는 보이는 행 제한(클라이언트 측). IndexFieldNames는 레코드 정렬. TFDTable/TFDQuery를 TDataSource에 연결, 그다음 TDBGrid/TDBEdit로 자동 데이터 인식 UI. Live Bindings(FMX)는 컨트롤을 데이터 필드에 시각적 바인딩 제공.

delphi
var
  Table: TFDTable;
begin
  Table := TFDTable.Create(nil);
  try
    Table.Connection := FDConn;
    Table.TableName := 'users';
    Table.Open;                    // SELECT * FROM users

    // navigate
    Table.First;
    while not Table.Eof do
    begin
      ShowMessage(Table.FieldByName('name').AsString);
      Table.Next;
    end;

    // locate a record
    if Table.Locate('name', 'Alice', []) then
      ShowMessage('Found Alice');

    // edit/insert/post
    Table.Append;                  // or Insert / Edit
    Table.FieldByName('name').AsString := 'Bob';
    Table.FieldByName('age').AsInteger := 25;
    Table.Post;                    // commit to dataset

    // filter
    Table.Filter := 'age > 20';
    Table.Filtered := True;

    // index for sorting
    Table.IndexFieldNames := 'name';
  finally
    Table.Free;
  end;
end;

// connect to DBGrid via DataSource
DataSource1.DataSet := Table;
DBGrid1.DataSource := DataSource1;

트랜잭션 & 배치 연산

트랜잭션은 원자성을 보장 — 모든 작업이 성공하거나 아무것도 안 함. StartTransaction/Commit/Rollback으로 관련 작업을 감쌈. 명시적 트랜잭션 없이 FireDAC은 각 명령문을 자동 커밋(대량 삽입에 느림). Array DML(Execute(count, startAt))은 매개변수화된 배치를 한 번에 전송 — 대량 삽입에 극적으로 빠름(10-100배 가속). 실패 시 Rollback하기 위해 항상 트랜잭션을 try/except로 감싸세요. 긴 트랜잭션의 경우 격리 수준(xiReadCommitted, xiRepeatableRead)을 고려. 연결 풀링(TFDManager)은 멀티스레드 성능을 향상.

delphi
var
  FDConn: TFDConnection;
  Query: TFDQuery;
  I: Integer;
begin
  FDConn := TFDConnection.Create(nil);
  Query := TFDQuery.Create(nil);
  try
    FDConn.DriverName := 'SQLite';
    FDConn.Params.Database := 'app.db';
    FDConn.Connected := True;
    Query.Connection := FDConn;

    // explicit transaction
    FDConn.StartTransaction;
    try
      Query.SQL.Text := 'INSERT INTO users (name, age) VALUES (:n, :a)';
      for I := 1 to 1000 do
      begin
        Query.ParamByName('n').AsString := 'User' + IntToStr(I);
        Query.ParamByName('a').AsInteger := 20 + (I mod 50);
        Query.ExecSQL;
      end;
      FDConn.Commit;               // commit all
    except
      FDConn.Rollback;             // undo all on error
      raise;
    end;

    // batch execute (Array DML - very fast)
    Query.SQL.Text := 'INSERT INTO logs (msg) VALUES (:m)';
    Query.Params.ArraySize := 100;
    for I := 0 to 99 do
      Query.Params[0].AsStrings[I] := 'Log entry ' + IntToStr(I);
    Query.Execute(100, 0);         // execute 100 times at once
  finally
    Query.Free;
    FDConn.Free;
  end;
end;

저장 프로시저 & 메타데이터

TFDStoredProc는 데이터베이스 저장 프로시저 호출. StoredProcName과 매개변수 설정(ParamType: ptInput, ptOutput, ptInputOutput, ptResult). ExecProc는 커서를 반환하지 않는 프로시저 실행; Open은 결과 집합을 반환하는 것 실행. 저장 프로시저는 성능과 보안을 위해 서버 측에 비즈니스 로직을 캡슐화. TFDMetaInfoQuery는 데이터베이스 스키마(테이블, 열, 인덱스, 제약 조건) 쿼리 — 동적 도구, ORM, 스키마 브라우저 구축에 유용. MetaInfoKind 옵션: mkTables, mkColumns, mkIndexes, mkPrimaryKey, mkForeignKeys. FireDAC은 오프라인 메타데이터 접근을 위한 스키마 캐싱도 지원.

delphi
// call stored procedure
var
  SP: TFDStoredProc;
begin
  SP := TFDStoredProc.Create(nil);
  try
    SP.Connection := FDConn;
    SP.StoredProcName := 'get_user_by_id';
    SP.Params.ParamByName('@user_id').AsInteger := 42;

    // output parameter
    SP.Params.ParamByName('@name').ParamType := ptOutput;

    SP.ExecProc;                   // execute (no cursor)
    ShowMessage(SP.ParamByName('@name').AsString);

    // or Open if it returns a result set
    SP.Open;
    ShowMessage(SP.FieldByName('name').AsString);
  finally
    SP.Free;
  end;
end;

// metadata - list tables
var
  Meta: TFDMetaInfoQuery;
begin
  Meta := TFDMetaInfoQuery.Create(nil);
  try
    Meta.Connection := FDConn;
    Meta.MetaInfoKind := mkTables;     // or mkColumns, mkIndexes
    Meta.Open;
    while not Meta.Eof do
    begin
      ShowMessage(Meta.FieldByName('TABLE_NAME').AsString);
      Meta.Next;
    end;
  finally
    Meta.Free;
  end;
end;

FireDAC 메모리 테이블 & 로컬 SQL

TFDMemTable은 메모리 내 데이터셋 — 캐싱, 임시 데이터, 데이터베이스 없는 단위 테스트에 완벽. FieldDefs로 필드 정의, 그다음 CreateDataSet. AppendRecord로 행 추가. 인덱스, 필터, 모든 데이터셋 탐색 지원. Local SQL(TFDLocalSQL)은 모든 TDataSet(TFDMemTable, TClientDataSet, ODBC를 통한 Excel 포함)에 대해 SQL 쿼리 실행 — 메모리 테이블과 데이터베이스 테이블 간 조인 가능. ETL, 보고, 오프라인으로 작동하는 데이터 레이어 구축에 강력. TFDMemTable은 영속성을 위해 바이너리나 JSON 파일로 로드/저장도 가능.

delphi
uses FireDAC.Comp.Client, FireDAC.Stan.Intf;

var
  MemTable: TFDMemTable;
begin
  MemTable := TFDMemTable.Create(nil);
  try
    // define schema in code
    MemTable.FieldDefs.Add('id', ftInteger);
    MemTable.FieldDefs.Add('name', ftString, 50);
    MemTable.FieldDefs.Add('salary', ftCurrency);
    MemTable.CreateDataSet;        // create in-memory table

    // populate
    MemTable.AppendRecord([1, 'Alice', 75000]);
    MemTable.AppendRecord([2, 'Bob', 68000]);
    MemTable.AppendRecord([3, 'Carol', 82000]);

    // index & filter
    MemTable.IndexFieldNames := 'salary';
    MemTable.Filter := 'salary > 70000';
    MemTable.Filtered := True;

    // Local SQL - query any TDataSet with SQL
    var LocalSQL := TFDLocalSQL.Create(nil);
    try
      LocalSQL.Connection := FDConn;  // or a dedicated connection
      LocalSQL.DataSets.AddDataSet(MemTable, 'employees');
      LocalSQL.Active := True;

      var Q := TFDQuery.Create(nil);
      try
        Q.Connection := FDConn;
        Q.Open('SELECT * FROM employees WHERE salary > :min ORDER BY name');
        // query in-memory data with full SQL!
      finally
        Q.Free;
      end;
    finally
      LocalSQL.Free;
    end;
  finally
    MemTable.Free;
  end;
end;
12

제네릭 & 익명 메서드

제네릭 클래스 & 메서드

제네릭(Delphi 2009에서 도입)은 타입 안전 컨테이너와 알고리즘을 가능하게. TStack<T>는 모든 타입 T와 작동 — 컴파일러가 특수화된 버전 생성. 이것은 런타임 캐스트 제거(TObject 캐스팅 없음)하고 컴파일 타임에 타입 오류를 잡음. 제네릭 타입 매개변수는 <T> 구문 사용. 제네릭 메서드, 클래스, 레코드, 인터페이스 모두 지원. 제약 조건(class, constructor, interface)은 사용할 수 있는 타입을 제한. RTL은 System.Generics.Collections에 TList<T>, TDictionary<TKey,TValue>, TQueue<T>, TStack<T>, TObjectList<T>를 제공.

delphi
type
  TStack<T> = class
  private
    FItems: array of T;
    FCount: Integer;
  public
    procedure Push(const Value: T);
    function Pop: T;
    function Peek: T;
    function Count: Integer;
  end;

procedure TStack<T>.Push(const Value: T);
begin
  if FCount = Length(FItems) then
    SetLength(FItems, FCount * 2 + 4);
  FItems[FCount] := Value;
  Inc(FCount);
end;

function TStack<T>.Pop: T;
begin
  if FCount = 0 then
    raise Exception.Create('Stack empty');
  Dec(FCount);
  Result := FItems[FCount];
end;

// usage - type-safe, no casts
var
  IntStack: TStack<Integer>;
  StrStack: TStack<string>;
begin
  IntStack := TStack<Integer>.Create;
  IntStack.Push(42);
  IntStack.Push(99);
  ShowMessage(IntToStr(IntStack.Pop));  // 99

  StrStack := TStack<string>.Create;
  StrStack.Push('Hello');
  ShowMessage(StrStack.Pop);             // Hello
end;

제네릭 제약 조건

제네릭 제약 조건은 타입 매개변수를 제한: 'class'(클래스 타입이어야 함), 'constructor'(매개변수 없는 Create 생성자가 있어야 함 — T.Create 활성화), 'record'(값 타입이어야 함), 인터페이스 이름(인터페이스를 구현해야 함). 여러 제약 조건은 쉼표로 구분. 제약 조건은 T에서 메서드 호출을 가능하게(예: constructor 제약 조건으로 T.Create). 제약 조건 없이는 T를 할당/비교만 가능(메서드 호출 없음). 타입 추론은 때때로 명시적 타입 매개변수를 생략할 수 있게. 제약 조건은 타입 안전 프레임워크와 ORM 구축에 필수.

delphi
type
  // T must be a class
  TRepository<T: class> = class
    function Find(Id: Integer): T;
  end;

  // T must be a class with a parameterless constructor
  TFactory<T: class, constructor> = class
    function CreateInstance: T;
  end;

  // T must implement IComparable
  TSorter<T: IComparable> = class
    procedure Sort(var Arr: array of T);
  end;

  // multiple constraints
  TManager<T: class, constructor, IComparable> = class
  end;

function TFactory<T>.CreateInstance: T;
begin
  Result := T.Create;    // allowed because of 'constructor' constraint
end;

// type inference
type
  TPair<TKey, TValue> = class
    Key: TKey;
    Value: TValue;
    constructor Create(const K: TKey; const V: TValue);
  end;

var
  P: TPair<string, Integer>;
begin
  P := TPair<string, Integer>.Create('age', 30);
end;

TList<T> & TDictionary<TKey,TValue>

System.Generics.Collections는 타입 안전 컨테이너 제공: TList<T>(동적 배열), TDictionary<TKey,TValue>(해시 맵), TQueue<T>(FIFO), TStack<T>(LIFO), TObjectList<T>(객체 소유 — 자동으로 해제). Sort는 기본 비교 사용; TComparer<T>.Construct는 익명 메서드로 사용자 정의 비교자 생성. FindIndex/조건자 기반 검색은 익명 함수 조건자 사용. TryGetValue는 True 반환하고 찾으면 값을 출력(예외 방지). AddOrSetValue는 업데이트 또는 삽입. OwnsObjects := True인 TObjectList<T>는 목록이 해제될 때 포함된 객체를 자동으로 해제 — 메모리 누수 방지.

delphi
uses System.Generics.Collections, System.Generics.Defaults;

var
  List: TList<Integer>;
  Dict: TDictionary<string, Integer>;
  ObjList: TObjectList<TPerson>;
begin
  // TList<T>
  List := TList<Integer>.Create;
  try
    List.AddRange([3, 1, 4, 1, 5, 9, 2, 6]);
    List.Sort;                     // 1, 1, 2, 3, 4, 5, 6, 9
    List.BinarySearch(5, var Idx); // fast lookup in sorted list

    // custom comparer
    List.Sort(TComparer<Integer>.Construct(
      function(const L, R: Integer): Integer
      begin
        Result := R - L;           // descending
      end));

    // find with predicate
    var Found := List.FindIndex(
      function(const X: Integer): Boolean
      begin
        Result := X > 4;
      end);
  finally
    List.Free;
  end;

  // TDictionary
  Dict := TDictionary<string, Integer>.Create;
  try
    Dict.Add('apple', 5);
    Dict.Add('banana', 3);
    Dict.AddOrSetValue('apple', 10);  // update or insert

    if Dict.TryGetValue('banana', var Count) then
      ShowMessage(Count.ToString);

    // iterate
    for var Pair in Dict do
      ShowMessage(Pair.Key + ': ' + Pair.Value.ToString);
  finally
    Dict.Free;
  end;
end;

콜백으로서의 익명 메서드

익명 메서드는 Delphi에서 함수형 프로그래밍을 가능하게. 'reference to function' 타입은 클로저 — 둘러싸는 범위의 변수를 캡처. Map과 Filter 같은 고차 함수는 매개변수로 함수를 취해 간결한 데이터 변환 가능. TFunc<T,TResult>와 TProc<T>는 내장 제네릭 대리자 타입. 클로저는 참조로 변수를 캡처하므로 나중의 변경을 반영. 이 패턴은 장황한 콜백 인터페이스를 대체하며 PPL(병렬 프로그래밍 라이브러리), 이벤트 처리기, LINQ 스타일 연산에 필수. 익명 메서드는 참조 카운트되고 자동으로 관리.

delphi
uses System.SysUtils;

type
  TFunc<T, TResult> = reference to function(Arg: T): TResult;
  TProc<T> = reference to procedure(Arg: T);

// higher-order functions
function Map<T, TResult>(const Source: array of T;
  const Mapper: TFunc<T, TResult>): TArray<TResult>;
var
  I: Integer;
begin
  SetLength(Result, Length(Source));
  for I := 0 to High(Source) do
    Result[I] := Mapper(Source[I]);
end;

function Filter<T>(const Source: array of T;
  const Predicate: TFunc<T, Boolean>): TArray<T>;
var
  I, Count: Integer;
begin
  Count := 0;
  SetLength(Result, Length(Source));
  for I := 0 to High(Source) do
    if Predicate(Source[I]) then
    begin
      Result[Count] := Source[I];
      Inc(Count);
    end;
  SetLength(Result, Count);
end;

// usage with closures
var
  Numbers: array of Integer;
  Doubled, Evens: TArray<Integer>;
  Threshold: Integer;
begin
  Numbers := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
  Threshold := 5;

  Doubled := Map<Integer, Integer>(Numbers,
    function(X: Integer): Integer
    begin
      Result := X * 2;
    end);

  // closure captures Threshold
  Evens := Filter<Integer>(Numbers,
    function(X: Integer): Boolean
    begin
      Result := (X > Threshold) and (X mod 2 = 0);
    end);
end;

제네릭 인터페이스 & TComparer

제네릭 인터페이스는 타입 안전 계약을 가능하게: IRepository<T>는 모든 엔티티 타입과 작동. 참조 카운팅(TInterfacedObject)과 결합하여 자동 메모리 관리 제공 — 인터페이스는 참조 카운트되고 마지막 참조가 떨어지면 해제. TComparer<T>.Construct는 익명 비교 함수에서 IComparer<T> 생성 — Sort, BinarySearch, SortedDictionary에서 사용. 제네릭 인터페이스는 Delphi에서 의존성 주입의 기초(IRepository<TUser> 등록, 서비스에 주입). Spring4D 프레임워크는 완전한 DI 컨테이너로 이것을 확장. 제네릭 제약 조건(class, constructor)은 T를 인스턴스화할 수 있게 보장.

delphi
type
  IComparable<T> = interface
    function CompareTo(const Other: T): Integer;
  end;

  IRepository<T> = interface
    function GetById(Id: Integer): T;
    function GetAll: TArray<T>;
    procedure Save(const Entity: T);
    procedure Delete(Id: Integer);
  end;

  TMemoryRepository<T: class, constructor> = class(TInterfacedObject, IRepository<T>)
  private
    FItems: TList<T>;
  public
    constructor Create;
    destructor Destroy; override;
    function GetById(Id: Integer): T;
    function GetAll: TArray<T>;
    procedure Save(const Entity: T);
    procedure Delete(Id: Integer);
  end;

// custom comparer for sorting objects
type
  TPerson = class
    Name: string;
    Age: Integer;
  end;

var
  People: TObjectList<TPerson>;
begin
  People := TObjectList<TPerson>.Create;
  People.Sort(TComparer<TPerson>.Construct(
    function(const L, R: TPerson): Integer
    begin
      Result := CompareText(L.Name, R.Name);  // sort by name
    end));
end;
13

RTTI & 리플렉션

확장 RTTI 기본

확장 RTTI(런타임 타입 정보), Delphi 2010에서 도입, 완전한 리플렉션 제공: 런타임에 타입, 속성, 메서드, 필드 검사. TRTTIContext가 진입점. GetType은 클래스의 TRttiType 반환. GetProperties는 published 속성 열거. GetValue/SetValue는 TValue(변형 같은 타입)를 사용하여 동적으로 속성 값을 읽기/쓰기. 기본적으로 'published' 멤버만 RTTI를 가짐(더 많이는 {$RTTI EXPLICIT ...} 지시문 사용). RTTI는 직렬화(JSON/XML), ORM, 의존성 주입, 시각적 디자이너를 구동. 작은 성능 오버헤드가 있지만 강력한 메타프로그래밍을 가능하게.

delphi
uses System.RTTI, System.TypInfo;

type
  TPerson = class
  private
    FName: string;
    FAge: Integer;
  published
    property Name: string read FName write FName;
    property Age: Integer read FAge write FAge;
  end;

var
  Ctx: TRTTIContext;
  RType: TRttiType;
  Prop: TRttiProperty;
  Person: TPerson;
begin
  Person := TPerson.Create;
  try
    Person.Name := 'Alice';
    Person.Age := 30;

    Ctx := TRTTIContext.Create;
    try
      RType := Ctx.GetType(TPerson);

      // enumerate properties
      for Prop in RType.GetProperties do
      begin
        WriteLn(Prop.Name, ': ', Prop.PropertyType.Name);

        // read value
        if Prop.IsReadable then
          WriteLn('  Value: ', Prop.GetValue(Person).ToString);

        // write value
        if Prop.IsWritable then
          Prop.SetValue(Person, TValue.From<string>('Bob'));
      end;

      // get specific property
      Prop := RType.GetProperty('Name');
      ShowMessage(Prop.GetValue(Person).AsString);
    finally
      Ctx.Free;
    end;
  finally
    Person.Free;
  end;
end;

메서드 호출 & 속성

RTTI는 TRttiMethod.Invoke를 통해 동적으로 메서드를 호출할 수 있습니다 — TValue 배열로 인수를 전달. 속성(TCustomAttribute 하위 클래스)은 [Attribute] 구문을 사용하여 타입, 속성, 메서드에 메타데이터를 첨부. GetAttributes로 런타임에 검색. 이것은 검증 프레임워크([Required], [MaxLength]), ORM 매핑([Table], [Column]), 직렬화 제어([JsonProperty])를 가능하게. 속성은 강력한 메타프로그래밍 기능 — 컴파일러가 RTTI에 저장하고 프레임워크가 동작을 구동하기 위해 읽음. RTTI를 통한 메서드 호출은 직접 호출보다 느리지만 스크립팅, DI, 동적 디스패치에 필수.

delphi
uses System.RTTI;

type
  TValidatorAttribute = class(TCustomAttribute)
  private
    FMaxLen: Integer;
  public
    constructor Create(MaxLen: Integer);
    property MaxLen: Integer read FMaxLen;
  end;

  TUser = class
  private
    FName: string;
  public
    [Validator(50)]
    property Name: string read FName write FName;

    function Greet(const Greeting: string): string;
  end;

constructor TValidatorAttribute.Create(MaxLen: Integer);
begin
  FMaxLen := MaxLen;
end;

var
  Ctx: TRTTIContext;
  RType: TRttiType;
  Prop: TRttiProperty;
  Attr: TCustomAttribute;
  Method: TRttiMethod;
  User: TUser;
  Result: TValue;
begin
  User := TUser.Create;
  User.Name := 'Alice';
  Ctx := TRTTIContext.Create;
  try
    RType := Ctx.GetType(TUser);

    // read attributes
    Prop := RType.GetProperty('Name');
    for Attr in Prop.GetAttributes do
    begin
      if Attr is TValidatorAttribute then
        WriteLn('Max length: ', TValidatorAttribute(Attr).MaxLen);
    end;

    // invoke method by name
    Method := RType.GetMethod('Greet');
    Result := Method.Invoke(User, ['Hello']);
    ShowMessage(Result.AsString);  // Hello, Alice
  finally
    Ctx.Free;
    User.Free;
  end;
end;

타입 발견 & 열거

TRTTIContext.GetTypes는 컴파일된 프로그램에서 RTTI가 있는 모든 타입을 열거 — 플러그인 발견, ORM 모델 스캐닝, 타입 브라우저 구축에 유용. FindType은 정규화된 이름('UnitName.TypeName')으로 타입을 찾음. TRttiType은 GetFields(모든 필드), GetMethods(모든 메서드), GetProperties(published 속성) 제공. TypeKind는 클래스, 레코드, 인터페이스, 열거형 등을 구분. AsInstance.MetaclassType은 인스턴스화를 위한 클래스 참조 제공. 이것은 컴포넌트를 자동 발견하고 연결하는 프레임워크를 가능하게. Spring4D와 DORM 프레임워크는 자동 ORM 매핑에 이것을 사용. RTTI 열거는 느림 — 반복 사용을 위해 결과 캐시.

delphi
uses System.RTTI;

var
  Ctx: TRTTIContext;
  Types: TArray<TRttiType>;
  T: TRttiType;
  Field: TRttiField;
  Method: TRttiMethod;
begin
  Ctx := TRTTIContext.Create;
  try
    // enumerate ALL types in the program
    Types := Ctx.GetTypes;

    // find types by name
    T := Ctx.FindType('Unit1.TPerson');
    if T <> nil then
      ShowMessage('Found: ' + T.QualifiedName);

    // filter: all classes in a unit
    for T in Types do
    begin
      if (T.TypeKind = tkClass) and T.QualifiedName.StartsWith('MyApp.') then
      begin
        WriteLn('Class: ', T.Name);

        // enumerate fields
        for Field in T.GetFields do
          WriteLn('  Field: ', Field.Name, ': ', Field.FieldType.Name);

        // enumerate methods
        for Method in T.GetMethods do
          WriteLn('  Method: ', Method.Name,
            ' - ', Method.MethodType.ToString);
      end;
    end;

    // create instance via RTTI
    var Instance := T.AsInstance.MetaclassType.Create;
    try
      // use instance...
    finally
      Instance.Free;
    end;
  finally
    Ctx.Free;
  end;
end;

TValue & 동적 타이핑

TValue는 Delphi의 동적 값 타입 — 타입 정보와 함께 모든 타입을 보유하는 태그된 공용체. From<T>로 값을 감싸고; AsType<T>/AsInteger/AsString으로 풀기. IsType<T>는 타입을 검사. TryAsType은 안전한 변환을 시도. TValue는 RTTI(속성 값, 메서드 인수)에 필수적이고 정적 타입 언어에서 동적 타이핑을 가능하게. C#의 타입 정보가 있는 'object'나 Python의 동적 특성과 유사. TValue는 기본 타입, 문자열, 객체, 배열, 레코드를 처리. 직렬 변환기, 스크립트 엔진, 제네릭 데이터 레이어 구축 시 사용. 직접 타이핑보다 오버헤드가 있지만 최대 유연성 제공.

delphi
uses System.RTTI;

var
  V: TValue;
  I: Integer;
  S: string;
  D: Double;
  Obj: TObject;
begin
  // wrap values
  V := TValue.From<Integer>(42);
  ShowMessage(V.ToString);          // '42'
  I := V.AsInteger;                 // unwrap

  V := TValue.From<string>('Hello');
  S := V.AsString;

  // type checking
  if V.IsType<string> then
    ShowMessage('It is a string');

  // conversion
  V := TValue.From<Integer>(100);
  D := V.AsExtended;                // 100.0

  // boxing objects
  var Person := TPerson.Create;
  try
    V := TValue.From<TPerson>(Person);
    if V.IsObject then
      ShowMessage(V.AsObject.ClassName);  // 'TPerson'
  finally
    Person.Free;
  end;

  // array of TValue for method invocation
  var Args: array of TValue;
  SetLength(Args, 2);
  Args[0] := TValue.From<Integer>(10);
  Args[1] := TValue.From<Integer>(20);

  // try conversion
  V := TValue.From<string>('123');
  if V.TryAsType<Integer>(I) then
    ShowMessage(IntToStr(I));       // 123
end;

RTTI로 직렬화

RTTI는 자동 직렬화를 가능하게 — 수동 매핑 코드 없이 객체를 JSON, XML 또는 모든 형식으로 변환. ObjectToJSON은 published 속성을 반복하고 RTTI로 값을 읽고 TJSONObject를 빌드. JSONToObject는 과정을 역으로. 이 패턴은 REST 클라이언트, 설정 시스템, ORM 레이어를 구동. REST.Json 유닛은 이것을 위해 TJson.ObjectToJsonString과 TJson.JsonToObject를 기본 제공. 프로덕션 사용의 경우, 필드 이름을 제어하기 위해 속성([JsonProperty('name')])을 추가하고, 중첩 객체, 배열, 사용자 정의 타입을 처리. RTTI 기반 직렬화는 수작업 매핑보다 느리지만 훨씬 더 유지보수 가능.

delphi
uses System.RTTI, System.JSON;

function ObjectToJSON(Obj: TObject): TJSONObject;
var
  Ctx: TRTTIContext;
  RType: TRttiType;
  Prop: TRttiProperty;
  Val: TValue;
begin
  Result := TJSONObject.Create;
  Ctx := TRTTIContext.Create;
  try
    RType := Ctx.GetType(Obj.ClassType);
    for Prop in RType.GetProperties do
    begin
      if not Prop.IsReadable then Continue;
      Val := Prop.GetValue(Obj);
      case Val.Kind of
        tkString, tkUString:
          Result.AddPair(Prop.Name, Val.AsString);
        tkInteger:
          Result.AddPair(Prop.Name, TJSONNumber.Create(Val.AsInteger));
        tkFloat:
          Result.AddPair(Prop.Name, TJSONNumber.Create(Val.AsExtended));
        tkEnumeration:
          Result.AddPair(Prop.Name, TJSONBool.Create(Val.AsBoolean));
      end;
    end;
  finally
    Ctx.Free;
  end;
end;

procedure JSONToObject(Obj: TObject; const JSON: TJSONObject);
var
  Ctx: TRTTIContext;
  RType: TRttiType;
  Prop: TRttiProperty;
  Pair: TJSONPair;
begin
  Ctx := TRTTIContext.Create;
  try
    RType := Ctx.GetType(Obj.ClassType);
    for Prop in RType.GetProperties do
    begin
      if not Prop.IsWritable then Continue;
      Pair := JSON.FindPair(Prop.Name);
      if Pair <> nil then
      begin
        case Prop.PropertyType.TypeKind of
          tkString, tkUString:
            Prop.SetValue(Obj, TValue.From<string>(Pair.JsonValue.Value));
          tkInteger:
            Prop.SetValue(Obj, TValue.From<Integer>((Pair.JsonValue as TJSONNumber).AsInt));
        end;
      end;
    end;
  finally
    Ctx.Free;
  end;
end;
14

인터페이스 & COM

인터페이스 기본 & 참조 카운팅

인터페이스는 구현 없이 계약(메서드 시그니처)을 정의. TInterfacedObject는 참조 카운팅을 제공 — 마지막 인터페이스 참조가 떨어지면 객체가 자동으로 해제됨(Free 호출 불필요). 이것은 Delphi의 인터페이스 객체에 대한 자동 메모리 관리. GUID(['{...}'])는 COM interop과 InterfaceAs/Supports 검사를 가능하게. 클래스는 여러 인터페이스를 구현할 수 있음(TShape는 IMovable과 IDrawable 모두 구현). 인터페이스 속성은 허용(읽기/쓰기 메서드가 있어야 함). 참조 카운팅이 작동하려면 클래스 타입(TShape)이 아닌 인터페이스 타입(IMovable)을 항상 사용. 객체와 인터페이스 참조를 혼합하면 조기 해제 발생 가능.

delphi
type
  IMovable = interface
    ['{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}']  // GUID (optional)
    procedure MoveTo(X, Y: Integer);
    function GetPosition: TPoint;
    property Position: TPoint read GetPosition;
  end;

  IDrawable = interface
    procedure Draw(Canvas: TCanvas);
  end;

  TShape = class(TInterfacedObject, IMovable, IDrawable)
  private
    FX, FY: Integer;
  public
    procedure MoveTo(X, Y: Integer);
    function GetPosition: TPoint;
    procedure Draw(Canvas: TCanvas);
  end;

procedure TShape.MoveTo(X, Y: Integer);
begin
  FX := X;
  FY := Y;
end;

// usage - reference counted automatically
var
  Shape: IMovable;
begin
  Shape := TShape.Create;          // ref count = 1
  Shape.MoveTo(100, 200);
  ShowMessage(Format('%d, %d', [Shape.Position.X, Shape.Position.Y]));
  // when Shape goes out of scope, ref count drops to 0, object freed
end;

인터페이스로 의존성 주입

인터페이스는 의존성 주입을 가능하게 — 종속성(ILogger, IUserDataAccess)을 하드코딩 대신 생성자를 통해 전달. 이것은 TUserService를 구체적 구현에서 분리: TUserService를 변경하지 않고 TConsoleLogger를 TFileLogger로 교체. TUserService 자체는 참조 카운트되지 않음(TObject에서 상속, TInterfacedObject가 아님)으로 수동 Free 필요. 완전한 DI의 경우 인터페이스 타입으로 종속성을 해결하는 컨테이너(Spring4D, DSharp) 사용: Container.RegisterType<ILogger, TConsoleLogger>; Container.Build; Service := Container.Resolve<TUserService>. DI는 테스트 가능성(모킹 주입), 유지보수성, 모듈성을 향상. 구체가 아닌 추상(인터페이스)에 항상 의존.

delphi
type
  ILogger = interface
    procedure Log(const Msg: string);
  end;

  IUserDataAccess = interface
    function GetUser(Id: Integer): string;
  end;

  TConsoleLogger = class(TInterfacedObject, ILogger)
    procedure Log(const Msg: string);
  end;

  TDatabaseAccess = class(TInterfacedObject, IUserDataAccess)
    function GetUser(Id: Integer): string;
  end;

  TUserService = class
  private
    FLogger: ILogger;
    FDataAccess: IUserDataAccess;
  public
    constructor Create(ALogger: ILogger; ADataAccess: IUserDataAccess);
    function GetUserName(Id: Integer): string;
  end;

constructor TUserService.Create(ALogger: ILogger; ADataAccess: IUserDataAccess);
begin
  FLogger := ALogger;
  FDataAccess := ADataAccess;
end;

function TUserService.GetUserName(Id: Integer): string;
begin
  FLogger.Log('Fetching user ' + IntToStr(Id));
  Result := FDataAccess.GetUser(Id);
end;

// wire up dependencies (manual DI)
var
  Logger: ILogger;
  DataAccess: IUserDataAccess;
  Service: TUserService;
begin
  Logger := TConsoleLogger.Create;
  DataAccess := TDatabaseAccess.Create;
  Service := TUserService.Create(Logger, DataAccess);
  try
    ShowMessage(Service.GetUserName(42));
  finally
    Service.Free;    // TUserService is not ref-counted (not TInterfacedObject)
  end;
end;

COM Interop

COM(Component Object Model)은 Delphi가 Windows 애플리케이션과 라이브러리와 상호 작용하게 함. CreateOleObject는 레이트 바인딩으로 COM 객체 생성(Variant 타입 — 컴파일 타임 검사 없음, 단순함). Import Type Library는 타입화된 인터페이스로 얼리 바인딩 유닛 생성(IntelliSense, 타입 검사, 더 나은 성능). IUnknown은 참조 카운팅을 위한 AddRef/Release/QueryInterface를 가진 기본 COM 인터페이스. stdcall은 COM 호출 규약. CoCreateInstance는 저수준 API. 일반적인 COM 용도: Office 자동화(Excel, Word), ADO(데이터베이스), 셸 통합, WMI 쿼리. 스레드에서 COM 작업 전에 항상 CoInitialize 호출. COM 객체는 아파트 스레드 — 스레드 간 주의해서 마샬링.

delphi
uses
  Winapi.ActiveX, System.Win.ComObj;

// create COM object (e.g., Excel)
var
  Excel: Variant;
  Workbook: Variant;
  Sheet: Variant;
begin
  Excel := CreateOleObject('Excel.Application');
  try
    Excel.Visible := True;
    Workbook := Excel.Workbooks.Add;
    Sheet := Workbook.Worksheets[1];

    // write data
    Sheet.Cells[1, 1].Value := 'Name';
    Sheet.Cells[1, 2].Value := 'Score';
    Sheet.Cells[2, 1].Value := 'Alice';
    Sheet.Cells[2, 2].Value := 95;

    // formula
    Sheet.Cells[3, 2].Value := '=AVERAGE(B2:B2)';

    Workbook.SaveAs('C:\report.xlsx');
  finally
    Excel.Quit;
  end;
end;

// import type library for early binding
// Component → Import Component → Import Type Library
// generates a unit with typed interfaces (early binding, IntelliSense)

// IUnknown - base COM interface
type
  IMyComObject = interface(IUnknown)
    ['{...}']
    function DoSomething: HResult; stdcall;
  end;

// CoCreateInstance for low-level COM
var
  Obj: IUnknown;
  MyObj: IMyComObject;
begin
  CoCreateInstance(CLASS_MyComObject, nil, CLSCTX_INPROC_SERVER,
    IMyComObject, MyObj);
  MyObj.DoSomething;
end;

Implements & 집약

'implements' 지시문은 인터페이스를 속성에 위임 — 상속보다 조합. TDataService는 FCache(TMemoryCache)에 위임하여 ICache를 노출. 이것은 상속보다 깔끔하고 동작을 혼합 및 매칭. Supports()는 객체가 인터페이스를 구현하는지 확인(내부적으로 QueryInterface 사용). As 연산자는 검사된 인터페이스 캐스트 수행. 인터페이스 위임은 데코레이터 패턴(캐시를 로깅으로 래핑), 전략 패턴(캐시 구현 교체), 관심사의 깔끔한 분리를 가능하게. COM의 QueryInterface가 기본 메커니즘 — 모든 인터페이스 객체는 지원하는 모든 인터페이스에 대해 쿼리 가능.

delphi
type
  ICache = interface
    function Get(const Key: string): string;
    procedure Put(const Key, Value: string);
  end;

  TMemoryCache = class(TInterfacedObject, ICache)
  private
    FDict: TDictionary<string, string>;
  public
    constructor Create;
    destructor Destroy; override;
    function Get(const Key: string): string;
    procedure Put(const Key, Value: string);
  end;

  TDataService = class(TInterfacedObject, ICache)
  private
    FCache: ICache;
  public
    constructor Create(ACache: ICache);
    // 'implements' delegates ICache to FCache
    property Cache: ICache read FCache implements ICache;
  end;

// usage - TDataService exposes ICache via delegation
var
  Service: ICache;
begin
  Service := TDataService.Create(TMemoryCache.Create);
  Service.Put('key1', 'value1');    // delegates to TMemoryCache
  ShowMessage(Service.Get('key1'));
end;

// QueryInterface / Supports
var
  Obj: TInterfacedObject;
  Intf: ICache;
begin
  Obj := TMemoryCache.Create;
  if Supports(Obj, ICache, Intf) then
    Intf.Put('a', 'b');
end;

약한 & 안전하지 않은 참조

참조 카운팅은 순환 참조(부모↔자식)로 메모리 누수를 일으킬 수 있음. [Weak]은 순환을 끊음 — 참조를 추적하지만 참조 카운트를 증가시키지 않고, 대상이 해제되면 자동으로 nil로 설정. [Unsafe]은 원시 포인터(추적 없음, 참조 카운트 없음) — 가장 빠르지만 위험(매달린 포인터). 부모/백 참조, 관찰자 패턴, 이벤트 구독에 [Weak] 사용. 기본(강한) 참조는 참조 카운트를 증가하고 객체를 살아있게 유지. Delphi의 ARC([Weak]으로 대체되어 더 이상 사용되지 않음)은 모바일에서 이것을 자동으로 처리했음. 데스크탑에서 인터페이스는 수동 참조 카운팅 사용 — [Weak]은 순환 없는 설계에 필수. 강한 참조와 약한 참조를 항상 올바르게 짝지으세요.

delphi
type
  TParent = class;
  TChild = class;

  TParent = class(TInterfacedObject)
  private
    FChild: TChild;
    procedure ChildCallback(const Msg: string);
  public
    destructor Destroy; override;
    property Child: TChild read FChild;
  end;

  TChild = class(TInterfacedObject)
  private
    // [Weak] avoids circular reference counting (parent-child cycle)
    [Weak] FParent: TParent;
    FCallback: TProc<string>;
  public
    constructor Create(AParent: TParent);
    property Parent: TParent read FParent;
  end;

  // [Unsafe] - raw pointer, no ref counting at all
  // [Weak] - tracked but doesn't increment ref count
  // (default) - strong reference, increments ref count

destructor TParent.Destroy;
begin
  FChild := nil;  // releases strong ref
  inherited;
end;

// without [Weak], this creates a memory leak:
// Parent holds Child (ref=1), Child holds Parent (ref=1)
// neither ref count ever reaches 0 → leak

var
  Parent: TParent;
begin
  Parent := TParent.Create;
  // when Parent goes out of scope, both are freed correctly
end;
15

멀티스레딩 & PPL

TThread 기본

TThread는 Delphi 멀티스레딩의 기초. Execute를 백그라운드 작업으로 override. 우아한 취소를 위해 Terminated를 주기적으로 확인. TThread.Synchronize는 메인 스레드에서 코드 실행(차단 — 완료 대기); TThread.Queue는 비동기(게시 후 즉시 반환). 백그라운드 스레드에서 UI 컨트롤에 접근 금지 — 항상 Synchronize나 Queue 사용. FreeOnTerminate := True는 Execute가 끝나면 스레드 자동 해제. CreateAnonymousThread는 익명 메서드에서 일회성 스레드 생성 — 단순한 작업에 편리. 프로덕션 코드의 경우 더 나은 조합과 오류 처리를 위해 원시 TThread보다 PPL(TTask)을 선호.

delphi
type
  TWorkerThread = class(TThread)
  private
    FResult: Integer;
  protected
    procedure Execute; override;
  public
    property Result: Integer read FResult;
  end;

procedure TWorkerThread.Execute;
var
  I: Integer;
begin
  FResult := 0;
  for I := 1 to 100 do
  begin
    if Terminated then Break;       // check for cancellation
    FResult := FResult + I;
    Sleep(10);                      // simulate work
  end;

  // update UI from background thread
  TThread.Synchronize(nil,
    procedure
    begin
      Form1.Label1.Caption := 'Done: ' + IntToStr(FResult);
    end);
end;

// create and run
var
  Worker: TWorkerThread;
begin
  Worker := TWorkerThread.Create(True);  // suspended
  Worker.FreeOnTerminate := True;        // auto-free when done
  Worker.Start;                          // begin execution
end;

// TThread.CreateAnonymousThread - quick one-off
TThread.CreateAnonymousThread(
  procedure
  var I: Integer;
  begin
    for I := 1 to 10 do
      TThread.Queue(nil,
        procedure
        begin
          Form1.Label1.Caption := IntToStr(I);
        end);
  end).Start;

병렬 프로그래밍 라이브러리 (PPL)

System.Threading의 병렬 프로그래밍 라이브러리(PPL)는 고수준 동시성 제공: TTask(fire-and-forget 비동기), TTask.Future<T>(반환값이 있는 비동기), 병렬 루프. 작업은 스레드 풀을 자동으로 사용 — 스레드 관리 불필요. WaitForAll/WaitForAny는 여러 작업을 조합. Future.Value는 결과가 준비될 때까지 차단(promise처럼). PPL은 원시 TThread의 현대적 대안 — 더 깔끔하고 조합 가능하며 async/await 패턴과 통합. 작업은 예외를 캡처하고 .Value에 접근할 때 재발생시켜 적절한 오류 전파 가능. 작업 간 세분화된 동기화를 위해 TEvent/TCountdownEvent 사용.

delphi
uses System.Threading, System.SyncObjs;

// TTask - async operations
var
  Task: ITask;
begin
  Task := TTask.Create(
    procedure
    begin
      Sleep(2000);  // simulate work
      TThread.Queue(nil,
        procedure
        begin
          ShowMessage('Task done');
        end);
    end);
  Task.Start;
end;

// TTask.WaitForAll - wait for multiple tasks
var
  Tasks: array of ITask;
begin
  SetLength(Tasks, 3);
  Tasks[0] := TTask.Create(procedure begin DownloadFile('a.txt'); end);
  Tasks[1] := TTask.Create(procedure begin DownloadFile('b.txt'); end);
  Tasks[2] := TTask.Create(procedure begin DownloadFile('c.txt'); end);

  for var T in Tasks do T.Start;

  // wait for all (with timeout)
  if TTask.WaitForAll(Tasks, 30000) then
    ShowMessage('All downloads complete')
  else
    ShowMessage('Timeout');
end;

// TTask.Future<T> - async with return value
var
  Future: IFuture<string>;
begin
  Future := TTask.Future<string>(
    function: string
    begin
      Result := FetchDataFromServer;  // long operation
    end);

  // do other work...
  ShowMessage('Result: ' + Future.Value);  // blocks until ready
end;

병렬 For & 루프

TParallel.For는 스레드 풀에 걸쳐 루프를 병렬화 — 반복이 여러 코어에서 동시에 실행. 'for'는 예약어이므로 &For(이스케이프된 키워드) 사용. 독립적인 반복이 있는 CPU 바운드 루프의 경우 멀티코어 머신에서 거의 선형 가속 제공 가능. 중요: 공유 상태(Sum 같은)는 잠금(TCriticalSection)으로 보호하거나 원자 연산을 위해 TInterlocked.Increment 사용. State.Break는 루프 중지(break처럼). State.ShouldExit는 Break가 호출되었는지 확인. 적은 반복이나 무거운 I/O가 있는 루프를 병렬화하지 마세요(스레드 풀이 고갈됨). Stride는 반복 스텝핑 제어. 중첩된 병렬 루프는 거의 도움되지 않음 — 외부 루프가 이미 코어를 포화.

delphi
uses System.Threading, System.SyncObjs;

// TParallel.For - parallelized loop
var
  Sum: Integer;
  Lock: TCriticalSection;
  I: Integer;
begin
  Sum := 0;
  Lock := TCriticalSection.Create;
  try
    TParallel.&For(1, 1000000,
      procedure(Index: Integer)
      begin
        // thread-safe accumulation
        Lock.Enter;
        try
          Sum := Sum + Index;
        finally
          Lock.Leave;
        end;
      end);
    ShowMessage('Sum: ' + IntToStr(Sum));
  finally
    Lock.Free;
  end;
end;

// with stride and state
TParallel.&For(1, 100,
  procedure(Index: Integer; var State: TParallelLoopState)
  begin
    if Index = 50 then
      State.Break;                  // stop after current iterations
    // process Index...
  end);

// TParallel.For with step (stride)
TParallel.&For(0, 99, 2,    // 0, 2, 4, 6, ...
  procedure(Index: Integer)
  begin
    ProcessEven(Index);
  end);

// nested parallel loops (use sparingly)
TParallel.&For(0, 9,
  procedure(I: Integer)
  begin
    TParallel.&For(0, 9,
      procedure(J: Integer)
      begin
        Matrix[I, J] := Compute(I, J);
      end);
  end);

동기화 기본 요소

System.SyncObjs는 동기화 기본 요소 제공: TCriticalSection(뮤텍스 — 한 번에 하나의 스레드만 진입), TEvent(스레드 간 신호 — SetEvent가 깨움, WaitFor가 차단), TMonitor(모든 객체 잠금 — Wait/Pulse를 가진 Java/C# 모니터처럼), TInterlocked(원자 Increment/Decrement/Exchange/CompareExchange — 잠금 없음). TCriticalSection이 가장 일반적 — 항상 Enter/Leave를 try/finally와 짝지으세요. TEvent.WaitFor은 wrSignaled, wrTimeout, wrAbandoned 반환. TMonitor.Wait은 일시적으로 잠금을 해제하고 차단; Pulse/PulseAll은 대기자를 깨움. TInterlocked는 단순 카운터에 가장 빠름 — 잠금 오버헤드 없음. 올바른 기본 요소 선택: 배타적 접근에는 CriticalSection, 신호에는 Event, 원자 카운터에는 Interlocked.

delphi
uses System.SyncObjs;

// TCriticalSection - mutual exclusion
var
  CS: TCriticalSection;
begin
  CS := TCriticalSection.Create;
  try
    CS.Enter;
    try
      // exclusive access to shared data
    finally
      CS.Leave;
    end;
  finally
    CS.Free;
  end;
end;

// TEvent - signaling between threads
var
  Event: TEvent;
begin
  Event := TEvent.Create(nil, True, False, '');  // manual reset
  try
    // thread 1: wait
    if Event.WaitFor(5000) = wrSignaled then
      ShowMessage('Signaled');

    // thread 2: signal
    Event.SetEvent;    // wake waiting threads
    Event.ResetEvent;  // clear signal
  finally
    Event.Free;
  end;
end;

// TMonitor - lock any object
var
  List: TList<Integer>;
begin
  TMonitor.Enter(List);
  try
    List.Add(42);
  finally
    TMonitor.Exit(List);
  end;

  // TMonitor.Wait / Pulse (like Java wait/notify)
  TMonitor.Enter(List);
  try
    while List.Count = 0 do
      TMonitor.Wait(List, 1000);  // release lock, wait
    TMonitor.PulseAll(List);      // wake waiting threads
  finally
    TMonitor.Exit(List);
  end;
end;

// TInterlocked - atomic operations
TInterlocked.Increment(Counter);
TInterlocked.Exchange(Value, 42);

스레드 풀 & Async/Await 패턴

TThreadPool은 작업자 스레드 풀 관리 — 스레드 재사용으로 생성 오버헤드 방지. 작업 부하에 따라 최소/최대 스레드 설정(CPU 바운드: ~코어 수, I/O 바운드: 더 많이). TTask.Run은 Create+Start의 약자. ContinueWith는 작업을 체인 — 선행 작업이 완료된 후 실행, 파이프라인 활성화. Task.Status(Created, WaitingToRun, Running, Completed, Canceled, Faulted)는 수명 주기 추적. ICancellation은 협력적 취소 — 긴 작업에서 주기적으로 IsCancelled 확인. 진정한 async/await를 위해 Delphi에는 언어 수준 await가 없지만 TTask.Future + .Value가 동등한 의미론 제공. OmniThreadLibrary(OTL)은 PPL 위에 더 높은 수준의 추상화(파이프라인, 메시지 전달) 제공.

delphi
uses System.Threading, System.SyncObjs;

// configure thread pool
var
  Pool: TThreadPool;
begin
  Pool := TThreadPool.Create;
  try
    Pool.SetMaxWorkerThreads(8);
    Pool.SetMinWorkerThreads(2);

    // use pool with TTask
    TTask.Run(
      procedure
      begin
        // runs on the pool
      end, Pool);
  finally
    Pool.Free;
  end;
end;

// async/await pattern using futures
function FetchDataAsync: IFuture<TStrings>;
begin
  Result := TTask.Future<TStrings>(
    function: TStrings
    begin
      Result := TStringList.Create;
      // simulate slow fetch
      TThread.Sleep(2000);
      Result.LoadFromFile('data.txt');
    end);
end;

// chain tasks
var
  Task1, Task2: ITask;
begin
  Task1 := TTask.Run(
    procedure
    begin
      DownloadFile('part1.zip');
    end);

  // Task2 runs after Task1 completes
  Task2 := Task1.ContinueWith(
    procedure(const ATask: ITask)
    begin
      if ATask.Status = TTaskStatus.Completed then
        ProcessFile('part1.zip')
      else
        ShowMessage('Download failed');
    end);
end;

// cancellation
var
  Cancel: ICancellation;
begin
  Cancel := TTask.CurrentTask.Cancellation;
  while not Cancel.IsCancelled do
  begin
    DoChunk;
    Sleep(100);
  end;
end;
16

Indy로 네트워크 프로그래밍

TCP 클라이언트 & 서버 (Indy)

Indy(Internet Direct)는 Delphi의 번들 네트워킹 라이브러리. TIdTCPClient는 서버에 연결 — 줄 기반 프로토콜에는 WriteLn/ReadLn, 바이너리에는 Write/Read. ConnectTimeout은 중단 방지. TIdTCPServer는 연결 수신 — OnExecute는 클라이언트당 스레드에서 실행(AContext가 각 연결을 나타냄). Indy는 차단 소켓 사용(더 단순한 모델 — 콜백 없음), 서버 처리기는 작업자 스레드에서 실행. 연결 끊김을 항상 우아하게 처리. 고성능 서버의 경우 ICS(겹친 I/O)나 Synapse 고려. Indy 컴포넌트는 비시각적 — 폼에 놓거나 코드에서 생성. Active := True로 수신 시작. DefaultPort는 수신 포트 설정.

delphi
uses IdTCPClient, IdTCPServer, IdContext;

// TCP Client
var
  Client: TIdTCPClient;
  Response: string;
begin
  Client := TIdTCPClient.Create(nil);
  try
    Client.Host := 'example.com';
    Client.Port := 8080;
    Client.ConnectTimeout := 5000;
    Client.Connect;
    try
      Client.IOHandler.WriteLn('Hello Server');
      Response := Client.IOHandler.ReadLn;
      ShowMessage('Server: ' + Response);
    finally
      Client.Disconnect;
    end;
  finally
    Client.Free;
  end;
end;

// TCP Server
type
  TForm1 = class(TForm)
    IdTCPServer1: TIdTCPServer;
    procedure FormCreate(Sender: TObject);
    procedure ServerExecute(AContext: TIdContext);
  end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  IdTCPServer1.DefaultPort := 8080;
  IdTCPServer1.OnExecute := ServerExecute;
  IdTCPServer1.Active := True;
end;

procedure TForm1.ServerExecute(AContext: TIdContext);
var
  Msg: string;
begin
  Msg := AContext.Connection.IOHandler.ReadLn;
  AContext.Connection.IOHandler.WriteLn('Echo: ' + Msg);
  if Msg = 'quit' then
    AContext.Connection.Disconnect;
end;

HTTP 클라이언트 (TIdHTTP)

TIdHTTP는 Indy의 HTTP 클라이언트 — GET, POST, PUT, DELETE, 헤더, 쿠키, SSL/TLS 지원. HTTPS의 경우 TIdSSLIOHandlerSocketOpenSSL 연결(OpenSSL DLL 필요: libeay32/ssleay32 또는 libcrypto/libssl). Request.ContentType과 CustomHeaders는 요청 메타데이터 설정. POST는 문자열 본문(JSON/API용)이나 TStrings(폼 데이터용)을 받음. EIdHTTPProtocolException은 HTTP 오류(404, 500 등)를 ErrorCode와 ErrorMessage로 잡음. 현대 REST 클라이언트의 경우 TRESTClient(내장, OpenSSL 종속성 없음)나 TNetHTTPClient(더 가벼움) 고려. HTTP와 SSL 핸들러는 항상 finally 블록에서 해제. Http.HandleRedirects := True로 301/302 리다이렉트 자동 팔로우.

delphi
uses IdHTTP, IdSSLOpenSSL, System.JSON;

var
  Http: TIdHTTP;
  SSL: TIdSSLIOHandlerSocketOpenSSL;
  Response: string;
  JSON: TJSONObject;
  Params: TStringList;
begin
  Http := TIdHTTP.Create(nil);
  SSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  try
    Http.IOHandler := SSL;
    SSL.SSLOptions.Method := sslvTLSv1_2;
    Http.Request.ContentType := 'application/json';
    Http.Request.CustomHeaders.AddValue('Authorization', 'Bearer token123');

    // GET request
    Response := Http.Get('https://api.example.com/users');
    ShowMessage(Response);

    // POST with JSON body
    JSON := TJSONObject.Create;
    try
      JSON.AddPair('name', 'Alice');
      JSON.AddPair('age', 30);
      Response := Http.Post('https://api.example.com/users', JSON.ToJSON);
    finally
      JSON.Free;
    end;

    // POST form data
    Params := TStringList.Create;
    try
      Params.Add('username=alice');
      Params.Add('password=secret');
      Response := Http.Post('https://api.example.com/login', Params);
    finally
      Params.Free;
    end;

    // handle errors
    try
      Http.Get('https://api.example.com/missing');
    except
      on E: EIdHTTPProtocolException do
        ShowMessage('HTTP ' + IntToStr(E.ErrorCode) + ': ' + E.ErrorMessage);
    end;
  finally
    SSL.Free;
    Http.Free;
  end;
end;

SMTP 이메일 (TIdSMTP)

TIdSMTP는 SMTP 서버를 통해 이메일 전송. TIdMessage는 이메일을 나타냄(From, Recipients, Subject, Body). Gmail/Office365의 경우 TLS(포트 587, utUseExplicitTLS) 또는 SSL(포트 465, utUseImplicitTLS) 사용. Gmail은 2FA가 활성화된 '앱 비밀번호'(일반 비밀번호가 아님) 필요. TIdAttachmentFile은 파일 첨부 추가. HTML 이메일의 경우 ContentType := 'text/html' 설정. 다중 파트(HTML + 일반 텍스트 + 첨부 파일)의 경우 TIdMessageBuilderHTML 사용. 일반 포트: 25(암호화 없음/릴레이), 465(SSL), 587(STARTTLS). Disconnect 보장을 위해 항상 Connect/Send를 try/finally로 감싸세요. 이메일 수신의 경우 TIdPOP3나 TIdIMAP4 사용.

delphi
uses IdSMTP, IdMessage, IdSSLOpenSSL, IdExplicitTLSClientServerBase;

var
  SMTP: TIdSMTP;
  Msg: TIdMessage;
  SSL: TIdSSLIOHandlerSocketOpenSSL;
begin
  SMTP := TIdSMTP.Create(nil);
  Msg := TIdMessage.Create(nil);
  SSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  try
    // SMTP config (Gmail example)
    SMTP.Host := 'smtp.gmail.com';
    SMTP.Port := 587;
    SMTP.UseTLS := utUseExplicitTLS;
    SMTP.IOHandler := SSL;
    SSL.SSLOptions.Method := sslvTLSv1_2;
    SMTP.Username := '[email protected]';
    SMTP.Password := 'app-password';

    // message
    Msg.From.Address := '[email protected]';
    Msg.From.Name := 'My App';
    Msg.Recipients.Add.Address := '[email protected]';
    Msg.Subject := 'Test from Delphi';
    Msg.Body.Text := 'Hello,' + sLineBreak + 'This is a test email.';

    // attachment
    var Attachment := TIdAttachmentFile.Create(Msg.MessageParts,
      'C:\report.pdf');

    // HTML body
    Msg.ContentType := 'text/html';
    Msg.Body.Text := '<h1>Hello</h1><p>HTML email from Delphi</p>';

    // connect and send
    SMTP.Connect;
    try
      SMTP.Send(Msg);
      ShowMessage('Email sent!');
    finally
      SMTP.Disconnect;
    end;
  finally
    SSL.Free;
    Msg.Free;
    SMTP.Free;
  end;
end;

UDP & 원시 소켓

UDP는 비연결 — 핸드셰이크 없음, 보장된 전달 없음, 하지만 TCP보다 빠름. TIdUDPClient.Send는 데이터그램 발사; ReceiveString은 타임아웃으로 응답 대기. BroadcastEnabled는 255.255.255.255로 전송(LAN의 모든 장치) — 서비스 발견에 유용. TIdUDPServer.OnUDPRead는 데이터그램 수신; ABinding.PeerIP/PeerPort는 송신자 식별. UDP에 이상적인 용도: DNS, SNMP, 게임 상태 업데이트, 스트리밍 미디어, 발견 프로토콜. UDP 위에 신뢰성을 위해 애플리케이션 수준에서 ACK/재시도 구현. TIdBytes는 Indy의 바이트 배열 타입 — 변환에는 BytesToString/ToBytes 사용. 원시 소켓 제어(원시 IP 패킷, 사용자 정의 프로토콜)의 경우 WinSock2 유닛이나 Synapse 라이브러리 사용.

delphi
uses IdUDPClient, IdUDPServer, IdSocketHandle;

// UDP Client (connectionless, fire-and-forget)
var
  UDP: TIdUDPClient;
begin
  UDP := TIdUDPClient.Create(nil);
  try
    UDP.Host := '255.255.255.255';  // broadcast
    UDP.Port := 9999;
    UDP.BroadcastEnabled := True;
    UDP.Send('DISCOVER');

    // receive response
    var Response: string;
    UDP.ReceiveString(Response, 1000);  // timeout 1s
    ShowMessage(Response);
  finally
    UDP.Free;
  end;
end;

// UDP Server
type
  TForm1 = class(TForm)
    IdUDPServer1: TIdUDPServer;
    procedure FormCreate(Sender: TObject);
    procedure UDPRead(AThread: TIdUDPListenerThread;
      const AData: TIdBytes; ABinding: TIdSocketHandle);
  end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  IdUDPServer1.DefaultPort := 9999;
  IdUDPServer1.OnUDPRead := UDPRead;
  IdUDPServer1.Active := True;
end;

procedure TForm1.UDPRead(AThread: TIdUDPListenerThread;
  const AData: TIdBytes; ABinding: TIdSocketHandle);
var
  Msg: string;
begin
  Msg := BytesToString(AData);
  // reply to sender
  ABinding.SendTo(ABinding.PeerIP, ABinding.PeerPort,
    ToBytes('ACK: ' + Msg));
end;

// raw socket with TIdIOHandlerSocket
// for low-level protocols, use WinSock2 unit directly

FTP & REST 클라이언트

TIdFTP는 FTP 클라이언트 기능 제공 — Connect, List, Put(업로드), Get(다운로드), MakeDir, ChangeDir. Passive 모드(Passive := True)는 NAT/방화벽을 통해 작동. UseTLS는 FTP 보안(FTPS). SFTP(SSH 기반)의 경우 타사 라이브러리(libssh2, SecureBlackbox) 사용. TRESTClient/TRESTRequest/TRESTResponse는 내장 REST 컴포넌트(OpenSSL 종속성 없음) — 현대 API 소비에 이상적. Resource는 AddUrlSegment로 채워지는 {param} 자리 표시자 사용. Execute는 요청 전송; RESTResponse.Content는 본문 보유; JSONValue는 JSON을 자동으로 파싱. REST 컴포넌트는 OAuth2, 기본 인증, 사용자 정의 인증자 지원. 고성능 REST의 경우 TNetHTTPClient(더 가벼움)나 최대 제어를 위해 Indy의 TIdHTTP 고려.

delphi
uses IdFTP, IdFTPCommon, IdExplicitTLSClientServerBase;

// FTP client
var
  FTP: TIdFTP;
begin
  FTP := TIdFTP.Create(nil);
  try
    FTP.Host := 'ftp.example.com';
    FTP.Username := 'user';
    FTP.Password := 'pass';
    FTP.Passive := True;            // NAT-friendly mode
    FTP.UseTLS := utUseExplicitTLS;
    FTP.Connect;
    try
      // list directory
      var Listing: TStringList := TStringList.Create;
      try
        FTP.List(Listing);
        for var S in Listing do
          ShowMessage(S);
      finally
        Listing.Free;
      end;

      // change directory
      FTP.ChangeDir('/uploads');

      // upload/download
      FTP.Put('C:\local.txt', 'remote.txt');
      FTP.Get('remote.txt', 'C:\downloaded.txt');

      // create/remove directory
      FTP.MakeDir('newfolder');
      FTP.RemoveDir('oldfolder');
    finally
      FTP.Disconnect;
    end;
  finally
    FTP.Free;
  end;
end;

// REST Client (built-in, no Indy needed)
uses REST.Client, REST.Types;

var
  RESTClient: TRESTClient;
  RESTRequest: TRESTRequest;
  RESTResponse: TRESTResponse;
begin
  RESTClient := TRESTClient.Create('https://api.example.com');
  RESTRequest := TRESTRequest.Create(RESTClient);
  RESTResponse := TRESTResponse.Create(nil);
  try
    RESTRequest.Resource := 'users/{id}';
    RESTRequest.Method := TRESTRequestMethod.rmGET;
    RESTRequest.Params.AddUrlSegment('id', '42');
    RESTRequest.Params.AddItem('fields', 'name,email', pkGETorPOST);

    RESTRequest.Execute;
    ShowMessage(RESTResponse.Content);     // JSON response

    // access JSON fields directly
    ShowMessage(RESTResponse.JSONValue.GetValue<string>('name'));
  finally
    RESTResponse.Free;
    RESTRequest.Free;
    RESTClient.Free;
  end;
end;
17

DLL & BPL 패키지

DLL 생성 & 사용

DLL(Dynamic Link Libraries)은 애플리케이션 간 코드 공유. 'library' 키워드('program'이 아님)로 DLL 빌드. 'exports'는 외부 호출자가 사용 가능한 함수를 나열. stdcall은 표준 Windows 호출 규약(C/C++, VB, C# 호환). 정적 임포트(external)는 컴파일 타임에 링크 — 런타임에 DLL이 존재해야 함. 동적 로딩(LoadLibrary/GetProcAddress)은 런타임에 로드 — 플러그인과 선택적 기능 활성화. FreeLibrary는 DLL 언로드. PChar(PWideChar)는 DLL 내보내기의 표준 문자열 타입(공유 메모리, Delphi 특정 타입 없음). Delphi 문자열, 객체, 인터페이스를 직접 내보내지 마세요 — Delphi 내부용. Delphi-Delphi 문자열 공유에는 ShareMem 유닛 사용(BorlndMM.dll 필요).

delphi
// --- MyLib.dpr (DLL project) ---
library MyLib;

uses
  System.SysUtils, System.Classes;

// exported function (stdcall for compatibility)
function Add(A, B: Integer): Integer; stdcall;
begin
  Result := A + B;
end;

// exported procedure
procedure ShowMessage(const Msg: PChar); stdcall;
begin
  WriteLn(Msg);
end;

// export table
exports
  Add name 'Add',
  ShowMessage name 'ShowMessage';

begin
end.

// --- MainApp.dpr (consumer) ---
// static import
function Add(A, B: Integer): Integer; stdcall; external 'MyLib.dll';
procedure ShowMsg(const Msg: PChar); stdcall; external 'MyLib.dll';

begin
  ShowMessage(IntToStr(Add(3, 4)));  // 7
end;

// dynamic loading (load at runtime)
var
  LibHandle: THandle;
  AddFunc: function(A, B: Integer): Integer; stdcall;
begin
  LibHandle := LoadLibrary('MyLib.dll');
  if LibHandle <> 0 then
  try
    @AddFunc := GetProcAddress(LibHandle, 'Add');
    if Assigned(@AddFunc) then
      ShowMessage(IntToStr(AddFunc(10, 20)));
  finally
    FreeLibrary(LibHandle);
  end;
end;

인터페이스로 객체 공유

DLL 경계를 넘어 객체 공유는 까다로움 — Delphi 클래스를 직접 내보낼 수 없음(다른 메모리 관리자, 다른 RTTI). 해결책: GUID가 있는 인터페이스 사용. DLL은 IPlugin을 반환하는 팩토리 함수(CreatePlugin)를 내보냄. 호스트 앱은 동일한 인터페이스(동일한 GUID!)를 정의하고 팩토리 호출. 인터페이스 참조 카운팅이 자동으로 정리 처리. 메모리 관리자 충돌을 피하기 위해 문자열에는 Delphi 문자열이 아닌 PChar 사용. 이것이 플러그인 아키텍처 패턴 — DLL을 동적으로 로드, 팩토리로 플러그인 생성, 인터페이스로 통신. 완전한 플러그인 시스템의 경우 Delphi의 플러그인 프레임워크를 고려하거나 RTL을 공유하고 직접 클래스 공유를 허용하는 패키지(BPL) 사용.

delphi
// --- PluginDLL.dpr ---
library PluginDLL;

type
  IPlugin = interface
    ['{12345678-1234-1234-1234-123456789012}']
    function GetName: PChar; stdcall;
    function Execute(const Input: PChar): PChar; stdcall;
    procedure Free; stdcall;
  end;

  TMyPlugin = class(TInterfacedObject, IPlugin)
  public
    function GetName: PChar; stdcall;
    function Execute(const Input: PChar): PChar; stdcall;
    procedure Free; stdcall;
  end;

function TMyPlugin.GetName: PChar;
begin
  Result := 'My Plugin v1.0';
end;

function TMyPlugin.Execute(const Input: PChar): PChar;
begin
  Result := PChar('Processed: ' + Input);
end;

// factory function - creates and returns the plugin
function CreatePlugin: IPlugin; stdcall;
begin
  Result := TMyPlugin.Create;
end;

exports CreatePlugin;

// --- HostApp.dpr ---
type
  IPlugin = interface
    ['{12345678-1234-1234-1234-123456789012}']
    function GetName: PChar; stdcall;
    function Execute(const Input: PChar): PChar; stdcall;
    procedure Free; stdcall;
  end;

var
  CreatePlugin: function: IPlugin; stdcall;
  Plugin: IPlugin;
  Handle: THandle;
begin
  Handle := LoadLibrary('PluginDLL.dll');
  if Handle <> 0 then
  try
    @CreatePlugin := GetProcAddress(Handle, 'CreatePlugin');
    if Assigned(@CreatePlugin) then
    begin
      Plugin := CreatePlugin;
      ShowMessage(Plugin.GetName);
      ShowMessage(Plugin.Execute('test'));
    end;
  finally
    FreeLibrary(Handle);
  end;
end;

BPL 패키지 (Borland Package Library)

BPL(Borland Package Libraries)은 Delphi 특정 공유 라이브러리 — Delphi RTL을 공유하여 직접 클래스/객체 공유 가능(DLL과 달리). 'package' 키워드로 빌드. 런타임 패키지는 EXE 크기를 줄이고(.bpl 파일의 공유 코드) 핫스왑 가능 모듈을 활성화. LoadPackage/UnloadPackage로 BPL을 동적으로 로드 — GetClass는 이름으로 등록된 클래스 찾기. RegisterClass/UnRegisterClass는 클래스를 발견 가능하게. BPL은 Delphi RTL BPL(rtl.bpl, vcl.bpl)이 배포되어야 함. BPL 용도: 플러그인 아키텍처(Delphi 타입 직접 공유), 모듈형 애플리케이션(주문형 기능 로드), 메모리 감소(공유 코드 한 번 로드). 크로스 언어 공유에는 DLL 사용; Delphi 전용의 경우 BPL이 더 강력.

delphi
// --- MyPackage.dpk (runtime package) ---
package MyPackage;

requires
  rtl,
  vcl;

contains
  MyUnit in 'MyUnit.pas',
  MyForm in 'MyForm.pas' {Form1};

// compile: dcc32 -B MyPackage.dpk
// produces MyPackage.bpl (shared runtime package)

// --- Using the package ---
// Option 1: link statically (compile-time reference)
// Project → Options → Packages → Runtime packages → add MyPackage.bpl

// Option 2: load dynamically with LoadPackage
var
  PackageModule: THandle;
  FormClass: TPersistentClass;
begin
  PackageModule := LoadPackage('MyPackage.bpl');
  try
    // register and use forms/classes from the package
    FormClass := GetClass('TForm1');
    if FormClass <> nil then
      with TFormClass(FormClass).Create(Application) do
        try
          ShowModal;
        finally
          Free;
        end;
  finally
    UnloadPackage(PackageModule);
  end;
end;

// RegisterClass in the package's unit:
unit MyForm;
interface
uses Vcl.Forms;
type
  TForm1 = class(TForm)
  end;
implementation
initialization
  RegisterClass(TForm1);    // make class discoverable
finalization
  UnRegisterClass(TForm1);
end.

경계를 넘어선 메모리 관리

#1 DLL 함정: 한 모듈에서 할당된 메모리를 다른 모듈에서 해제. 각 모듈은 자체 메모리 관리자를 가짐 — 혼합하면 힙 손상과 크래시. 해결책: (1) ShareMem — BorlndMM.dll 공유, 하지만 해당 DLL 배포 필요. (2) 호출자 할당 패턴 — 호출자가 버퍼 제공, DLL이 채움(가장 안전, 언어 무관). (3) SimpleShareMem/FastMM — 현대 공유 메모리 관리자(FastMM은 Delphi 2006부터 기본). (4) 콜백 기반 해제 — DLL이 free 함수 제공. PChar 반환의 경우 StrNew/StrDispose 사용(Windows API, 공유). 프로덕션 Delphi-Delphi의 경우 BPL(공유 RTL)이나 SimpleShareMem 사용. 크로스 언어의 경우 항상 호출자 할당 패턴 사용. 공유 메모리 관리자 없이 DLL 경계를 넘어 Delphi 문자열/객체/인터페이스 타입을 전달하지 마세요.

delphi
// PROBLEM: different memory managers in EXE and DLL
// → crashes when freeing memory allocated in another module

// Solution 1: ShareMem (Delphi-to-Delphi only)
// First unit in both EXE and DLL .dpr file:
uses
  ShareMem;  // uses BorlndMM.dll as shared memory manager

// Solution 2: Caller allocates, caller frees (safest)
// DLL fills a buffer provided by the caller
procedure GetData(Buffer: PChar; var BufSize: Integer); stdcall;
var
  Data: string;
begin
  Data := 'Hello from DLL';
  BufSize := Length(Data) + 1;
  if Buffer <> nil then
    StrLCopy(Buffer, PChar(Data), BufSize);
end;

// caller:
var
  Size: Integer;
  Buffer: PChar;
begin
  GetData(nil, Size);              // query size
  GetMem(Buffer, Size);            // allocate
  try
    GetData(Buffer, Size);         // fill
    ShowMessage(Buffer);
  finally
    FreeMem(Buffer);               // caller frees
  end;
end;

// Solution 3: Use SafeCall / COM-style allocation
// Solution 4: Use FastMM as shared manager (modern approach)
// Add SimpleShareMem unit (uses FastMM) to both projects

// Solution 5: Return only simple types / PChar with callback
type
  TFreeCallback = procedure(Ptr: Pointer); stdcall;
function CreateString(out S: PChar; FreeProc: TFreeCallback): Boolean; stdcall;
begin
  S := StrNew('Allocated in DLL');
  Result := True;
  // caller calls FreeProc(S) which calls StrDispose in the DLL
end;

리소스 파일 & 임베딩

리소스 파일은 바이너리 데이터(이미지, 아이콘, 사운드, 문자열, 버전 정보)를 EXE/DLL에 임베드 — 외부 파일 불필요. .rc 스크립트 생성, brcc32로 컴파일(또는 IDE가 자동 컴파일). {$R file.res}로 링크. TResourceStream은 RCDATA 리소스를 스트림으로 읽기. LoadIcon/LoadString은 특정 리소스 타입에 Windows API 사용. 리소스는 런타임에 읽기 전용이지만 모든 것을 하나의 파일로 유지(배포에 좋음). 일반적인 용도: 애플리케이션 아이콘, 시작 화면 이미지, 기본 설정, WAV 사운드, 버전 정보(파일 속성 대화상자), 현지화된 문자열. 큰 데이터의 경우 임베딩 전에 압축 고려. 리소스 ID는 이름(문자열)이나 숫자. RT_RCDATA는 일반 바이너리 리소스 타입.

delphi
// --- Resource script (.rc file) ---
// MyResources.rc:
//   LOGO     RCDATA "logo.png"
//   ICON1    ICON    "app.ico"
//   VERSION  VERSIONINFO ...
//   WAVE1    WAVE    "sound.wav"
//   STR1     STRINGTABLE { "Hello" }

// compile: brcc32 MyResources.rc → MyResources.res
// or add .rc to project (auto-compiled)

// --- In .dpr ---
{$R MyResources.res}  // link resource

// --- Loading resources ---
uses System.Classes, Vcl.Graphics, Winapi.Windows;

// load RCDATA (binary data)
var
  Stream: TResourceStream;
begin
  Stream := TResourceStream.Create(HInstance, 'LOGO', RT_RCDATA);
  try
    Image1.Picture.LoadFromStream(Stream);
  finally
    Stream.Free;
  end;
end;

// load icon
var
  Icon: TIcon;
begin
  Icon := TIcon.Create;
  try
    Icon.Handle := LoadIcon(HInstance, 'ICON1');
    Image1.Picture.Icon.Assign(Icon);
  finally
    Icon.Free;
  end;
end;

// load string resource
var
  S: string;
  Buffer: array[0..255] of Char;
begin
  LoadString(HInstance, 1, Buffer, SizeOf(Buffer));
  S := Buffer;
end;

// embed a file as resource at compile time
// {$R 'data.bin' 'data.bin'}  // or use .rc
18

디버깅 & 성능 튜닝

디버거 & 중단점

Delphi의 IDE 디버거는 강력함: 거터를 클릭하여 중단점 설정. 조건부 중단점은 식이 참일 때만 중단(예: i > 100). 로그/추적 중단점은 중지 없이 메시지 로깅 — 루프 모니터링에 좋음. asm int 3 end는 코드에 하드 중단점 생성(CPU 트랩). OutputDebugString은 이벤트 로그 창(및 DebugView 도구)에 로깅. Assert는 디버그 빌드에서 조건 검사({$C-} 또는 release에서 어설션 끄기로 비활성화). DebugHook은 IDE에서 실행 중일 때 0이 아님. 호출 스택 창은 호출 체인 추적; 스레드 창은 모든 스레드 검사; 지역 변수는 현재 범위 표시. RTL/VCL 소스 코드를 단계별 실행하려면 'Use Debug DCUs' 활성화.

delphi
// Conditional breakpoints (set in IDE):
//   Break when expression is true
//   e.g., (i > 100) and (List.Count > 0)

// Log breakpoints (no break, just log):
//   Log message: "Iteration {i}, Count={List.Count}"

// Trace points / Action breakpoints:
//   Run macro or evaluate expression on hit

// Code-based breakpoints:
var
  I: Integer;
begin
  for I := 1 to 1000 do
  begin
    // break only when condition met
    if (I mod 100 = 0) and DebugHook <> 0 then
      asm int 3 end;            // hard breakpoint (CPU trap)

    // or use OutputDebugString for logging
    OutputDebugString(PChar('Processing ' + IntToStr(I)));
  end;
end;

// Assert (only in debug builds)
Assert(List.Count > 0, 'List must not be empty');

// DebugHook: 0 = release, 1 = IDE, 2 = IDE step-over
if DebugHook <> 0 then
  ShowMessage('Running in debugger');

// Watch and Evaluate expressions in IDE:
//   List.Count
//   List[0].Name
//   TMyObject(Obj).PrivateField  (with "Use Debug DCUs")

// Call Stack window shows the call chain
// Threads window shows all active threads
// Local Variables shows current scope variables

예외 처리 & 스택 추적

Delphi 예외: try/except는 오류 잡기, try/finally는 정리 보장. 예외 클래스는 계층 구조 형성: Exception → EDivByZero, EAccessViolation, EListError, EAbort(조용함) 등. 'on E: ExceptionType do'는 특정 타입 잡기; 기본 'on E: Exception do'는 모두 잡기. 'raise;'는 현재 예외 재발생(스택 추적 보존). EAbort(또는 Abort 프로시저)는 조용한 예외 발생(대화상자 없음). TApplicationEvents.OnException은 전역 처리기 — 처리되지 않은 예외 잡기. 스택 추적을 위해 JCL(JclDebug)이나 MadExcept/ExceptionHunter 사용 — 호출 스택, 레지스터 덤프, 크래시 보고서 이메일까지 캡처. 사후 디버깅을 위해 항상 예외 로깅. 프로덕션에서 예외를 조용히 삼키지 마세요.

delphi
uses
  System.SysUtils, System.Diagnostics;

// structured exception handling
try
  RiskyOperation;
except
  on E: EDivByZero do
    ShowMessage('Division error: ' + E.Message);
  on E: EAccessViolation do
    ShowMessage('Access violation at ' + E.Message);
  on E: Exception do
  begin
    ShowMessage('Unexpected: ' + E.ClassName + ': ' + E.Message);
    raise;                         // re-raise
  end;
end;

// finally (always executes)
try
  AcquireResource;
  UseResource;
finally
  ReleaseResource;                 // always runs
end;

// nested try/except/finally
try
  try
    RiskyCode;
  except
    on E: Exception do
    begin
      LogError(E);
      raise EAbort.Create('');     // suppress display
    end;
  end;
finally
  Cleanup;
end;

// global exception handler
procedure TForm1.ApplicationEvents1Exception(Sender: TObject; E: Exception);
begin
  LogError(Format('%s: %s', [E.ClassName, E.Message]));
  if not (E is EAbort) then
    ShowMessage('Error: ' + E.Message);
end;

// get stack trace (with JCL or MadExcept)
// JclDebug: JclCreateStackInfo, JclLastExceptStackList

프로파일링 & 성능

TStopwatch는 고정밀 타이머(QueryPerformanceCounter 사용). 최적화 전에 항상 벤치마크 — 추측하지 마세요. ReportMemoryLeaksOnShutdown := True는 프로그램 종료 시 누수 잡기(디버그 빌드). 일반적인 Delphi 성능 함정: (1) 루프에서 문자열 연결은 복사본 생성 — TStringBuilder나 미리 할당 사용. (2) 루프에서 SetLength는 재할당 — 크기를 한 번 설정. (3) 값으로 문자열/배열 전달은 복사 — 읽기 전용 매개변수에 'const' 사용. (4) TStringList.Sorted + Find는 O(log n); 정렬되지 않은 IndexOf는 O(n). (5) TList<T>.Add는 상각 O(1)이지만 앞에 Insert는 O(n). 심층 프로파일링의 경우 Sampling Profiler(무료), AQTime, GpProfile 사용 — 코드 변경 없이 핫스팟 식별. 시간의 80%를 차지하는 코드의 20%를 최적화.

delphi
uses System.Diagnostics;

// TStopwatch - precise timing
var
  SW: TStopwatch;
  Elapsed: Int64;
begin
  SW := TStopwatch.StartNew;
  try
    ExpensiveOperation;
  finally
    SW.Stop;
    ShowMessage(Format('Elapsed: %d ms', [SW.ElapsedMilliseconds]));
  end;
end;

// benchmark comparison
function Benchmark(const Name: string; const Action: TProc): Int64;
var
  SW: TStopwatch;
  I: Integer;
begin
  SW := TStopwatch.StartNew;
  for I := 1 to 1000 do
    Action;
  SW.Stop;
  WriteLn(Format('%s: %d ms', [Name, SW.ElapsedMilliseconds]));
  Result := SW.ElapsedMilliseconds;
end;

// memory usage
var
  Mem: TMemoryManagerState;
begin
  GetMemoryManagerState(Mem);
  ShowMessage(Format('Allocated: %d bytes', [Mem.TotalAllocated]));

  // report memory leaks on shutdown
  ReportMemoryLeaksOnShutdown := True;  // shows leak dialog on exit
end;

// common optimizations:
// 1. Use TStringBuilder for heavy string concatenation
var SB := TStringBuilder.Create;
try
  for var I := 1 to 10000 do
    SB.Append('Line ').Append(I).AppendLine;
  Result := SB.ToString;
finally
  SB.Free;
end;

// 2. SetLength once, not in a loop
SetLength(Result, Count);  // pre-allocate
for I := 0 to Count - 1 do
  Result[I] := Compute(I);

// 3. Use const for strings/arrays (avoids copy)
procedure Process(const Data: string);  // const = no copy

메모리 관리 & 누수

메모리 관리는 Delphi의 가장 큰 버그 원인. 규칙 #1: 모든 Create에는 일치하는 Free가 있어야 함. try/finally를 종교적으로 사용. 자동 관리를 위해 인터페이스(TInterfacedObject + 참조 카운팅) 사용 — Free 불필요. OwnsObjects := True인 TObjectList<T>는 포함된 객체를 자동으로 해제. ReportMemoryLeaksOnShutdown := True는 종료 시 누수된 객체 목록 대화상자 표시(디버그만). FullDebugMode의 FastMM(기본 메모리 관리자)은 할당 스택 추적과 함께 파일로 누수 로그 — 누수 추적에 필수. 일반적인 누수 패턴: try/finally 누락, 이벤트 처리기 제거 안 됨, 순환 참조([Weak]으로 수정), 스레드 해제 안 됨, finalization에서 전역 객체 해제 안 됨. 유닛의 finalization 섹션은 종료 시 실행 — 전역 정리에 사용.

delphi
// Rule: every Create needs a Free (or use interfaces)

// Pattern 1: try/finally
var
  Obj: TMyObject;
begin
  Obj := TMyObject.Create;
  try
    Obj.DoWork;
  finally
    Obj.Free;    // always freed
  end;
end;

// Pattern 2: interface reference counting (automatic)
var
  Obj: IMyInterface;
begin
  Obj := TMyObject.Create;  // TInterfacedObject
  Obj.DoWork;
  // freed automatically when Obj goes out of scope
end;

// Pattern 3: TObjectList (owns children)
var
  List: TObjectList<TPerson>;
begin
  List := TObjectList<TPerson>.Create(True);  // OwnsObjects
  try
    List.Add(TPerson.Create('Alice'));
    List.Add(TPerson.Create('Bob'));
    // freeing List frees all TPerson objects
  finally
    List.Free;
  end;
end;

// Detecting leaks
// 1. ReportMemoryLeaksOnShutdown := True;
// 2. FastMM (default since D2006) with FullDebugMode
//    → logs leaks with stack traces to file
// 3. Set breakpoint on System._DebugIntfMemLeak (FastMM)

// Common leak causes:
// - Create without Free (missing try/finally)
// - Event handler assigned but never removed
// - Circular references (use [Weak])
// - TThread not freed (FreeOnTerminate := True)
// - Global objects not freed in finalization

// finalization section for globals
var
  GlobalCache: TDictionary<string, TObject>;
initialization
  GlobalCache := TDictionary<string, TObject>.Create;
finalization
  GlobalCache.Free;    // cleanup on shutdown

코드 품질 & 테스팅

DUnitX는 현대 단위 테스트 프레임워크(DUnit 대체). [TestFixture]는 테스트 클래스 표시, [Test]는 테스트 메서드 표시, [Setup]/[TearDown]은 각 테스트 전/후 실행. [TestCase]는 인라인 데이터로 테스트 매개변수화. Assert.AreEqual/IsTrue/WillRaise는 결과 검증. 테스트 주도 개발(TDD): 테스트 먼저 작성, 그다음 코드. 테스트는 회귀를 잡고 예상 동작을 문서화. Delphi Mocks(또는 Spring4D 모킹)는 인터페이스에서 모킹 객체 생성 — Setup.Expect는 기대치 정의, VerifyAll은 충족 확인. 모킹은 유닛을 격리하는 데 필수(데이터베이스, 네트워크, 파일 시스템 모킹). 비즈니스 로직의 높은 커버리지 목표. 회귀를 일찍 잡기 위해 CI(지속적 통합)에서 테스트 실행. 통합 테스트는 컴포넌트가 함께 작동하는지 검증; 단위 테스트는 개별 유닛을 격리하여 검증.

delphi
// DUnitX - unit testing framework
uses DUnitX.TestFramework;

type
  [TestFixture]
  TCalculatorTests = class
  public
    [Setup]
    procedure Setup;

    [TearDown]
    procedure TearDown;

    [Test]
    procedure TestAdd;

    [Test]
    [TestCase('A', '1,2,3')]
    [TestCase('B', '10,20,30')]
    procedure TestAddParam(A, B, Expected: Integer);

    [Test]
    procedure TestDivideByZero;
  end;

procedure TCalculatorTests.TestAdd;
var
  Calc: TCalculator;
begin
  Calc := TCalculator.Create;
  try
    Assert.AreEqual(5, Calc.Add(2, 3));
    Assert.AreNotEqual(6, Calc.Add(2, 3));
    Assert.IsTrue(Calc.Add(0, 0) = 0);
  finally
    Calc.Free;
  end;
end;

procedure TCalculatorTests.TestDivideByZero;
var
  Calc: TCalculator;
begin
  Calc := TCalculator.Create;
  try
    Assert.WillRaise(
      procedure
      begin
        Calc.Divide(10, 0);
      end, EDivByZero);
  finally
    Calc.Free;
  end;
end;

// mock with interfaces
type
  [Mock]
  ILogger = interface
    ['{...}']
    procedure Log(const Msg: string);
  end;

// Delphi Mocks framework
var
  MockLogger: TMock<ILogger>;
begin
  MockLogger := TMock<ILogger>.Create;
  MockLogger.Setup.Expect.Once.When.Log('test');
  // ... use MockLogger.Object ...
  MockLogger.VerifyAll;  // asserts Log was called
end;
19

제네릭 & 컬렉션

제네릭 클래스 선언

제네릭은 캐스트 없이 타입 안전 컨테이너를 작성할 수 있게 함. 타입 이름 뒤에 <T>로 선언. 컴파일러는 사용된 타입별로 특수화된 버전 생성. 제네릭 타입의 동적 배열에는 array of 대신 TArray<T> 사용.

delphi
type
  TStack<T> = class
  private
    FItems: TArray<T>;
    FCount: Integer;
  public
    procedure Push(const AValue: T);
    function Pop: T;
    function Peek: T;
    function Count: Integer;
  end;

procedure TStack<T>.Push(const AValue: T);
begin
  if FCount = Length(FItems) then
    SetLength(FItems, FCount * 2 + 4);
  FItems[FCount] := AValue;
  Inc(FCount);
end;

TDictionary 사용

TDictionary<K,V>는 제네릭 해시 맵. Add는 중복 키에서 예외 발생; OrAdd는 upsert 수행. TryGetValue는 키가 없으면 false 반환(예외 아님). 사전은 항상 해제 — 기본적으로 객체를 소유하지 않음.

delphi
uses
  System.Generics.Collections;

var
  Dict: TDictionary<string, Integer>;
begin
  Dict := TDictionary<string, Integer>.Create;
  try
    Dict.Add('apple', 5);
    Dict.Add('banana', 3);
    Dict.OrAdd('apple', 10);  // add or replace

    if Dict.TryGetValue('apple', Value) then
      Writeln(Value);

    for var Pair in Dict do
      Writeln(Pair.Key, ': ', Pair.Value);
  finally
    Dict.Free;
  end;
end;

비교자가 있는 TList

TList<T>.Sort는 IComparer<T> 사용. TComparer<T>.Construct는 익명 함수를 비교자로 래핑. BinarySearch는 동일한 비교자로 정렬된 목록 필요. AddRange는 열린 배열이나 다른 목록을 받음.

delphi
var
  List: TList<Integer>;
begin
  List := TList<Integer>.Create;
  try
    List.AddRange([5, 2, 8, 1, 9]);
    List.Sort;  // ascending

    // custom comparer (descending)
    List.Sort(TComparer<Integer>.Construct(
      function(const L, R: Integer): Integer
      begin
        Result := R - L;
      end));

    List.BinarySearch(8, Index);  // requires sorted list
  finally
    List.Free;
  end;
end;

제네릭 제약 조건

제약 조건은 대체할 수 있는 타입을 제한: 'class'(참조 타입), 'record'(값 타입), 'constructor'(매개변수 없는 생성자), 또는 특정 조상 클래스. 여러 제약 조건은 쉼표로 구분. 'constructor' 없이는 T.Create를 호출할 수 없습니다.

delphi
type
  TRepository<T: class, constructor> = class
  public
    function CreateInstance: T;
    procedure Save(const AEntity: T);
  end;

function TRepository<T>.CreateInstance: T;
begin
  Result := T.Create;  // requires 'constructor' constraint
end;

// multiple constraints: class, constructor, specific base
type
  TControlFactory<T: TControl, constructor> = class ... end;

TObjectDictionary로 객체 소유권

TObjectDictionary<K,V>는 소유권으로 TDictionary 확장. [doOwnsValues], [doOwnsKeys], 또는 둘 다 전달. Remove/Clear/Free 시 소유된 객체가 자동으로 해제 — 객체 컬렉션에서 메모리 누수 방지.

delphi
var
  Dict: TObjectDictionary<string, TButton>;
begin
  // owns values — frees them automatically
  Dict := TObjectDictionary<string, TButton>.Create([doOwnsValues]);
  try
    Dict.Add('btn1', TButton.Create(nil));
    Dict.Add('btn2', TButton.Create(nil));
    Dict.Remove('btn1');  // frees the TButton
  finally
    Dict.Free;  // frees remaining buttons
  end;
end;
20

익명 메서드 & 클로저

기본 익명 메서드

익명 메서드는 인라인 함수 참조. 함수에는 TFunc<...>, 프로시저에는 TProc<...>. 둘러싸는 범위의 변수를 캡처(클로저). 변수에 할당 가능, 매개변수로 전달 가능.

delphi
var
  Adder: TFunc<Integer, Integer, Integer>;
begin
  Adder := function(A, B: Integer): Integer
    begin
      Result := A + B;
    end;

  Writeln(Adder(3, 4));  // 7
end;

변수를 캡처하는 클로저

캡처된 변수는 힙 할당되고 익명 메서드가 살아있는 동안 존재. MakeMultiplier에 대한 각 호출은 자체 Factor를 캡처 — 클로저는 독립적. 이것이 팩토리와 부분 적용이 작동하는 방식.

delphi
function MakeMultiplier(Factor: Integer): TFunc<Integer, Integer>;
begin
  Result := function(X: Integer): Integer
    begin
      Result := X * Factor;  // captures Factor
    end;
end;

var
  Double: TFunc<Integer, Integer>;
  Triple: TFunc<Integer, Integer>;
begin
  Double := MakeMultiplier(2);
  Triple := MakeMultiplier(3);
  Writeln(Double(10));  // 20
  Writeln(Triple(10));  // 30
end;

고차 함수

'reference to'는 익명 메서드와 호환되는 프로시저 타입 선언. Apply는 고차 함수 — 함수를 인수로 취함. map/filter/reduce 패턴 활성화. 동적 배열에는 TArray<Integer> 사용.

delphi
type
  TIntFunc = reference to function(X: Integer): Integer;

function Apply(const F: TIntFunc; Values: array of Integer): TArray<Integer>;
var
  I: Integer;
begin
  SetLength(Result, Length(Values));
  for I := 0 to High(Values) do
    Result[I] := F(Values[I]);
end;

var
  Squared: TArray<Integer>;
begin
  Squared := Apply(function(X: Integer): Integer
    begin
      Result := X * X;
    end, [1, 2, 3, 4, 5]);
end;

클로저가 있는 이벤트 처리기

익명 메서드는 전통적인 메서드 기반 이벤트 처리기를 대체하여 필드 없이 컨텍스트를 캡처. 일회성 처리기와 보일러플레이트 감소에 유용. 캡처된 Caption은 OnClick이 보유한 클로저 참조와 함께 살아있음.

delphi
procedure SetupButton(Button: TButton; const Caption: string);
begin
  Button.Caption := Caption;
  Button.OnClick := procedure(Sender: TObject)
    begin
      ShowMessage(Caption + ' clicked!');  // captures Caption
    end;
end;

// instead of:
//   procedure TForm1.Button1Click(Sender: TObject);
//   begin
//     ShowMessage('Button1 clicked!');
//   end;

익명이 있는 TThread

CreateAnonymousThread는 클로저를 스레드로 래핑 — fire-and-forget 백그라운드 작업. UI 업데이트를 메인 스레드로 마샬링하려면 TThread.Queue(또는 Synchronize) 사용. 작업자 스레드에서 UI 컨트롤을 직접 만지지 마세요.

delphi
TThread.CreateAnonymousThread(
  procedure
  var
    I: Integer;
  begin
    for I := 1 to 10 do
    begin
      TThread.Queue(nil,
        procedure
        begin
          Memo1.Lines.Add('Progress: ' + I.ToString);
        end);
      Sleep(100);
    end;
  end).Start;
21

속성 & RTTI

사용자 정의 속성 선언

속성은 TCustomAttribute를 상속하는 클래스. 타입, 필드, 메서드, 속성에 [AttrName(...)]으로 적용. 컴파일러가 RTTI에 임베드. 생성자 매개변수가 속성 인수가 됨.

delphi
type
  DisplayNameAttribute = class(TCustomAttribute)
  private
    FName: string;
  public
    constructor Create(const AName: string);
    property Name: string read FName;
  end;

  constructor DisplayNameAttribute.Create(const AName: string);
  begin
    FName := AName;
  end;

  [DisplayName('User Account')]
  TUser = class
    [DisplayName('Full Name')]
    FName: string;
  end;

RTTI로 속성 읽기

TRttiContext가 RTTI의 진입점. GetType은 클래스의 TRttiType 반환. GetAttributes는 적용된 모든 속성 반환. 속성을 읽으려면 속성 타입으로 캐스트. RTTI는 클래스가 {$M+}로 컴파일된 유닛에 있거나 TPersistent에서 파생되어야 함.

delphi
uses
  System.Rtti;

var
  Ctx: TRttiContext;
  RttiType: TRttiType;
  Attr: TCustomAttribute;
begin
  Ctx := TRttiContext.Create;
  try
    RttiType := Ctx.GetType(TUser);
    for Attr in RttiType.GetAttributes do
      if Attr is DisplayNameAttribute then
        Writeln(DisplayNameAttribute(Attr).Name);
  finally
    Ctx.Free;
  end;
end;

필드와 메서드 RTTI

GetFields는 모든 public/published 필드 반환. SetValue/GetValue는 이름으로 동적 필드 접근 제공 — 직렬 변환기와 ORM에 유용. GetMethods는 상속된 것 포함 모든 메서드 반환. RTTI는 직접 호출보다 느림.

delphi
var
  Ctx: TRttiContext;
  FieldType: TRttiField;
  Method: TRttiMethod;
  User: TUser;
begin
  Ctx := TRttiContext.Create;
  try
    User := TUser.Create;
    try
      for FieldType in Ctx.GetType(TUser).GetFields do
      begin
        Writeln(FieldType.Name, ': ', FieldType.FieldType.Name);
        FieldType.SetValue(User, 'Alice');  // set by RTTI
      end;
      Writeln(FieldType.GetValue(User).AsString);
    finally
      User.Free;
    end;
  finally
    Ctx.Free;
  end;
end;

속성 RTTI 및 호출

GetProperties는 published 속성 반환. IsReadable/IsWritable은 접근자 확인. GetValue/SetValue는 속성에서도 작동. TypeKind(tkInteger, tkString, tkClass 등)는 각 타입을 적절히 처리하게 함. 대부분의 Delphi 직렬 변환기가 이렇게 작동.

delphi
var
  Ctx: TRttiContext;
  Prop: TRttiProperty;
  Instance: TMyClass;
begin
  Instance := TMyClass.Create;
  try
    for Prop in Ctx.GetType(TMyClass).GetProperties do
    begin
      if Prop.IsReadable then
        Writeln(Prop.Name, ' = ', Prop.GetValue(Instance).ToString);
      if Prop.IsWritable and (Prop.PropertyType.TypeKind = tkInteger) then
        Prop.SetValue(Instance, 42);
    end;
  finally
    Instance.Free;
  end;
end;

이름으로 메서드 호출

GetMethod는 이름으로 메서드 찾기(대소문자 구분). Invoke는 TValue 배열 인수로 동적으로 호출. TValue는 모든 타입에 대한 변형 같은 래퍼. 플러그인 시스템, 스크립팅, 레이트 바인딩에 유용. TValue 반환 — AsInteger, AsString 등으로 변환.

delphi
var
  Ctx: TRttiContext;
  Method: TRttiMethod;
  Args: array of TValue;
  Result: TValue;
begin
  Method := Ctx.GetType(TMyClass).GetMethod('CalculateTotal');
  if Assigned(Method) then
  begin
    SetLength(Args, 2);
    Args[0] := 10;
    Args[1] := 20;
    Result := Method.Invoke(MyInstance, Args);
    Writeln(Result.AsInteger);
  end;
end;
22

인터페이스 심층

인터페이스 선언 및 구현

인터페이스는 구현 없이 계약 정의. GUID(선택 사항이지만 권장)는 'as' 캐스트와 Supports()를 가능하게. TInterfacedObject는 참조 카운팅 제공. 모든 인터페이스 메서드는 구현되어야 함('abstract' 탈출 없음). 인터페이스의 속성은 접근자 메서드 필요.

delphi
type
  IShape = interface
    ['{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}']
    function GetArea: Double;
    function GetPerimeter: Double;
    procedure Draw;
    property Color: TColor read FColor write SetColor;
  end;

  TCircle = class(TInterfacedObject, IShape)
  private
    FRadius: Double;
    FColor: TColor;
    procedure SetColor(Value: TColor);
  public
    constructor Create(ARadius: Double);
    function GetArea: Double;
    function GetPerimeter: Double;
    procedure Draw;
  end;

참조 카운팅과 메모리

인터페이스 참조는 참조 카운트됨. 마지막 인터페이스 참조가 범위를 벗어나면 객체가 해제됨. 동일한 인스턴스에 객체와 인터페이스 참조를 절대 혼합하지 마세요 — 인터페이스 참조 카운팅이 객체 포인터가 여전히 가리키는 동안 해제할 것. 하나의 소유권 모델을 선택.

delphi
var
  Shape: IShape;
begin
  Shape := TCircle.Create(5.0);  // refcount = 1
  // ... use Shape ...
end;  // refcount drops to 0, object freed automatically

// Mixing interface and object references — DANGER:
var
  Obj: TCircle;
begin
  Obj := TCircle.Create(5.0);
  Shape := Obj;       // refcount = 1
  Shape := nil;       // refcount = 0, Obj freed!
  Obj.GetArea;        // AV — dangling pointer
end;

인터페이스 상속과 다중 인터페이스

인터페이스는 여러 부모에서 상속 가능. 클래스는 여러 인터페이스를 구현 가능. 메서드 해결 절(method = interface.method)은 여러 인터페이스가 동일한 메서드를 선언할 때 충돌 해결. 런타임에 인터페이스를 쿼리하려면 'as'나 Supports() 사용.

delphi
type
  IReadable = interface
    function Read: string;
  end;

  IWritable = interface
    procedure Write(const S: string);
  end;

  IStream = interface(IReadable, IWritable)
    procedure Flush;
  end;

  TFileStream = class(TInterfacedObject, IStream, IReadable, IWritable)
    // must implement all methods from all interfaces
  end;

Supports와 as 캐스트

Supports()는 객체가 인터페이스를 구현하는지 확인 — 부울 반환, 선택적으로 인터페이스 반환. 'as' 캐스트는 동일하게 수행하지만 실패 시 EInvalidCast 발생. Supports()는 객체와 인터페이스 참조 모두에서 작동. 인터페이스에 GUID가 필요.

delphi
uses
  System.SysUtils, System.TypInfo;

var
  Obj: TObject;
  Shape: IShape;
begin
  Obj := TCircle.Create(5.0);
  try
    if Supports(Obj, IShape, Shape) then
      Writeln(Shape.GetArea:0:2);

    // 'as' cast — raises if not supported
    Shape := Obj as IShape;

    // type info
    if Supports(Obj, IShape) then
      Writeln('Obj supports IShape');
  finally
    Obj.Free;  // object reference — must free manually
  end;
end;

의존성 주입 패턴

종속성을 인터페이스로 전달 — 모킹, 구현 교체, 테스트 가능성 활성화. 클래스는 구체적 타입이 아닌 추상(ILogger)에 의존. 이것이 Spring4D 같은 DI 컨테이너의 기초. 인터페이스 소유권은 서비스가 참조를 보유하는 동안 로거가 살아있음을 의미.

delphi
type
  ILogger = interface
    procedure Log(const Msg: string);
  end;

  TOrderService = class
  private
    FLogger: ILogger;
  public
    constructor Create(ALogger: ILogger);
    procedure ProcessOrder(OrderId: Integer);
  end;

constructor TOrderService.Create(ALogger: ILogger);
begin
  FLogger := ALogger;  // injected dependency
end;

procedure TOrderService.ProcessOrder(OrderId: Integer);
begin
  FLogger.Log('Processing order ' + OrderId.ToString);
end;
23

메모리 관리 고급

Try-finally 패턴

try-finally에서 할당을 항상 Free와 짝지으세요. 여러 리소스의 경우 finally 블록 중첩. FreeAndNil(Free 대신)은 변수도 정리 — use-after-free 감지에 유용. Free는 nil에서 안전 — Assigned를 먼저 확인할 필요 없음.

delphi
var
  List: TObjectList;
  Stream: TFileStream;
begin
  List := TObjectList.Create;
  try
    Stream := TFileStream.Create('data.bin', fmOpenRead);
    try
      // ... use Stream ...
    finally
      Stream.Free;
    end;
  finally
    List.Free;
  end;
end;

인터페이스 기반 소유권

TInterfacedObject + 인터페이스 참조 = 자동 정리. 인터페이스가 범위를 벗어나면 소멸자 실행. 이것은 Delphi의 RAII — try-finally 보일러플레이트 없이 보장된 정리를 위해 리소스를 인터페이스 객체로 래핑.

delphi
type
  TTempFile = class(TInterfacedObject)
  private
    FName: string;
  public
    constructor Create(const AName: string);
    destructor Destroy; override;
  end;

destructor TTempFile.Destroy;
begin
  if FileExists(FName) then
    DeleteFile(FName);
  inherited;
end;

// usage:
var
  Temp: TTempFile;
begin
  Temp := TTempFile.Create('tmp.txt');
  // ... use file ...
end;  // Temp freed automatically (refcount)

약한 참조

약한 참조는 참조 순환을 끊음. [Weak] 없이 서로에 대한 인터페이스 참조를 보유한 두 객체는 절대 해제되지 않음(순환). TComponent는 약한 참조를 위한 내장 FreeNotification 메커니즘 보유. [Weak] 속성은 RTTI 필요, 인터페이스와 클래스 필드에서 작동.

delphi
type
  [Weak]
  FParent: TComponent;  // weak reference — no refcount increment

  // or for interfaces:
  [Weak]
  FLogger: ILogger;

// TComponent uses notification-based weak refs:
type
  TChild = class(TComponent)
  private
    FParent: TComponent;
  public
    property Parent: TComponent read FParent write FParent;
  end;

레코드 vs 객체

레코드는 값 타입(스택, 할당 시 복사) — 메모리 관리 불필요. 클래스는 참조 타입(힙, 해제 필요). 작은 불변 데이터(점, 날짜, 돈)에는 레코드 사용. 다형적이거나 큰 객체에는 클래스 사용. 현대 Delphi에서 레코드는 메서드와 연산자를 가질 수 있음.

delphi
type
  TPoint = record  // value type — stack allocated
    X, Y: Double;
    function Distance: Double;
  end;

  TPointObj = class  // reference type — heap allocated
    X, Y: Double;
    function Distance: Double;
  end;

var
  P1, P2: TPoint;
  O1, O2: TPointObj;
begin
  P1 := P2;  // copies values
  O1 := O2;  // copies reference (both point to same object)
end;

메모리 누수 감지

ReportMemoryLeaksOnShutdown은 종료 시 누수된 객체 목록 대화상자 표시. FastMM(기본 메모리 관리자)은 누수, 이중 해제, use-after-free 감지. 프로덕션의 경우 파일로 누수 로그. 개발 중에 정기적으로 누수 검사 실행 — 누수가 도입될 때 수정하기 더 쉬움.

delphi
uses
  System.ReportMemoryLeaksOnShutdown;

begin
  ReportMemoryLeaksOnShutdown := True;
  // ... your code ...
  // on app exit, leak report shown if any unfreed objects
end;

// FastMM4 (built into modern Delphi):
//   - Detects leaks with call stack
//   - Reports type and count of leaked objects
//   - Use FullDebugMode for detailed diagnostics

// Manual check:
var
  StartMem: Integer;
begin
  StartMem := AllocMemSize;
  // ... code under test ...
  if AllocMemSize > StartMem then
    Writeln('Memory leak detected');
end;
24

FireMonkey (FMX)

크로스 플랫폼 폼 기본

FMX 폼은 크로스 플랫폼(Windows, macOS, iOS, Android, Linux). 동일한 코드, 다른 네이티브 렌더러. Vcl.* 대신 FMX.* 유닛 사용. 컨트롤은 벡터 기반(완벽하게 스케일). 스타일이 테마를 대체 — 시각적 외관은 데이터 기반.

delphi
unit MainForm;

interface

uses
  System.SysUtils, System.Types, FMX.Forms, FMX.Controls,
  FMX.Controls.Presentation, FMX.Edit, FMX.Buttons;

type
  TFormMain = class(TForm)
    EditName: TEdit;
    ButtonSubmit: TSpeedButton;
    procedure ButtonSubmitClick(Sender: TObject);
  private
    FName: string;
  public
    property Name: string read FName;
  end;

var
  FormMain: TFormMain;

implementation

procedure TFormMain.ButtonSubmitClick(Sender: TObject);
begin
  FName := EditName.Text;
  Close;
end;

end.

레이아웃과 정렬

FMX는 Align(Client, Top, Bottom, Left, Right, None)과 Margins/Padding으로 레이아웃. TFlowLayout은 CSS flexbox처럼 자식 정렬. TGridLayout은 그리드 생성. 해상도 독립적 스케일링을 위해 TScaleBox 사용. 레이아웃은 자체가 컨트롤 — 중첩 가능.

delphi
// Layout types: TLayout, TFlowLayout, TGridLayout, TScrollBox
var
  Layout: TFlowLayout;
  Btn: TButton;
begin
  Layout := TFlowLayout.Create(Self);
  Layout.Parent := Self;
  Layout.Align := TAlignLayout.Client;
  Layout.FlowDirection := TFlowDirection.LeftToRight;
  Layout.Justify := TJustifyMode.SpaceBetween;

  for var I := 1 to 5 do
  begin
    Btn := TButton.Create(Self);
    Btn.Parent := Layout;
    Btn.Text := 'Button ' + I.ToString;
    Btn.Margins.Rect := RectF(5, 5, 5, 5);
  end;
end;

스타일과 스타일링

스타일은 .fsf나 .style 파일에 저장된 시각적 리소스(브러시, 글꼴, 효과) 모음. StyleLookup은 컨트롤에 명명된 스타일 선택. TStyleManager는 런타임에 전역 스타일 전환. FMX 스타일은 벡터 — 모든 DPI로 스케일. Style Designer로 스타일을 시각적으로 편집.

delphi
// Load a custom style
begin
  TStyleManager.LoadFromFile('Dark.fsf');
  TStyleManager.TrySetStyleFromResource('DarkStyle');
end;

// Apply style to a single control:
Button1.StyleLookup := 'cornerbutton';

// Read style in code:
var
  StyleObj: TFmxObject;
begin
  StyleObj := TStyleManager.ActiveStyle(Self).FindStyleResource('buttonstyle');
end;

// LiveBindings designer for visual data binding
// Tools > LiveBindings Designer

효과와 애니메이션

효과(Glow, Shadow, Blur, Reflection)는 컨트롤에 부모가 있는 비시각적 컴포넌트. 애니메이션(TFloatAnimation, TColorAnimation, TPathAnimation)은 시간에 따라 속성 애니메이션. Parent를 대상 컨트롤로 설정. Trigger/Start로 시작. 모두 GPU 가속 — 모든 플랫폼에서 부드러움.

delphi
uses
  FMX.Effects, FMX.Ani;

var
  Glow: TGlowEffect;
  Ani: TFloatAnimation;
begin
  // Glow effect on a button
  Glow := TGlowEffect.Create(Button1);
  Glow.Parent := Button1;
  Glow.GlowColor := TAlphaColors.Blue;
  Glow.Enabled := True;

  // Animate opacity
  Ani := TFloatAnimation.Create(Button1);
  Ani.Parent := Button1;
  Ani.PropertyName := 'Opacity';
  Ani.StartValue := 0;
  Ani.EndValue := 1;
  Ani.Duration := 0.5;
  Ani.Start;
end;

플랫폼 서비스

플랫폼 서비스는 OS 특정 기능 추상화. SupportsPlatformService로 쿼리 — 지원되지 않는 플랫폼에서 false 반환. 사용 전에 항상 확인. 일반 서비스: 클립보드, 대화상자, 가상 키보드, 장치 정보, 화면. 이 패턴은 {$IFDEF} 블록 없이 코드를 크로스 플랫폼으로 유지.

delphi
uses
  FMX.Platform;

var
  ScreenSvc: IFMXScreenService;
  Size: TPoint;
begin
  if TPlatformServices.Current.SupportsPlatformService(
    IFMXScreenService, IInterface(ScreenSvc)) then
  begin
    Size := ScreenSvc.GetScreenSize;
    Writeln(Size.X.ToString, 'x', Size.Y.ToString);
  end;
end;

// Other services:
//   IFMXClipboardService
//   IFMXDialogService (async message boxes)
//   IFMXVirtualKeyboardService
//   IFMXDeviceService
25

데이터베이스 (FireDAC)

연결 설정

TFDConnection은 중앙 FireDAC 객체. DriverName(SQLite, MSSQL, MySQL, PostgreSQL, Oracle 등)과 Params 설정. 연결 정의는 재사용을 위해 .ini 파일에 저장 가능. 해제 전에 항상 Connected := False 설정. 연결 풀링을 위해 TFDManager 사용.

delphi
uses
  FireDAC.Comp.Client, FireDAC.Stan.Def;

var
  FDConn: TFDConnection;
begin
  FDConn := TFDConnection.Create(nil);
  try
    FDConn.DriverName := 'SQLite';
    FDConn.Params.Database := 'app.db';
    FDConn.Params.Add('Encrypt=AES-256');
    FDConn.Params.Password := 'secret';
    FDConn.Connected := True;

    // or use a connection definition file:
    // FDConn.ConnectionDefName := 'MySQLite';
  finally
    FDConn.Free;
  end;
end;

쿼리 실행

SELECT에는 Open(커서 반환), INSERT/UPDATE/DELETE에는 execSQL(영향받은 행 반환) 사용. 항상 매개변수 사용 — SQL에 값을 연결하지 마세요(인젝션 위험). ParamByName은 대소문자 구분 없음. FieldByName은 이름으로 열 접근. Eof/Next로 행 반복.

delphi
var
  Query: TFDQuery;
begin
  Query := TFDQuery.Create(nil);
  try
    Query.Connection := FDConn;

    // parameterized query (prevents SQL injection)
    Query.SQL.Text := 'SELECT * FROM users WHERE age > :min_age';
    Query.ParamByName('min_age').AsInteger := 18;
    Query.Open;

    while not Query.Eof do
    begin
      Writeln(Query.FieldByName('name').AsString);
      Query.Next;
    end;

    // execute non-query (INSERT/UPDATE/DELETE)
    Query.SQL.Text := 'INSERT INTO users (name, age) VALUES (:n, :a)';
    Query.ParamByName('n').AsString := 'Alice';
    Query.ParamByName('a').AsInteger := 30;
    Query.ExecSQL;
  finally
    Query.Free;
  end;
end;

트랜잭션

StartTransaction/Commit/Rollback으로 원자적 작업 감쌈. 명령문이 실패하면 Rollback이 모든 변경 사항 취소. 중첩 트랜잭션은 저장점 사용(부분 롤백). 오류 후 롤백 후 전파하기 위해 항상 try-except-raise로 감싸세요. 트랜잭션 없이 각 명령문은 자동 커밋.

delphi
FDConn.StartTransaction;
try
  Query.SQL.Text := 'UPDATE accounts SET balance = balance - 100 WHERE id = 1';
  Query.ExecSQL;
  Query.SQL.Text := 'UPDATE accounts SET balance = balance + 100 WHERE id = 2';
  Query.ExecSQL;

  FDConn.Commit;
except
  FDConn.Rollback;
  raise;
end;

// nested transactions via savepoints:
FDConn.StartTransaction;
try
  // ... work ...
  FDConn.StartTransaction;  // savepoint
  try
    // ... more work ...
    FDConn.Commit;
  except
    FDConn.Rollback;  // rolls back to savepoint
  end;
finally
  FDConn.Commit;
end;

TFDTable과 라이브 데이터

TFDTable은 테이블에 대한 라이브, 편집 가능한 커서. Edit/Post는 현재 행 수정. Append/Post는 삽입. Delete는 현재 행 제거. 변경 사항은 데이터베이스로 직접 전달. 정렬에는 IndexFieldNames 사용. 복잡한 쿼리에는 TFDQuery 대신 사용.

delphi
var
  Table: TFDTable;
begin
  Table := TFDTable.Create(nil);
  try
    Table.Connection := FDConn;
    Table.TableName := 'users';
    Table.IndexFieldNames := 'name';  // ORDER BY
    Table.Open;  // SELECT * FROM users

    // edit current row
    Table.Edit;
    Table.FieldByName('age').AsInteger := 31;
    Table.Post;

    // insert new row
    Table.Append;
    Table.FieldByName('name').AsString := 'Bob';
    Table.FieldByName('age').AsInteger := 25;
    Table.Post;

    // delete current row
    Table.Delete;
  finally
    Table.Free;
  end;
end;

배치 업데이트와 캐시 모드

CachedUpdates 모드는 변경 사항을 메모리에 버퍼링 — ApplyUpdates로 한 번에 적용. 행별 업데이트보다 대량 작업에 빠름. CancelUpdates는 버퍼 폐기. Status는 행별 변경 타입 표시. 연결이 끊긴 시나리오와 왕복 감소에 유용.

delphi
Query.CachedUpdates := True;
Query.Open;

// make many changes locally
while not Query.Eof do
begin
  Query.Edit;
  Query.FieldByName('status').AsString := 'processed';
  Query.Post;
  Query.Next;
end;

// apply all changes in one transaction
FDConn.StartTransaction;
try
  Query.ApplyUpdates;
  FDConn.Commit;
except
  FDConn.Rollback;
  Query.CancelUpdates;
  raise;
end;

// inspect change log:
Query.Status;  // TUpdateStatus (usModified, usInserted, usDeleted)
26

REST & HTTP

TRESTClient 기본

TRESTClient는 기본 URL 보유. TRESTRequest는 요청 빌드(메서드, 리소스, 매개변수). TRESTResponse는 결과 보유. URL 세그먼트({id})는 AddUrlSegment로 대체. StatusCode/Content는 HTTP 응답 제공. 생성 역순으로 해제.

delphi
uses
  REST.Client, REST.Types;

var
  Client: TRESTClient;
  Request: TRESTRequest;
  Response: TRESTResponse;
begin
  Client := TRESTClient.Create('https://api.example.com');
  Request := TRESTRequest.Create(Client);
  Response := TRESTResponse.Create(Client);
  try
    Request.Resource := '/users/{id}';
    Request.Method := TRESTRequestMethod.rmGET;
    Request.Params.AddUrlSegment('id', '42');
    Request.Params.AddItem('fields', 'name,email', TRESTRequestParameterKind.pkGETorPOST);

    Request.Execute;

    if Response.StatusCode = 200 then
      Writeln(Response.Content);
  finally
    Response.Free;
    Request.Free;
    Client.Free;
  end;
end;

JSON 파싱

System.JSON은 TJSONObject, TJSONArray, TJSONValue 제공. ParseJSONValue는 문자열 파싱(TJSONValue 반환 — 필요에 따라 캐스트). GetValue<T>는 타입화된 값 읽기. AddPair/AddElement로 JSON 빌드. 모든 JSON 객체는 해제 필요 — 부모가 소유할 때만 참조 카운트됨.

delphi
uses
  System.JSON;

var
  JSON: TJSONObject;
  Arr: TJSONArray;
  Item: TJSONObject;
  I: Integer;
begin
  // parse
  JSON := TJSONObject.ParseJSONValue('{"name":"Alice","age":30}') as TJSONObject;
  try
    Writeln(JSON.GetValue<string>('name'));
    Writeln(JSON.GetValue<Integer>('age'));
  finally
    JSON.Free;
  end;

  // build
  JSON := TJSONObject.Create;
  try
    JSON.AddPair('name', 'Bob');
    JSON.AddPair('scores', TJSONArray.Create(90, 85, 92));
  finally
    JSON.Free;
  end;
end;

datasnap으로 REST 서버

DataSnap은 Delphi 메서드를 REST 엔드포인트로 자동 노출. 메서드 이름이 URL 세그먼트가 됨. 매개변수는 URL 세그먼트나 POST 본문에 매핑. TJSONObject/TJSONArray는 표준 반환 타입. HTTP 동사 지정을 위해 [httppost] 같은 속성 적용. 기본 클래스로 TDSServerModule 사용.

delphi
// ServerContainerUnit1.pas
type
  TServerMethods1 = class(TDSServerModule)
    function GetUsers: TJSONArray;
    function GetUser(id: Integer): TJSONObject;
    [httppost] function CreateUser(Data: TJSONObject): TJSONObject;
  end;

function TServerMethods1.GetUsers: TJSONArray;
begin
  Result := TJSONArray.Create;
  // ... populate from DB ...
end;

// Access via URL:
//   GET  http://localhost:8080/datasnap/rest/TServerMethods1/GetUsers
//   GET  http://localhost:8080/datasnap/rest/TServerMethods1/GetUser/42
//   POST http://localhost:8080/datasnap/rest/TServerMethods1/CreateUser

저수준 제어를 위한 Indy HTTP

TIdHTTP(Indy)는 HTTP에 대한 완전한 제어 제공 — 헤더, 쿠키, 리다이렉트, 타임아웃. TRESTClient보다 장황하지만 더 유연. HTTPS의 경우 SSL IOHandler 할당(TIdSSLIOHandlerSocketOpenSSL). 프로덕션을 위해 ReadTimeout/ConnectTimeout 설정. Indy는 동기식 — 비동기를 위해 TThread로 감싸세요.

delphi
uses
  IdHTTP, IdGlobal;

var
  Http: TIdHTTP;
  Response: string;
  PostData: TStringStream;
begin
  Http := TIdHTTP.Create(nil);
  try
    Http.Request.ContentType := 'application/json';
    Http.Request.CustomHeaders.AddValue('Authorization', 'Bearer token123');

    // GET
    Response := Http.Get('https://api.example.com/users');

    // POST
    PostData := TStringStream.Create('{"name":"Alice"}', TEncoding.UTF8);
    try
      Response := Http.Post('https://api.example.com/users', PostData);
    finally
      PostData.Free;
    end;

    Writeln(Http.ResponseCode);  // 200, 404, etc.
  finally
    Http.Free;
  end;
end;

작업이 있는 비동기 HTTP

UI 스레드 차단을 피하기 위해 REST 호출을 TTask.Run으로 감싸세요. TThread.Queue(비동기)나 TThread.Synchronize(동기)로 UI 업데이트를 다시 마샬링. 객체 수명 주기 주의 — 요청이 작업보다 오래 살아야 함. 내장 비동기 지원을 위해 TRESTRequest.ExecuteAsync 고려.

delphi
uses
  System.Threading, REST.Client;

var
  Request: TRESTRequest;
begin
  Request := TRESTRequest.Create(nil);
  try
    Request.Client := TRESTClient.Create('https://api.example.com/users');
    Request.Client.Owner := Request;

    TTask.Run(
      procedure
      begin
        Request.Execute;  // background thread

        TThread.Queue(nil,
          procedure
          begin
            // update UI on main thread
            Memo1.Lines.Text := Request.Response.Content;
          end);
      end);
  finally
    // don't free Request here — task may still be running
  end;
end;
27

멀티스레딩 (병렬)

TThread 기본

TThread를 하위 클래스화하고 Execute override. Create(False)는 즉시 시작; Create(True)는 .Start 필요. FreeOnTerminate := True는 자동 해제 — 이러한 스레드에서 Free 호출 금지. 우아한 종료를 위해 Terminated 주기적으로 확인. Execute에서 UI 만지지 마세요 — Synchronize/Queue 사용.

delphi
type
  TWorker = class(TThread)
  protected
    procedure Execute; override;
  public
    constructor Create;
  end;

procedure TWorker.Execute;
var
  I: Integer;
begin
  for I := 1 to 100 do
  begin
    if Terminated then Exit;
    // ... work ...
    Sleep(50);
  end;
end;

constructor TWorker.Create;
begin
  inherited Create(False);  // False = start immediately
  FreeOnTerminate := True;  // auto-free on completion
end;

TTask와 future

System.Threading의 ITask/IFuture<T>는 TThread보다 고수준. Future는 타입화된 값 반환 — .Value는 결과가 준비될 때까지 차단. 작업은 참조 카운트됨(수동 Free 없음). TTask.WaitForAll / WaitForAny로 여러 작업 조정. 원시 TThread보다 사용하기 쉬움.

delphi
uses
  System.Threading;

var
  Task: ITask;
  Future: IFuture<string>;
begin
  // fire-and-forget task
  Task := TTask.Create(
    procedure
    begin
      // ... background work ...
    end);
  Task.Start;

  // future — returns a value
  Future := TTask.Future<string>(
    function: string
    begin
      Sleep(1000);
      Result := 'computed value';
    end);

  // ... do other work ...

  Writeln(Future.Value);  // blocks until ready
end;

병렬 for 루프

TParallel.For는 CPU 코어에 걸쳐 루프 반복을 병렬로 실행. 공유 상태를 동기화해야 함(TCriticalSection 또는 TInterlocked 사용). 반복 순서는 비결정적. break/continue를 위해 TLoopState 사용. CPU 바운드 작업에 더 빠름; 오버헤드로 인해 사소한 반복에는 더 느림.

delphi
uses
  System.Threading, System.SyncObjs;

var
  Total: Integer;
  Lock: TCriticalSection;
  I: Integer;
begin
  Lock := TCriticalSection.Create;
  try
    TParallel.For(1, 1000,
      procedure(I: Integer)
      begin
        Lock.Enter;
        try
          Total := Total + ComputeExpensive(I);
        finally
          Lock.Leave;
        end;
      end);
  finally
    Lock.Free;
  end;
end;

동기화 기본 요소

TCriticalSection: 상호 배제(한 번에 하나의 스레드). TEvent: 스레드 간 신호(SetEvent/WaitFor). 수동 리셋이 있는 TEvent는 Reset까지 신호 유지. TInterlocked.Increment는 원자적이고 단순 카운터에 크리티컬 섹션보다 빠름. TMonitor(TObject에 내장)는 또 다른 옵션.

delphi
uses
  System.SyncObjs;

var
  Lock: TCriticalSection;
  Event: TEvent;
  Count: Integer;
begin
  Lock := TCriticalSection.Create;
  Event := TEvent.Create(nil, True, False, '');
  try
    TThread.CreateAnonymousThread(
      procedure
      begin
        Lock.Enter;
        try
          Inc(Count);
        finally
          Lock.Leave;
        end;
        Event.SetEvent;  // signal completion
      end).Start;

    Event.WaitFor(INFINITE);  // wait for signal
  finally
    Lock.Free;
    Event.Free;
  end;
end;

TThread.Queue와 Synchronize

UI 컨트롤은 메인 스레드에서만 만질 수 있음. Synchronize는 작업자가 메인 스레드가 익명 메서드를 실행할 때까지 차단 — 신중하게 사용(직렬화 발생). Queue는 게시 후 계속 — fire-and-forget UI 업데이트에 선호. 현재 스레드를 사용하려면 thread 인수로 nil 전달.

delphi
// From a worker thread, update UI safely:

// Synchronous — blocks worker until main thread runs the code
TThread.Synchronize(nil,
  procedure
  begin
    Label1.Caption := 'Done';
  end);

// Asynchronous — posts to main thread queue, doesn't block
TThread.Queue(nil,
  procedure
  begin
    Label1.Caption := 'Progress: 50%';
  end);

// TThread.Queue is preferred for non-critical updates
// Synchronize for cases where you need the result before continuing
28

패키지 & 컴포넌트

패키지 프로젝트 기본

패키지(.bpl)는 Delphi 메타데이터가 있는 DLL — 앱 간 코드 공유. 'requires'는 종속성 나열. 'contains'는 이 패키지의 유닛 나열. 설계 시간 패키지는 IDE에 컴포넌트 설치; 런타임 패키지는 앱과 함께 배포. IDE 블로트를 줄이기 위해 설계/런타임 분리.

delphi
// MyPackage.dpk
package MyPackage;

{$R *.res}
{$ALIGN 8}
{$ASSERTIONS ON}
{$DESIGNONLY MyDesignUnits}  // design-time only
{$RUNONLY MyRuntimeUnits}    // runtime only

requires
  rtl,
  vcl,
  System.Generics.Collections;

contains
  MyUnit1 in 'MyUnit1.pas',
  MyUnit2 in 'MyUnit2.pas',
  MyComponent in 'MyComponent.pas';

end.

// Build configurations:
//   - Build (debug)
//   - Release
//   - Design-time (installs into IDE)
//   - Runtime (deployed with app)

사용자 정의 컴포넌트 골격

가장 가까운 기존 클래스에서 파생(TCustomLabel은 published 속성 없이 레이블 제공). 노출하려는 속성만 재게시. Register 프로시저는 컴포넌트를 IDE 팔레트에 추가. 'default'는 초기 값 설정(생성자와 일치해야 함). Register를 설계 시간 패키지에 배치.

delphi
unit MyLabel;

interface

uses
  Vcl.Controls, Vcl.Graphics, Vcl.StdCtrls;

type
  TMyLabel = class(TCustomLabel)
  private
    FHighlightColor: TColor;
    procedure SetHighlightColor(Value: TColor);
  protected
    procedure Paint; override;
  public
    constructor Create(AOwner: TComponent); override;
  published
    property HighlightColor: TColor
      read FHighlightColor write SetHighlightColor default clYellow;
    property Caption;
    property Font;
    property OnClick;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('MyPalette', [TMyLabel]);
end;

컴포넌트 속성과 편집기

TComponent는 비시각적 컴포넌트의 기본. 하위 객체 소유(FItems) — 생성자에서 생성, 소멸자에서 해제. TStrings 속성은 내장 문자열 편집기 획득. RegisterPropertyEditor는 특정 속성에 대해 객체 검사기 사용자 정의. 스트리밍이 필요한 중첩 객체에는 TPersistent 사용.

delphi
type
  TMyComponent = class(TComponent)
  private
    FItems: TStringList;
    function GetItems: TStrings;
    procedure SetItems(Value: TStrings);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Items: TStrings read GetItems write SetItems;
  end;

constructor TMyComponent.Create(AOwner: TComponent);
begin
  inherited;
  FItems := TStringList.Create;
end;

destructor TMyComponent.Destroy;
begin
  FItems.Free;
  inherited;
end;

// Property editor for rich editing in Object Inspector:
//   RegisterPropertyEditor(TypeInfo(TStrings), TMyComponent, 'Items',
//     TStringListProperty);

이벤트와 메서드 포인터

이벤트 타입은 'of object'가 있는 프로시저 타입 — 객체 참조와 메서드 포인터 모두 보유. 호출 전에 항상 Assigned() 확인 — nil 이벤트는 AV 발생. Do* 메서드(DoChange, DoClick)는 이벤트를 발생시키는 보호된 디스패처. 하위 클래스는 이벤트를 가로채기 위해 Do*를 override할 수 있음.

delphi
type
  TMyEvent = procedure(Sender: TObject; Value: Integer) of object;

  TMyComponent = class(TComponent)
  private
    FOnChange: TMyEvent;
  protected
    procedure DoChange(Value: Integer);
  published
    property OnChange: TMyEvent read FOnChange write FOnChange;
  end;

procedure TMyComponent.DoChange(Value: Integer);
begin
  if Assigned(FOnChange) then
    FOnChange(Self, Value);
end;

// 'of object' makes it a method pointer — must be assigned to a method
// (e.g., Form1.Button1Click). Always check Assigned before calling.

스트리밍과 영속성

TPersistent는 스트리밍과 Assign 활성화. Published 속성은 자동으로 DFM 파일에 저장. 객체 간 복사를 지원하기 위해 Assign override. DefineProperties는 non-published 데이터를 스트림에 추가. WriteComponent/ReadComponent는 모든 TStream으로 직렬화. 이것이 폼이 상태를 유지하는 방식.

delphi
type
  TMySettings = class(TPersistent)
  private
    FTimeout: Integer;
    FTitle: string;
  published
    property Timeout: Integer read FTimeout write FTimeout default 30;
    property Title: string read FTitle write FTitle;
  end;

// TPersistent enables streaming (DFM, RTTI):
//   - Inherits from TPersistent (gives Assign)
//   - Published properties are streamed
//   - Override AssignTo/Assign for custom copy

// Save to DFM automatically:
//   - Component owned by a form is streamed
//   - Sub-properties (TPersistent) are nested in DFM
//   - Use DefineProperties for non-standard data

// Manual streaming:
var
  Stream: TFileStream;
begin
  Stream := TFileStream.Create('settings.bin', fmCreate);
  try
    Stream.WriteComponent(MyComponent);
  finally
    Stream.Free;
  end;
end;

Was this helpful?