delphirecordbit-fields

How to simulate bit-fields in Delphi records?


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 API 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. Preferably without loads of getters/setters.


Solution

  • 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.