ビットフィールドを操作する (Delphi 2006 以降...フル機能は 2009 以降)
序論
Delphi はビットフィールドの操作が苦手です。たとえば集合型を用意しておき、
type
{ TBit32 }
TBit32 = 0..31;
{ TBits32 }
TBits32 = set of TBit32;
const
bit0 = 0; bit16 = 16;
bit1 = 1; bit17 = 17;
bit2 = 2; bit18 = 18;
bit3 = 3; bit19 = 19;
bit4 = 4; bit20 = 20;
bit5 = 5; bit21 = 21;
bit6 = 6; bit22 = 22;
bit7 = 7; bit23 = 23;
bit8 = 8; bit24 = 24;
bit9 = 9; bit25 = 25;
bit10 = 10; bit26 = 26;
bit11 = 11; bit27 = 27;
bit12 = 12; bit28 = 28;
bit13 = 13; bit29 = 29;
bit14 = 14; bit30 = 30;
bit15 = 15; bit31 = 31;
|
集合型を DWORD でキャストして、
var
Value: DWORD;
Bits: TBits32;
begin
Bits := [bit3, bit1, bit0];
Value := DWORD(Bits);
ShowMessage(IntToHex(Value, 8));
end;
|
このように使う事は可能ですが、これはあくまでビット操作であり、ビットフィールドの操作ではありません。
type
TFLICK_DATA =
packed record
FlickActionCommandCode: set of 0..4; // 5bit
FlickDirection: set of 0..2; // 3bit
ControlModifier: set of 0..0; // 1bit
MenuModifier: set of 0..0; // 1bit
AltGRModifier: set of 0..0; // 1bit
WinModifier: set of 0..0; // 1bit
ShiftModifier: set of 0..0; // 1bit
Reserved: set of 0..1; // 2bit
OnInkingSurface: set of 0..0; // 1bit
ActionArgument: set of 0..15; // 16bit
end;
|
このように FLICK_DATA ビットフィールド構造体を定義してみましたが、SizeOf(TFLICK_DATA) は 4 を返しません。C++ のビットフィールドに似たような記述はできても、ビットフィールド操作には使えないという事です。
TBitFieldRec
Delphi でビットフィールド操作を簡単に行えるようにするための高度なレコード型である TBitFieldRec を用意してみました。
ペイロードは DWORD なので、32bit のビット / ビットフィールド操作ができます。
{ TBitFieldRec }
TBitFieldRec = packed record
// 型変換 (代入時)
class operator Implicit(a: DWORD): TBitFieldRec; // 暗黙の型変換: TBitFieldRec に DWORD を代入 (レコードが左辺)
class operator Implicit(a: TBitFieldRec): DWORD; // 暗黙の型変換: DWORD に TBitFieldRec を代入 (レコードが右辺)
// 算術演算子 (単項)
class operator Negative(a: TBitFieldRec): TBitFieldRec; // 符号反転: -
class operator Positive(a: TBitFieldRec): TBitFieldRec; // 符号恒等: +
// 算術演算子
class operator Add(a, b: TBitFieldRec): TBitFieldRec; // 加算: +
class operator Subtract(a, b: TBitFieldRec): TBitFieldRec; // 減算: -
class operator Multiply(a, b: TBitFieldRec): TBitFieldRec; // 乗算: *
class operator IntDivide(a, b: TBitFieldRec): TBitFieldRec; // 整数除算: div
class operator Modulus(a, b: TBitFieldRec): TBitFieldRec; // 剰余: mod
// 論理演算子
class operator LogicalNot(a: TBitFieldRec): TBitFieldRec; // 否定: not
class operator LogicalAnd(a, b: TBitFieldRec): Boolean; // 論理積: and
class operator LogicalOr(a, b: TBitFieldRec): Boolean; // 論理和: or
class operator LogicalXor(a, b: TBitFieldRec): Boolean; // 排他的論理和: xor
// 論理(ビット)演算子
class operator BitwiseAnd(a, b: TBitFieldRec): TBitFieldRec; // ビット and: and
class operator BitwiseOr(a, b: TBitFieldRec): TBitFieldRec; // ビット or: or
class operator BitwiseXor(a, b: TBitFieldRec): TBitFieldRec; // ビット xor: xor
class operator LeftShift(a, b: TBitFieldRec): TBitFieldRec; // ビット単位の左シフト: shl
class operator RightShift(a, b: TBitFieldRec): TBitFieldRec; // ビット単位の右シフト: shr
// 関係演算子
class operator Equal(a, b: TBitFieldRec): Boolean; // 等しい: =
class operator NotEqual(a, b: TBitFieldRec): Boolean; // 等しくない: <>
class operator LessThan(a, b: TBitFieldRec): Boolean; // より小さい: <
class operator GreaterThan(a, b: TBitFieldRec): Boolean; // より大きい: >
class operator LessThanOrEqual(a, b: TBitFieldRec): Boolean; // 以下: <=
class operator GreaterThanOrEqual(a, b: TBitFieldRec): Boolean; // 以上: >=
// インクリメント / デクリメント
class operator Inc(a: TBitFieldRec): TBitFieldRec; // インクリメント: Inc()
class operator Dec(a: TBitFieldRec): TBitFieldRec; // デクリメント: Dec()
{$IFDEF CONDITIONALEXPRESSIONS}
{$IF CompilerVersion >= 20.0}
// Include / Exclude
class operator Include(a: TBitFieldRec; b: TBits32): TBitFieldRec; // 含める: Include() (2009 or later)
class operator Exclude(a: TBitFieldRec; b: TBits32): TBitFieldRec; // 含めない: Exclude() (2009 or later)
// 集合演算子
class operator In(a: TBit32; b: TBitFieldRec): Boolean; // メンバかどうか: in (2009 or later)
{$IFEND}
{$ENDIF}
private
function GetBit(Index: TBit32): Boolean;
procedure SetBit(Index: TBit32; const aValue: Boolean);
public
function GetBitField(aStartBit: TBit32; aCount: TCnt32): DWORD; // ビットフィールドの値を取得
procedure SetBitField(aStartBit: TBit32; aCount: TCnt32; aValue: DWORD); // ビットフィールドへ値を設定
function ToString: string; // 10 進数文字列へ変換
function ToHexString(Digits: Integer = 1): string; // 16 進数文字列へ変換
function ToOctString(Digits: Integer = 1): string; // 8 進数文字列へ変換
function ToBinString(Digits: Integer = 1): string; // 2 進数文字列へ変換
property Bits[Index: TBit32]: Boolean read GetBit write SetBit; default; // ブール型ビット値 (bit0..bit31)
procedure SwapByte(A, B: TByte32); // 任意のバイトを入れ替える
procedure SwapWord; // 上位ワードと下位ワードを入れ替える
case BYTE of // ペイロード共用体
0: (Value: DWORD); // - DWORD
1: (Words: array [0..1] of WORD); // - ワード配列
2: (LoWord, HiWord: WORD); // - Lo / Hi ワード
3: (Bytes: array [0..3] of BYTE); // - バイト配列
end;
|
uses にuBitField を追加するだけで利用可能です。
TBitFieldRec でのビットフィールド操作
ビットフィールドを操作するメソッドが用意されているので、それを利用します。先程の FLICK_DATA ビットフィールド構造体はこのような構成になっています。
31 |
ActionArgument |
SmallInt |
30 |
29 |
28 |
27 |
26 |
25 |
24 |
23 |
22 |
21 |
20 |
19 |
18 |
17 |
16 |
15 |
OnInkingSurface |
Boolean |
14 |
Reserved |
SmallInt |
13 |
12 |
ShiftModifier |
Boolean |
11 |
WinModifier |
Boolean |
10 |
AltGRModifier |
Boolean |
9 |
MenuModifier |
Boolean |
8 |
ControlModifier |
Boolean |
7 |
FlickDirection |
Byte |
6 |
5 |
4 |
FlickActionCommandCode |
Byte |
3 |
2 |
1 |
0 |
FlickActionCommandCode ビットフィールドや FlickDirection ビットフィールドは以下のようにして取得できます。
var
BitField: TBitFieldRec;
FlickActionCommandCode: Byte;
FlickDirection: Byte;
begin
FlickActionCommandCode := BitField.GetBitField(bit4, 5); // bit4 から 5 bit
FlickDirection := BitField.GetBitField(bit7, 3); // bit7 から 3 bit
...
end;
|
逆に、ビットフィールドに値を設定する事もできます。
var
BitField: TBitFieldRec;
FlickDirection: Byte;
begin
FlickDirection := FLICKDIRECTION_DOWNRIGHT;
BitField.SetBitField(bit7, 3, FlickDirection);
...
end;
|
シフト演算せずに値を設定できます。このままだと記述が冗長になってしまうので、レコードヘルパーを用いてビットフィールド名でアクセスできるようにしておくと読みやすいコードになります。
TFlickDataRec = TBitFieldRec;
TFlickDataHelper = record helper for TFlickDataRec
private
function GetFlickActionCommandCode: Byte;
function GetFlickDirection: Byte;
procedure SetFlickActionCommandCode(const aValue: Byte);
procedure SetFlickDirection(const aValue: Byte);
public
property FlickActionCommandCode: Byte read GetFlickActionCommandCode write SetFlickActionCommandCode;
property FlickDirection: Byte read GetFlickDirection write SetFlickDirection;
end;
...
function TFlickDataHelper.GetFlickActionCommandCode: Byte;
begin
result := Self.GetBitField(bit4, 5);
end;
function TFlickDataHelper.GetFlickDirection: Byte;
begin
result := Self.GetBitField(bit7, 3);
end;
procedure TFlickDataHelper.SetFlickActionCommandCode(const aValue: Byte);
begin
Self.SetBitField(bit4, 5, aValue);
end;
procedure TFlickDataHelper.SetFlickDirection(const aValue: Byte);
begin
Self.SetBitField(bit7, 3, aValue);
end;
...
|
こうしておけば、
var
FlickData: TFlickDataRec;
begin
FlickData.FlickActionCommandCode := FLICKACTION_COMMANDCODE_NULL;
FlickData.FlickDirection := FLICKDIRECTION_DOWNLEFT;
ShowMessage(IntToStr(FlickData.FlickActionCommandCode));
ShowMessage(IntToStr(FlickData.FlickDirection));
...
end;
|
こんな感じで使えます。
TBitFieldRec での WORD 操作
GetBitField() / SetBitField() を使わなくとも上位ワードあるいは下位ワードへアクセスすることができます。
var
a: TBitFieldRec;
Value1, Value2, Value3: DWORD;
begin
a := $11112222;
Value1 := a.GetBitField(bit31, 16);
Value2 := a.HiWord;
Value3 := a.Words[1];
ShowMessage(Format('HIWORD: %.4x : %.4x : %.4x', [Value1, Value2, Value3]));
Value1 := a.GetBitField(bit15, 16);
Value2 := a.LoWord;
Value3 := a.Words[0];
ShowMessage(Format('LOWORD: %.4x : %.4x : %.4x', [Value1, Value2, Value3]));
end;
|
ペイロードは共用体 (レコードの可変部分) になっているので、ワード配列 (またはバイト配列) でもアクセスできます。
TBitFieldRec でのビット操作
Bits[] プロパティへアクセスする事で、ビット単位での操作が可能になります。
ar
a: TBitFieldRec;
begin
a := 2;
if a.Bits[bit1] then
ShowMessage('Bit1: ON')
else
ShowMessage('Bit1: OFF');
end;
|
Bits[] プロパティはブール型です。値をセットする事もできます。また、デフォルトプロパティなので、a[bit1] のように、Bits の記述を省略する事ができます。
Delphi 2009 以降だと、Include() / Exclude() によるビット操作も可能です。
var
a: TBitFieldRec;
begin
a := 0;
Include(a, [bit0]); // Include 0001B
Include(a, [bit1..bit3]); // Include 1110B
ShowMessage(a.ToBinString(32));
Exclude(a, [bit1, bit2]); // 1st Exclude 0110B
Exclude(a, [bit1, bit2]); // 2nd Exclude 0110B
ShowMessage(a.ToBinString(32));
end;
|
TBitFieldRec の演算
TBitFieldRec には演算子がオーバーロードされていますので、そのまま四則演算や論理 / ビット演算、比較演算を行うことができます。"a.Value := a.Value + 1;" のようにペイロードを指定して演算する必要はありません。
var
a, b: TBitFieldRec;
begin
a := $FFFF0000;
b := $0000FFFF;
a := a + b;
a := a and $00FFFFFF;
a := a shl 8;
ShowMessage(IntToHex(a, 8));
end;
|
Delphi 2009 以降だと、in 集合演算で、ビットが立っているかどうかを調べる事もできます。
var
a: TBitFieldRec;
begin
a := 2;
if (bit1 in a) then
ShowMessage('Bit1: ON')
else
ShowMessage('Bit1: OFF');
end;
|
Bits[] プロパティの場合と比べてみて下さい。
TBitFieldRec の文字列変換
ペイロードの値を 16 / 10 / 8 / 2 進数文字列へ変換する事ができます。
var
a: TBitFieldRec;
begin
a := $A5;
ShowMessage(a.ToString()); // 10 進数文字列
ShowMessage(a.ToHexString(8)); // 16 進数文字列 ( 8 桁で表示)
ShowMessage(a.ToOctString(11)); // 8 進数文字列 ( 11 桁で表示)
ShowMessage(a.ToBinString(32)); // 2 進数文字列 (32 桁で表示)
end;
|
ToHexString() と ToBinString() は、引数として最低表示桁数を指定する事ができます。
See Also: