$theTitle=wp_title(" - ", false); if($theTitle != "") { ?>
About Virtualization, VDI, SBC, Application Compatibility and anything else I feel like
Recently I needed to convert a C header file to Delphi which contained bitfields. Let’s take a look at a sample structure that contains bitfields:
1 2 3 4 5 6 7 | typedef struct _BITFIELDSTRUCTURE { DWORD dwValue1; ULONG BitValue1: 1; ULONG BitValue2: 1; ULONG BitValue3: 1; ULONG BitValue4: 1; } BITFIELDSTRUCTURE, * BITFIELDSTRUCTURE; |
It means that there is a DWORD (Cardinal) dwValue1 followed by a bitfield with the size of a ULONG (32 bits). In this bitfield 4 values are defined (BitValue1..4) which are used as boolean’s because the value can offcourse be 0 or 1. Since Delphi doesn’t know a bitfield type the question is how to translate it. Usually it would mean that we simply treat the whole bitfield value as a ULONG and extract the required properties by applying a bitmask (shl/shr). Starting from BDS2006 we can define a record with propertes and use getters and setters. Using this technique we can present boolean values to the user:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | type _BITFIELDSTRUCTURE = record dwValue1: DWORD; strict private BitField: DWORD; function GetBitValue1: Boolean; function GetBitValue2: Boolean; function GetBitValue3: Boolean; function GetBitValue4: Boolean; procedure SetBitValue1(const Value: Boolean); procedure SetBitValue2(const Value: Boolean); procedure SetBitValue3(const Value: Boolean); procedure SetBitValue4(const Value: Boolean); public property BitValue1: Boolean read GetBitValue1 write SetBitValue1; property BitValue2: Boolean read GetBitValue2 write SetBitValue2; property BitValue3: Boolean read GetBitValue3 write SetBitValue3; property BitValue4: Boolean read GetBitValue4 write SetBitValue4; end; TBitFieldStructure = _BITFIELDSTRUCTURE; PBitFieldStructure = ^_BITFIELDSTRUCTURE; |
Code completion shows that the record has one DWORD Value and 4 Boolean Values which is just what we want!
Offcourse we need to implement the Getters and Setters:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | function _BITFIELDSTRUCTURE.GetBitValue1; begin Result := BitField and 1 = 1; end; function _BITFIELDSTRUCTURE.GetBitValue2; begin Result := BitField and 2 = 2; end; function _BITFIELDSTRUCTURE.GetBitValue3; begin Result := BitField and 4 = 4; end; function _BITFIELDSTRUCTURE.GetBitValue4; begin Result := BitField and 8 = 8; end; procedure _BITFIELDSTRUCTURE.SetBitValue1(const Value: Boolean); begin if Value then BitField := BitField or 1 else BitField := BitField and (not 1); end; procedure _BITFIELDSTRUCTURE.SetBitValue2(const Value: Boolean); begin if Value then BitField := BitField or 2 else BitField := BitField and (not 2); end; procedure _BITFIELDSTRUCTURE.SetBitValue3(const Value: Boolean); begin if Value then BitField := BitField or 4 else BitField := BitField and (not 4); end; procedure _BITFIELDSTRUCTURE.SetBitValue4(const Value: Boolean); begin if Value then BitField := BitField or 8 else BitField := BitField and (not 8); end; |
We can even add a constructor to it, this can be used to e.g. initialize the record (in the example below we fill with zeroes). Note that only a constructor with at least one argument can be used:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | ... public constructor Create(const dummy: word); ... implementation constructor _BITFIELDSTRUCTURE.Create; // Did you know that Delphi permits leaving out (const dummy: word) here? begin ZeroMemory(@Self, SizeOf(Self)); end; ... procedure TForm1.Button1Click(Sender: TObject); var BitFieldStructure: TBitFieldStructure; begin BitFieldStructure := TBitFieldStructure.Create(0); |
So why not use a class instead of record? The answer is that a class is just a pointer we can never pass this to a function, procedure or api call that expects a record. But if we want to support older Delphi versions, like Delphi 6 or Delphi 7 and even Delphi 2005, which are still used a lot we need to find another solution. I came up with (ab)using sets to emulate bitfields, we can do this because a set is actually a set of bits (limited to 256 bits). The example structure could look like this if we use sets:
1 2 3 4 5 6 7 8 | _BITFIELDSTRUCT = record dwValue1: DWORD; BitField: Set Of ( BitValue1, BitValue2, BitValue3, BitValue4 ); end; TBitFieldStruct = _BITFIELDSTRUCT; PBitFieldStruct = ^_BITFIELDSTRUCT; |
We can use normal set operations to get and set bitvalues:
1 2 3 4 5 6 7 8 9 | procedure TForm1.Button2Click(Sender: TObject); var bValue: Boolean; BitFieldStruct: TBitFieldStruct; begin bValue := BitValue2 in BitFieldStruct.BitField; BitFieldStruct.BitField := [BitValue1, BitValue3]; BitFieldStruct.BitField := BitFieldStruct.BitField - [BitValue3]; end; |
Settings like minimal enum size and record alignment are important because we need to asssure that te record size matches the C structure’s size (especially when using structures with a lot of bitfields. I choose to do this with a litte trick, first I declare some constants:
1 2 3 4 5 6 7 8 9 | const al32Bit=31; al64bit=63; al96bit=95; al128bit=127; al160bit=159; al192bit=191; al224bit=221; al256bit=255; |
We use these constants to force the correct size, in the example the bitfield was a ULONG which is 32 bits. We add the al32Bit constant to the bitfield:
1 2 3 4 5 6 7 8 | _BITFIELDSTRUCT = record dwValue1: DWORD; BitField: Set Of ( BitValue1, BitValue2, BitValue3, BitValue4, al32Bit ); end; TBitFieldStruct = _BITFIELDSTRUCT; PBitFieldStruct = ^_BITFIELDSTRUCT; |
So I thought I had it figured out… until I came to this line in the C header file:
1 2 3 | ULONG SomeValue : 1; ULONG OtherValue : 1; ULONG ColorDepth : 3; |
So we have a bitfield consisting off multiple bits! This gave me some headaches but I finally came up with the following approach
1 2 3 | BitField: Set Of ( SomeValue, OtherValue, ColorDepth1, ColorDepth2, ColorDepth3, al32Bit ); |
We need a helper function to retreive the numeric value of ColorDepth:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | function ValueFromBitSet(var ABitSet; const StartBit: Byte; const Count: Byte): Int64; var MaxBitSet: TMaxBitSet; i, BitValue: Integer; begin // The result can contain max. 64 bit value, Raise if Count > 64 if Count > 64 then Raise EIntOverflow.Create('Count cannot exceed 64'); // A Delphi Set contains at most 256 bits. So we raise Exception is we exceed if StartBit + Count > 255 then Raise EIntOverflow.Create('Startbit + Count cannot exceed maximum set size (255)'); Result := 0; BitValue := 1; // A Delphi Set Of can hold a maximum of 256 bits, since we do not know // which size was passed to us we cast to 256 bits. MaxBitSet := TMaxBitSet(ABitSet); // Loop through the requested bits from end to start (Little Endian) for i := StartBit+Count-1 downto StartBit do begin // is the bit set? if i in MaxBitSet then begin // Multiply with BitValue and add to result Result := Result + BitValue; end; // Multiply BitValue by 2 BitValue := BitValue shl 1; end; end; |
The helper function is used like this:
1 2 3 | Struct.BitFields := [OtherValue, ColorDepth1, ColorDepth3]; WriteLn(Format('Value=%d', [ValueFromBitSet(Struct.BitFields, Integer(ColorDepth1), 3)])); end. |
Some limitations remain, although I don’t think you are likely to encouter these:
5 Responses for "Working with bitfields in Delphi"
Seems like I left this part out:
type
TMaxBitSet = Set of Byte;
PMaxBitSet = ^TMaxBitSet;
Hi Remko, nice article!
Using sets is certainly a good solution (it does result in quite optimal code-generation, especially when you only need to handle single-bit values.
However, I don’t like the fact that using the values suddenly becomes a somewhat weird set-operation – I would rather approach them as properties.
For this (and support for bitfields with multiple bits in them), might I suggest you take a look at this :
http://stackoverflow.com/questions/282019/how-to-
simulate-bit-fields-in-delphi-records#282385
Cheers!
Patrick, I already looked at your solution in the NG (will reply there). Did you notice that I added a helper function for multiple bit fields: function ValueFromBitSet(var ABitSet; const StartBit: Byte;
const Count: Byte): Int64;
[…] that I use a set to emulate a bitfield (as described here). Below a usage example: var lpr: TlParamRecord; begin lpr.lParam := 0; […]
I use bits in a much simpler way;
something like this;
Unit BitOper;
Uses……
type…..
Var
Value : LongWord;
Bits : Array [1..32] of Longword;
X : Byte;
Procedure Bits_Map; {Initialize the bit values}
Begin
For X := 1 To 32 Do
Begin
If X = 1 Then Value := 1 Else Value := Value Shl 1;
Bits[X] := Value;
End;
End;
Procedure SetBit (Var V : LongWord; N:Byte;Onoff:Boolean);
Begin
Case OnOff Of
True : V := V Or Bits[N];
False : V := V And (Not Bits[N]);
End;
End;
Function GetBit (Var V:LongWord;N:Byte) : Boolean;
Begin
Result := V And Bits[N] = Bits[N];
End;
End.
So Simple
Leave a reply