Joint Endeavor of Delphi Innovators of Windows Programming

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;

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;

_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;

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);

…

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;

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;

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;

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;

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;

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

);

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′);

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.

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.

ACE
ACL
Administrator
callback
COM
Conversion
CreateProcess
DACL
Delphi
DidYouKnow
DLL
documentation
Download
elevation
file
Handle
header
HowTo
interface
JWA
JWSCL
KillProcess
Laptop
manifest
permission
Privilege
Process
ProcessExplorer
Recommendation
Registry
RunEl
Russinovich
security
Service
Session
Setup
Sid
TerminateProcess
Thread
Token
UAC
user
Vista
Window
Windows

- Faster TMultiReadExclusiveWriteSynchronizer? on Slim Read/Write Lock (SRWLock) – the fast alternative to TMultiReadExclusiveWriteSynchronizer
- Christian Wimmer on Restrict access to process
- Carlos on Restrict access to process
- Christian Wimmer on You don’t need an Admin manifest to get Admin privileges.
- Chris on You don’t need an Admin manifest to get Admin privileges.

## 10 Responses

Fernando Madruga

01|May|2008 1Nice trick but… isn’t xor a bit dangerous? What if you set the same bit twice without clearing it first? I may have missed something as I skimmed most of the code but it sure looks like an *and* with a proper bitmask would be safer…

Fernando Madruga

01|May|2008 2I said it wrong on my previous comment: the problem happens if you *clear* the same bit twice without setting it in between…

rweijnen

02|May|2008 3@Fernando: You are right!

Correction (also corrected in the article):

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;

Oliver

02|May|2008 4… what the heck, I’ll stick with C/C++

Christian Wimmer

02|May|2008 5traitor

Remko

03|May|2008 6Are you getting lazy Oliver?

Oliver

03|May|2008 7What do you mean “getting”? It’s one of the prerequisites I demand of everyone who installs my DDKWizard … only natural to be lazy myself. In fact I think programmers need to be lazy to some extent.

Remko

08|May|2008 8Seems like I left this part out:

type

TMaxBitSet = Set of Byte;

PMaxBitSet = ^TMaxBitSet;

Patrick van Logchem

04|Nov|2009 9Another elegant way to work with bit-fields, is to hide them behind indexed properties. I discovered this trick a while ago (I believe I’m the first to come up with this technique) and wrote about it on the following page :

http://stackoverflow.com/questions/282019/how-to-simulate-bit-fields-in-delphi-records

Good luck with it. Cheers!

Arioch

16|Nov|2011 10And now XE2 has at last “inline” keyword told to work.

Using sets is obvious and natural for this.

But it was not until getters in records that multi-bit fields could be treated with comfort.

## Leave a reply

You must be logged in to post a comment.