통신 등에서는 조그만 데이터의 차이가 계속 쌓이면서 큰 차이를 만드는 경우가 종종 있습니다. 이런 경우 사용 할 수 있는 간단한 트릭을 소개하도록 하겠습니다.
우선 아래와 같이 불린을 구조체로 묶어서 보내는 경우를 생각해보겠습니다.
[소스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;