How to simulate bit-fields in Delphi records?

PatrickvL picture PatrickvL · Nov 11, 2008 · Viewed 11k times · Source

I would like to declare a record in Delphi that contains the same layout as it has in C.

For those interested : This record is part of a union in the Windows OS's LDT_ENTRY record. (I need to use this record in Delphi because I'm working on an Xbox emulator in Delphi - see project Dxbx on sourceforge).

Anyway, the record in question is defined as:

    struct
    {
        DWORD   BaseMid : 8;
        DWORD   Type : 5;
        DWORD   Dpl : 2;
        DWORD   Pres : 1;
        DWORD   LimitHi : 4;
        DWORD   Sys : 1;
        DWORD   Reserved_0 : 1;
        DWORD   Default_Big : 1;
        DWORD   Granularity : 1;
        DWORD   BaseHi : 8;
    }
    Bits;

As far as I know, there are no bit-fields possible in Delphi. I did try this:

 Bits = record
      BaseMid: Byte; // 8 bits
      _Type: 0..31; // 5 bits
      Dpl: 0..3; // 2 bits
      Pres: Boolean; // 1 bit
      LimitHi: 0..15; // 4 bits
      Sys: Boolean; // 1 bit
      Reserved_0: Boolean; // 1 bit
      Default_Big: Boolean; // 1 bit
      Granularity: Boolean; // 1 bit
      BaseHi: Byte; // 8 bits
  end;

But alas: it's size becomes 10 bytes, instead of the expected 4. I would like to know how I should declare the record, so that I get a record with the same layout, the same size, and the same members. Preferrably without loads of getter/setters.

TIA.

Answer

PatrickvL picture PatrickvL · Nov 11, 2008

Thanks everyone!

Based on this information, I reduced this to :

RBits = record
public
  BaseMid: BYTE;
private
  Flags: WORD;
  function GetBits(const aIndex: Integer): Integer;
  procedure SetBits(const aIndex: Integer; const aValue: Integer);
public
  BaseHi: BYTE;
  property _Type: Integer index $0005 read GetBits write SetBits; // 5 bits at offset 0
  property Dpl: Integer index $0502 read GetBits write SetBits; // 2 bits at offset 5
  property Pres: Integer index $0701 read GetBits write SetBits; // 1 bit at offset 7
  property LimitHi: Integer index $0804 read GetBits write SetBits; // 4 bits at offset 8
  property Sys: Integer index $0C01 read GetBits write SetBits; // 1 bit at offset 12
  property Reserved_0: Integer index $0D01 read GetBits write SetBits; // 1 bit at offset 13
  property Default_Big: Integer index $0E01 read GetBits write SetBits; // 1 bit at offset 14
  property Granularity: Integer index $0F01 read GetBits write SetBits; // 1 bit at offset 15
end;

The index is encoded as follows : (BitOffset shl 8) + NrBits. Where 1<=NrBits<=32 and 0<=BitOffset<=31

Now, I can get and set these bits as follows :

{$OPTIMIZATION ON}
{$OVERFLOWCHECKS OFF}
function RBits.GetBits(const aIndex: Integer): Integer;
var
  Offset: Integer;
  NrBits: Integer;
  Mask: Integer;
begin
  NrBits := aIndex and $FF;
  Offset := aIndex shr 8;

  Mask := ((1 shl NrBits) - 1);

  Result := (Flags shr Offset) and Mask;
end;

procedure RBits.SetBits(const aIndex: Integer; const aValue: Integer);
var
  Offset: Integer;
  NrBits: Integer;
  Mask: Integer;
begin
  NrBits := aIndex and $FF;
  Offset := aIndex shr 8;

  Mask := ((1 shl NrBits) - 1);
  Assert(aValue <= Mask);

  Flags := (Flags and (not (Mask shl Offset))) or (aValue shl Offset);
end;

Pretty nifty, don't you think?!?!

PS: Rudy Velthuis now included a revised version of this in his excellent "Pitfalls of converting"-article.