Translating flags in API calls, part 1

OK, so we have all seen the way that flags are passed to API calls. Take the ShowWindow function. In Delphi it is declared as this:

function ShowWindow(hWnd: HWND; nCmdShow: Integer): BOOL; stdcall;

Now, there is nothing wrong with the declaration – it will work, is well documented and is of course not seen by most Delphi developers, since the calls are all wrapped up in the Delphi VCL.

Thing is, that the nCmdShow parameter is declared as in integer (even in the Windows API docs) but functionally, it acts as an enumerated type. That is to say, it has a defined set of values, and only those values are permissible. What’s more, those values are sequential from 0 to 10.

Sounds familiar? Sound a little like an enumerated type, now doesn’t it? You can look up the imports used in Delphi in Windows.pas, but here is an alternative version:

function ShowWindow(hWnd: HWND; nCmdShow: TShowWindowValues): BOOL; stdcall;

and TShowWindowValues declared as:

type
  {$Z4}
  TShowWindowValues = (SW_HIDE = 0,
    SW_SHOWNORMAL = 1, SW_NORMAL = SW_SHOWNORMAL,
    SW_SHOWMINIMIZED = 2, SW_SHOWMAXIMIZED = 3,
    SW_MAXIMIZE = 3, SW_SHOWNOACTIVATE = 4,
    SW_SHOW = 5, SW_MINIMIZE = 6,
    SW_SHOWMINNOACTIVE = 7, SW_SHOWNA = 8,
    SW_RESTORE = 9, SW_SHOWDEFAULT = 10,
    SW_MAX = SW_SHOWDEFAULT);
  {$Z1}

OK, so a few things to note:

  1. The {$Z4} directive is necessary because the API type for the parameter is a four-byte integer and Delphi’s enumerated types are one byte in size by default.
  2. I specified the numeric values even though they are not specifically needed in this case (everything is sequential). This of course won’t work at all in Delphi 3 and earlier.
  3. Two of the values are assigned a value held by one of their siblings. So even when two values are equivalent, you can still use an enum.
  4. Given that the function is now type safe, it is even possible to change the names of the identifiers – fix the case and remove the underscores – if you feel like it, without confusing anyone.

How about flags that are combined?

These work differently and the values are not sequential. The idea being that the values can be mixed using the or operator. Consider the DrawEdge function:

function DrawEdge(hdc: HDC; var qrc: TRect; edge: UINT; grfFlags: UINT): BOOL; stdcall;

The edge parameter takes a combination of four values. These values are declared as:

BDR_RAISEDOUTER = 1;
BDR_SUNKENOUTER = 2;
BDR_RAISEDINNER = 4;
BDR_SUNKENINNER = 8;

Powers of two are used, so that when these values are added together (or combined with or) you would retain all the values. Because these are never meant to be used on their own, but rather in combinations, I like using sets:

TBorder = (BDR_RAISEDOUTER, BDR_SUNKENOUTER, BDR_RAISEDINNER, BDR_SUNKENINNER);
TBorders = set of TBorder;

At first glance, this may seem like a disaster. The values of the three enum values are 0, 1, 2 and 3 respectively, which is not as they are declared in Windows.pas.

But the way that set types work is identical to the way flags do. While the value of BDR_RAISEDINNER may be 2, the value of [BDR_RAISEDINNER] is in fact 4. Two to the power of two is four, remember?

Some readers may know already that the declarations above don’t work. The concept is fine, but the type sizes are all wrong. TBorders is one byte in size, not four. And {$Z4} does nothing to change it.

You see, set values are stored as bit flags, but the size is determined by the range of the ordinal type. I have four values in TBorder, so TBorders only uses the four least significant bits of a byte. So how do we get TBorders to be four bytes in size? Easy, we add a value that should be stored in bit 31:

TBorder = (BDR_RAISEDOUTER, BDR_SUNKENOUTER, BDR_RAISEDINNER, BDR_SUNKENINNER, BDR_Range = 31);
TBorders = set of TBorder;

The extra BDR_Range value stretches TBorders to four bytes and we have a type safe version of DrawEdge.

There is more to come, but I think that will wait for the next post.

First, there is something that I want to clear up. Suppose you read this and you like the concept, now what? ShowWindow and DrawEdge both have their definitions in Windows.pas and I am in no way suggesting that these must be changed or that we should all import them separately.

The one (secondary) point here is that the declarations in Windows.pas are almost certainly generated by some tool. If Nick and the boys were to become real inspired they could change the tool to generate type safe overloads. There are probably some good reasons why this was not done from the start (you couldn’t assign numeric values to enum elements before Delphi 4 is one that comes to mind), but it is still a good idea.

More generally though, there are many APIs that Delphi doesn’t have imports for. Lots of programmers import these and some even share large repositories of them. When you find the need to do an API import, there is nothing stopping you from making it type safe.

In part 2, I’ll discuss what to do when your API call has a conditional set of values for a parameter – check out DrawFrameControl if you want a preview.

Update: Part 2 has now been published.

Advertisements

1 Comment »

  1. […] As promised, this is part 2 of my article on translating flags in API calls. If you haven’t read part 1 yet, please do so […]

RSS feed for comments on this post · TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: