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. typedef struct _BITFIELDSTRUCTURE {
  3.     DWORD dwValue1;
  4.     ULONG BitValue1: 1;
  5.     ULONG BitValue2: 1;
  6.     ULONG BitValue3: 1;
  7.     ULONG BitValue4: 1;
  8. } BITFIELDSTRUCTURE, * BITFIELDSTRUCTURE;
  9.  

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. type
  3.   _BITFIELDSTRUCTURE = record
  4.   dwValue1: DWORD;
  5.   strict private
  6.     BitField: DWORD;
  7.     function GetBitValue1: Boolean;
  8.     function GetBitValue2: Boolean;
  9.     function GetBitValue3: Boolean;
  10.     function GetBitValue4: Boolean;
  11.     procedure SetBitValue1(const Value: Boolean);
  12.     procedure SetBitValue2(const Value: Boolean);
  13.     procedure SetBitValue3(const Value: Boolean);
  14.     procedure SetBitValue4(const Value: Boolean);
  15.   public
  16.     property BitValue1: Boolean read GetBitValue1 write SetBitValue1;
  17.     property BitValue2: Boolean read GetBitValue2 write SetBitValue2;
  18.     property BitValue3: Boolean read GetBitValue3 write SetBitValue3;
  19.     property BitValue4: Boolean read GetBitValue4 write SetBitValue4;
  20.   end;
  21.   TBitFieldStructure = _BITFIELDSTRUCTURE;
  22.   PBitFieldStructure = ^_BITFIELDSTRUCTURE;
  23.  

Code completion shows that the record has one DWORD Value and 4 Boolean Values which is just what we want!
CodeCompletion

Offcourse we need to implement the Getters and Setters:

  1.  
  2. function _BITFIELDSTRUCTURE.GetBitValue1;
  3. begin
  4.   Result := BitField and 1 = 1;
  5. end;
  6.  
  7. function _BITFIELDSTRUCTURE.GetBitValue2;
  8. begin
  9.   Result := BitField and 2 = 2;
  10. end;
  11.  
  12. function _BITFIELDSTRUCTURE.GetBitValue3;
  13. begin
  14.   Result := BitField and 4 = 4;
  15. end;
  16.  
  17. function _BITFIELDSTRUCTURE.GetBitValue4;
  18. begin
  19.   Result := BitField and 8 = 8;
  20. end;
  21.  
  22. procedure _BITFIELDSTRUCTURE.SetBitValue1(const Value: Boolean);
  23. begin
  24.   if Value then BitField := BitField or 1 else BitField := BitField and (not 1);
  25. end;
  26.  
  27. procedure _BITFIELDSTRUCTURE.SetBitValue2(const Value: Boolean);
  28. begin
  29.   if Value then BitField := BitField or 2 else BitField := BitField and (not 2);
  30. end;
  31.  
  32. procedure _BITFIELDSTRUCTURE.SetBitValue3(const Value: Boolean);
  33. begin
  34.   if Value then BitField := BitField or 4 else BitField := BitField and (not 4);
  35. end;
  36.  
  37. procedure _BITFIELDSTRUCTURE.SetBitValue4(const Value: Boolean);
  38. begin
  39.   if Value then BitField := BitField or 8 else BitField := BitField and (not 8);
  40. end;
  41.  

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.   public
  3.     constructor Create(const dummy: word);
  4.     …
  5.  
  6. implementation
  7.  
  8. constructor _BITFIELDSTRUCTURE.Create;  // Did you know that Delphi permits leaving out (const dummy: word) here?
  9. begin
  10.   ZeroMemory(@Self, SizeOf(Self));
  11. end;
  12.  
  13.  
  14. procedure TForm1.Button1Click(Sender: TObject);
  15. var
  16.   BitFieldStructure: TBitFieldStructure;
  17. begin
  18.   BitFieldStructure := TBitFieldStructure.Create(0);
  19.  

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.   _BITFIELDSTRUCT = record
  3.   dwValue1: DWORD;
  4.   BitField: Set Of (
  5.     BitValue1, BitValue2, BitValue3, BitValue4
  6.   );
  7.   end;
  8.   TBitFieldStruct = _BITFIELDSTRUCT;
  9.   PBitFieldStruct = ^_BITFIELDSTRUCT;
  10.  

We can use normal set operations to get and set bitvalues:

  1.  
  2. procedure TForm1.Button2Click(Sender: TObject);
  3. var
  4.   bValue: Boolean;
  5.   BitFieldStruct: TBitFieldStruct;
  6. begin
  7.   bValue := BitValue2 in BitFieldStruct.BitField;
  8.   BitFieldStruct.BitField := [BitValue1, BitValue3];
  9.   BitFieldStruct.BitField := BitFieldStruct.BitField - [BitValue3];
  10. end;            
  11.  

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. const
  3.   al32Bit=31;
  4.   al64bit=63;
  5.   al96bit=95;
  6.   al128bit=127;
  7.   al160bit=159;
  8.   al192bit=191;
  9.   al224bit=221;
  10.   al256bit=255;
  11.  

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.   _BITFIELDSTRUCT = record
  3.   dwValue1: DWORD;
  4.   BitField: Set Of (
  5.     BitValue1, BitValue2, BitValue3, BitValue4, al32Bit
  6.   );
  7.   end;
  8.   TBitFieldStruct = _BITFIELDSTRUCT;            
  9.   PBitFieldStruct = ^_BITFIELDSTRUCT;
  10.  

So I thought I had it figured out… until I came to this line in the C header file:

  1.  
  2. ULONG   SomeValue  : 1;
  3. ULONG   OtherValue : 1;
  4. ULONG   ColorDepth : 3;
  5.  

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.   BitField: Set Of (
  3.     SomeValue, OtherValue, ColorDepth1, ColorDepth2, ColorDepth3, al32Bit
  4.   );
  5.  

We need a helper function to retreive the numeric value of ColorDepth:

  1.  
  2. function ValueFromBitSet(var ABitSet; const StartBit: Byte;
  3.   const Count: Byte): Int64;
  4. var
  5.   MaxBitSet: TMaxBitSet;
  6.   i, BitValue: Integer;
  7. begin
  8.   // The result can contain max. 64 bit value, Raise if Count > 64
  9.   if Count > 64 then Raise EIntOverflow.Create(‘Count cannot exceed 64′);
  10.  
  11.   // A Delphi Set contains at most 256 bits. So we raise Exception is we exceed
  12.   if StartBit + Count > 255 then Raise
  13.     EIntOverflow.Create(‘Startbit + Count cannot exceed maximum set size (255)’);
  14.  
  15.   Result := 0;
  16.   BitValue := 1;
  17.  
  18.   // A Delphi Set Of can hold a maximum of 256 bits, since we do not know
  19.   // which size was passed to us we cast to 256 bits.
  20.   MaxBitSet := TMaxBitSet(ABitSet);
  21.   // Loop through the requested bits from end to start (Little Endian)
  22.   for i := StartBit+Count-1 downto StartBit do
  23.   begin
  24.  
  25.     // is the bit set?
  26.     if i in MaxBitSet then
  27.     begin
  28.       // Multiply with BitValue and add to result
  29.       Result := Result + BitValue;
  30.     end;
  31.  
  32.     // Multiply BitValue by 2
  33.     BitValue := BitValue shl 1;
  34.   end;
  35. end;
  36.  

The helper function is used like this:

  1.  
  2. Struct.BitFields := [OtherValue, ColorDepth1, ColorDepth3];
  3. WriteLn(Format(‘Value=%d’, [ValueFromBitSet(Struct.BitFields, Integer(ColorDepth1), 3)]));
  4. end.
  5.  

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.
Send post as PDF to www.pdf24.org
convert this post to pdf.