ビットフィールドを操作する (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 = 
    record
      FlickActionCommandCode: 0..31//  5bit
      FlickDirection: 0..7;          //  3bit
      ControlModifier: 0..1;         //  1bit
      MenuModifier: 0..1;            //  1bit
      AltGRModifier: 0..1;           //  1bit
      WinModifier: 0..1;             //  1bit
      ShiftModifier: 0..1;           //  1bit
      Reserved: 0..2;                //  2bit
      OnInkingSurface: 0..1;         //  1bit
      ActionArgument: -32768..32767// 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..1of WORD);                                           //  - ワード配列
    2: (LoWord, HiWord: WORD);                                                  //  - Lo / Hi ワード
    3: (Bytes: array [0..3of BYTE);                                           //  - バイト配列
  end;

uses にuBitField を追加するだけで利用可能です。

TBitFieldRec でのビットフィールド操作

ビットフィールドを操作するメソッドが用意されているので、それを利用します。先程の FLICK_DATA ビットフィールド構造体はこのような構成になっています。

bit Field Type
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:


 BACK