터보 파스칼 5.5부터인가 지원되던 object라는 키워드가 있습니다.  구조체에 메소드를 추가할 수 있는 타입이라고 생각하시면 됩니다.  클래스는 객체를 힙에 생성하지만, object로 생성된 객체는 스택에 생성되는 등, class와 유사하면서도 다른 면들이 많습니다.


최근에 델파이의 record에도 메소드를 추가할 수 있기 때문에 크게 필요가 없어진 키워드이긴 하지만, 가끔 사용하고 싶은 충동일 생길 경우가 있습니다.  바로 상속을 사용하고 싶을 때 입니다.  구조체는 상속이 되지 않지만 object는 상속이 가능합니다.


조심해야 할 문제는 바로 object는 구조체와 달리 packing(packed)을 할 수 없다는 점 입니다.  [소스 1]에서 보시면 packing하지 않을 경우 4바이트 단위로 정렬되는 것을 보실 수가 있습니다.  즉, 4로 나눠떨어지도록 메모리를 더 사용하게 됩니다.  델파이 내에서 통신하는 경우는 상관없지만, 다른 언어와 통신하며 데이터를 주고 받을 때는 이 부분을 유의하셔야 합니다.


[소스 1]

type
  TPacketRecord = record
    Code : byte;
    Data : integer;
  end;

  TPacketObject = object
    Code : byte;
    Data : integer;
  end;

  TPacketPackedRecord = packed record
    Code : byte;
    Data : integer;
  end;

  TPacketPackedObject = object
    Member : TPacketPackedRecord;
  end;

{$R *.dfm}

procedure TfmMain.FormCreate(Sender: TObject);
begin
  moMsg.Lines.Add(Format('Size of TPacketRecord = %d', [SizeOf(TPacketRecord)]));  // 8 bytes
  moMsg.Lines.Add(Format('Size of TPacketObject = %d', [SizeOf(TPacketObject)]));  // 8 bytes
  moMsg.Lines.Add(Format('Size of TPacketPackedRecord = %d', [SizeOf(TPacketPackedRecord)]));  // 5 bytes
  moMsg.Lines.Add(Format('Size of TPacketPackedObject = %d', [SizeOf(TPacketPackedObject)]));  // 5 bytes
end;






Posted by 류종택
통신 등에서는 조그만 데이터의 차이가 계속 쌓이면서 큰 차이를 만드는 경우가 종종 있습니다.  이런 경우 사용 할 수 있는 간단한 트릭을 소개하도록 하겠습니다.  

우선 아래와 같이 불린을 구조체로 묶어서 보내는 경우를 생각해보겠습니다.

[소스1]
type
  TPacket = packed record
    Data1 : boolean;
    Data2 : boolean;
    Data3 : boolean;
  end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Caption := IntToStr(SizeOf(TPacket));
end;
버턴을 클릭해보면 당연히 3바이트 크기임을 알 수가 있습니다.  하지만, true/false의 상태를 가지고 있기 때문에 1 바이트에 8개의 불린을 처리할 수가 있기 때문에 아래와 같이 표현 할 수 있습니다.

[소스 2]
type
  TPacket = packed record
    Data : byte;
  end;

function GetData(APacket:TPacket; AIndex:integer):boolean;
var
  BitMask : byte;
begin
  BitMask := 1;
  BitMask := BitMask shl AIndex;
  Result := (APacket.Data and BitMask) = BitMask;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Caption := IntToStr(SizeOf(TPacket));
end;
이제 구조체의 크기는 1바이트로 줄었고, 최대 8개까지 불린 변수를 사용할 수 있게되었습니다.  SetData 함수는 일단 미뤄두기로 하겠습니다.

이제 이 넘을 좀 더 세련되게 바꿔 보도록 하겠습니다.  구조체에 메소드를 추가하는 것이 델파이 어느 버전에서부터인지 기억지 잘 안나는 군요.  오래된 버전을 사용하시는 분들은 "packed record"를 "object"로 바꿔서 사용하시기 바랍니다.  제가 도스 시절에 주로 사용하던 방법입니다.  구조체에 메소드를 추가한 타입이라고 생각하시면 됩니다.  다만, [소스 3]의 예제대로 사용하기에는 몇 가지 제약 사항이 있습니다.

[소스 3]
type
  TPacket = packed record
  private
    FData : byte;
    function GetBooleans(AIndex: integer): boolean;
    procedure SetBooleans(AIndex: integer; const Value: boolean);
  public
    property Booleans[AIndex:integer] : boolean read GetBooleans write SetBooleans;
  end;

{ TPacket }

function TPacket.GetBooleans(AIndex: integer): boolean;
var
  BitMask : byte;
begin
  BitMask := 1;
  BitMask := BitMask shl AIndex;
  Result := (FData and BitMask) = BitMask;
end;

procedure TPacket.SetBooleans(AIndex: integer; const Value: boolean);
var
  BitMask : byte;
begin
  BitMask := 1;
  BitMask := BitMask shl AIndex;

  if Value then FData := FData or BitMask
  else FData := FData and (not BitMask);
end;
이제 데이터를 보관하는 변수를 캡슐화해서 안전하게 보관하였고, property를 통해서 필요한 인터페이스만 제공하도록 하였습니다.  그리고, 여전히 구조체의 크기는 1 바이트이기 때문에 해당 구조체를 소켓 등으로 주고 받는데에도 문제가 없습니다.

저는 표현 범위가 byte, word, integer 등의 제한 범위 내에서만 사용 되면서 데이터의 크기를 줄여야 할 경우에 가끔 사용하는 방식입니다.  예를 들어 32 개수 미만의 열거형과 512 미만의 정수를 사용한다면, word 안에 집어 넣고도 몇 비트가 남게 될 것 입니다.

[소스 4]는 제가 실무에서 사용했던 예 입니다.  word의 맨 앞의 비트는 불린 변수로 사용하고, 나머지는 정수 형태의 변수로 사용하고 있습니다.

[소스 4]
type
  THeader = packed record
  private
    FIndex : word;
    function GetHasKey: boolean;
    procedure SetHasKey(const Value: boolean);
    procedure SetFrameIndex(const Value: word);
    function GetFrameIndex: word;
  public
    property HasKey : boolean read GetHasKey write SetHasKey;
    property FrameIndex : word read GetFrameIndex write SetFrameIndex;
  end;

function THeader.GetFrameIndex: word;
begin
  Result := FIndex and $7FFF;
end;

function THeader.GetHasKey: boolean;
begin
  Result := (FIndex and $8000) = $8000;
end;

procedure THeader.SetFrameIndex(const Value: word);
begin
  FIndex := (FIndex and $8000) or Value;
end;

procedure THeader.SetHasKey(const Value: boolean);
begin
  if value then
    FIndex := FIndex or $8000
  else
    FIndex := FIndex and $7FFF;
end;


 



Posted by 류종택

unit TimeUtils;

interface

uses
  Classes, SysUtils;

function Date2Str(ATime:integer):string;
function Time2Str(ATime:integer):string;

function IncTime(ATime,AValue:integer):integer;
function DecTime(ATime,AValue:integer):integer;

// 1/1000 초 단위를 10진표시 시간으로
function ms2Time(ms:int64):integer;
function Time2ms(ATime:integer):int64;

implementation

function Date2Str(ATime:integer):string;
begin
  Result := Format('%8d', [ATime]);
  Result := StringReplace(Result, ' ', '0', [rfReplaceAll]);
end;

function Time2Str(ATime:integer):string;
begin
  Result := Format('%6d', [ATime]);
  Result := StringReplace(Result, ' ', '0', [rfReplaceAll]);
end;

function IncTime(ATime,AValue:integer):integer;
var
  msTime, msValue : int64;
begin
  msTime  := Time2ms(ATime);
  msValue := Time2ms(AValue);

  Result := ms2Time(msTime + msValue);
end;

function DecTime(ATime,AValue:integer):integer;
var
  msTime, msValue : int64;
begin
  msTime  := Time2ms(ATime);
  msValue := Time2ms(AValue);

  Result := ms2Time(msTime - msValue);
end;

function ms2Time(ms:int64):integer;
var
  H, M, S : integer;
begin
  ms := ms div 1000;

  H := ms div (60 * 60);

  M := (ms div 60) mod 60;

  S := ms mod 60;

  Result := H * 10000 + M * 100 + S;
end;

function Time2ms(ATime:integer):int64;
var
  H, M, S : integer;
begin
  H := ATime div 10000;
  M := (ATime div 100) mod 100;
  S := (ATime mod 100);

  Result := (H*60*60 + M*60 + S) * 1000;
end;

begin
  {$IFDEF DEBUG}
    Assert(IncTime(120000, 100) = 120100, 'Error: IncTime');
    Assert(IncTime(120059,   1) = 120100, 'Error: IncTime');
    Assert(IncTime(115900, 100) = 120000, 'Error: IncTime');
    Assert(IncTime(115959,   1) = 120000, 'Error: IncTime');

    Assert(DecTime(120100, 100) = 120000, 'Error: IncTime');
    Assert(DecTime(120100,   1) = 120059, 'Error: IncTime');
    Assert(DecTime(115900, 100) = 115800, 'Error: IncTime');
    Assert(DecTime(120000,   1) = 115959, 'Error: IncTime');

    Assert(ms2Time(60*1000) = 000100, 'Error: ms2Time');
    Assert(ms2Time(60*1000 * 61) = 010100, 'Error: ms2Time');

    Assert(Time2ms(000100) = 60*1000, 'Error: Time2ms');
    Assert(Time2ms(010100) = 60*1000 * 61, 'Error: Time2ms');
  {$ENDIF}
end.

위의 코드처럼 델파이  각 유닛 하단에 테스트 케이스를 작성하면서 TDD를 흉내 낼 수 있습니다.  이런 형태의 장점은 TDD 프로젝트를 따로 관리 안해도 되고, private 메소드들에 대한 테스트 케이스를 작성하는데도 문제가 없습니다.

디버그 모드에서만 동작하도록 하면서, 테스트에 필요한 객체 및 자료를 생성하고 해제하는 코드를 집어넣어서 사용하시면 됩니다.  어찌보면 델파이는 TDD 내장형 언어 ^^*


Posted by 류종택
어떤 분의 요청으로 자료를 올립니다.  예전에 강의 했던 자료이며, 원래는 네트워크 게임 만들기 강좌를 진행하려고 했었지만, 중단되었습니다.  서버는 델파이로, 클라이언트는 플랙스로 만들어져 있습니다.

동영상 자료는 너무 커서 올리지 못했습니다.  인터넷을 검색해 보시면 자료가 있을 것 입니다.



Posted by 류종택
OAuth가 적용된 델파이 용 트위터 전송 프로그램입니다.
원이아빠님이 델마당 자료실에 올린 것을 한글이 되도록 수정한 것 입니다.

원래 소스가 제 스타일에 안맞아서,
한글 문제 이외의 부분도 살짝 수정하였습니다.

인증하기 위해서 웹브로우져를 오픈하게 되며,
PIN 코드 받기가 완료되면 창을 닫기만 하면 됩니다.

일단, 제 PC에서 테스트 해보니 잘되더군요.
델파이 2010에서 컴파일 되었습니다.

질문 안받습니다 ^^;
알아서 잘 쓰세요.


Posted by 류종택
예전 터보 파스칼 때부터 사용가능한 문법이 하나 있는데, 아시는 분들이 별로 없어 보여서 생각난 김에 설명합니다.  가끔 메소드의 interface 부분을 수정 할 때마다, implementation 쪽의 구현도 같이 변경해주어야 하는 불편함이 있는데, 이를 피할 수 있는 방법입니다.

unit Unit2;

interface

type
  TMyClass = class
  private
  public
    procedure DoSomething(A,B:integer);
  end;

implementation

{ TMyClass }

procedure TMyClass.DoSomething;
begin

end;

end.

16: 라인에보시면 9: 라인에서 선언한 것과 달리 메소드의 파라메터 부분이 없습니다.  이렇게 표현해도 전혀 문제없이 잘 컴파일 되고 실행됩니다.  즉, interface 부분이 아무리 달라져도 구현 쪽에서 변경 할 필요가 없다는 뜻 입니다.

이상 터보 파스칼 이후부터 저조차 안쓰는 쓸 때없는 팁이었습니다 ^^;


Posted by 류종택
RAD 2006 이상부터 투게더가 포함되어 있습니다.
투게더에는 "QA Audit" 라는 기능이 있는데, 이것으로 코드의 문제점을 찾아 낼 수 있습니다.
(툴이 지적한 것이 "모두 문제다" 라고 할 수는 없지만)

우선 [그림 1]처럼 빈 프로젝트를 만들어 봤습니다.

[그림 1] VCL Forms Application 생성

이후, [그림 1]의 빨간 동그라미 부분을 클릭합니다.  투게더를 통해서 모델링을 시작하겠다는 뜻 입니다.  이때 프로젝트가 저장되어 있지 않으면 저장 할 것인지를 물어보게 됩니다.  저장하세요!

다음으로는 [그림 2]처럼 원하는 유닛에서 오른쪽 마우스를 클릭하시고, "QA Audits" 메뉴를 실행합니다.  [그림 2]와 달리 저는 Unit2를 지정했습니다.

[그림 2] QA Audits 메뉴 실행

QA Audits 메뉴를 실행하면, [그림 3]과 같이 다이얼로그 창이 나타납니다.  원하는 옵션을 선택하시고 Start 버턴을 클릭 합니다.

[그림 3] QA Audits Option Dialog

이제 [그림 4]와 같이 결과 화면을 볼 수 있습니다.  그런데 설명에 성의가 없습니다 ㅡ.ㅡ  이때는 해당 사항에서 오른쪽 마우스를 클릭하시고, Show Description 을 실행합니다.  영어로 길게 설명이 나옵니다.  난감합니다 ㅋㅋ

[그림 4] 최종 결과 화면

Public 또는 Protected 메소드가 없다고 하네요.  클래스에 그런게 없다면, 왜 만들었는 지 궁굼해 할 만하기도 합니다.  ^^*

Posted by 류종택
배포 방법을 변경합니다.
아래 게시물을 참고하시기 바랍니다.



Posted by 류종택

class.xml

classc.xml

classd.xml

dest.xml

for.xml

forr.xml

forrb.xml

fors.xml


제 편의대로 수정해서 쓰는 넘들입니다.
제 자신을 위해서 저장해둡니다 ^^*

\Program Files (x86)\Embarcadero\RAD Studio\9.0\ObjRepos\en\Code_Templates\Delphi

desk.xml과 forb.xml을 제외한 넘들은 원래의 파일에 덮어 써야 합니다.

주위!  제 스타일이 맘에 안들 수 있으니, 미리 백업하세요.



Posted by 류종택


티스토리 툴바