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:
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:
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:
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:
...
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:
_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:
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:
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:
_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:
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
BitField: Set Of (
SomeValue, OtherValue, ColorDepth1, ColorDepth2, ColorDepth3, al32Bit
);We need a helper function to retreive the numeric value of ColorDepth:
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:
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:
- A Delphi Set can contain at most 256 values.
- The ValueFromBitSet function returns an Int64, so values that do not fit in an Int64 cannot be returned.
- Values in a Set need a unique name.
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