Translating flags in API calls, part 2

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 now.

Sometimes, you get API calls that make real hefty use of flags. One such example is DrawFrameControl, which has one flag to specify the type of control to draw as well as a second flag to specify the style of that control. The values are all declared as DFC_ or DFCS_ constants and they may be freely combined. Only, not all combinations work together and a lot of developers combine the flags using the plus-operator instead of or, which certainly doesn’t help.

Following on from the last post, we can create several overloads, each of which contain only the list of values relevant to the type of control:

function DrawFrameControl(DC: HDC; const Rect: TRect;
  uType: TDrawFrameControlType; uState: TDrawFrameControlCaptionStyles): BOOL; stdcall; overload; external user32 name 'DrawFrameControl';
function DrawFrameControl(DC: HDC; const Rect: TRect;
  uType: TDrawFrameControlType; uState: TDrawFrameControlMenuStyles): BOOL; stdcall; overload; external user32 name 'DrawFrameControl';
function DrawFrameControl(DC: HDC; const Rect: TRect; uType: TDrawFrameControlType; uState: TDrawFrameControlScrollStyles): BOOL; stdcall; overload; external user32 name 'DrawFrameControl';
function DrawFrameControl(DC: HDC; const Rect: TRect; uType: TDrawFrameControlType; uState: TDrawFrameControlButtonStyles): BOOL; stdcall; overload; external user32 name 'DrawFrameControl';

Once you then specify your control in uType, the values you pass to uStyle will select the correct overloaded version of this function.

Of course, if it was that simple I wouldn’t have done a separate post about this. In the case of this function (and several others), the Windows API conspires against us.

First, we have the shared states. All your control types can have the following states:

DFCS_INACTIVE = $100;
DFCS_PUSHED = $200;
DFCS_CHECKED = $400;
DFCS_TRANSPARENT = $800;
DFCS_HOT = $1000;
DFCS_ADJUSTRECT = $2000;
DFCS_FLAT = $4000;
DFCS_MONO = $8000;

There is no really sensible way of combining these with your other control-specific states, other than to add these to each of your types. So the State type for button controls will look like this:

TDfcButtonStyle = (DFCSBUTTONRADIOIMAGE, DFCSBUTTONRADIOMASK,
  DFCSBUTTONRADIO, DFCSBUTTON3STATE, DFCSBUTTONPUSH,
  DFCSINACTIVE = 8, DFCSPUSHED,
  DFCSCHECKED, DFCSTRANSPARENT, DFCSHOT,
  DFCSADJUSTRECT, DFCSFLAT, DFCSMONO,
  DFCSBUTTONRANGE = 31);
TDfcButtonStyles = set of TDfcButtonStyle;

And you’d have something similar for all four types (buttons, captions, menus/popup menus and scroll bars). Once again, notice that DFCSINACTIVE gets a value of 8 instead of $100, because we are interested in the bit position.

The issue then of course becomes on of naming the values. You see, if you have DFCSFLAT in both TDfcButtonStyle and TDfcCaptionStyle the compiler will see them both and complain about the identifier being re-declared.

My favourite solution to this, is using the {$SCOPEDENUMS} compiler directive. This basically makes the Delphi compiler treat enumerated types in a way that is very similar to that of C# – the actual enumerated values are only in scope when you specify the type. I don’t like needing to think up unique two-character prefixes for each type anyhow, but in this case I find scoped enumerations to be invaluable.

Not only is TDfcButtonStyle.DFCSHOT now a distinct value from TDfcCaptionStyle.DFCSHOT, but I can in fact clean the names up to look more like it was thought up with mortals in mind:

TDfcButtonStyle = (ButtonRadioImage, ButtonRadioMask,
  ButtonRadio, Button3State, ButtonPush,
  Inactive = 8, Pushed, Checked, Transparent, Hot,
  AdjustRect, Flat, Mono, Range = 31);
TDfcButtonStyles = set of TDfcButtonStyle;

No doubt this technique will have lots of traditionalists up in arms, but I think it looks much cleaner and more readable.

The second issue is a little worse and in a twist of irony, is even worse when scoped enumerations are used.

You’ll notice there is no DFCBUTTONCHECKED or ButtonChecked in the examples above. That’s because this Windows constant has a value of zero. And Delphi set types don’t handle zero at all. You could pass [] for that parameter, but then it is no longer obvious that you are trying to draw a checkbox. Also, the compiler will have a torrid time distinguishing between the DrawFrameControl overloads.

I found two ways of getting this to work, but they both have the word hack scribbled all over them.

Firstly, you could add ButtonCheck to the above enumeration at some unused position, say 6. This works and is transparent to the client code, but the success of the technique depends entirely on the API function you’re calling. If it has an undocumented feature, or it changes in the next Windows version or it keels over when an unknown flag is passed in, you’re toast. Experience shows it to be mostly reliable, but it is still not to be fully trusted.

The second solution – the one that doesn’t like scoped enumerations – is to declare the missing value as a typed constant:

DFCSBUTTONCHECK: TDrawFrameControlButtonStyles = [];

This is future proof but has another issue – inconsistent use. Compare the calls to draw a checked radio button and a checked check box:

DrawFrameControlTS(PaintBox.Canvas.Handle, PaintRect,
  DFCBUTTON, DFCSBUTTONCHECK + [DFCSCHECKED]);
DrawFrameControlTS(PaintBox.Canvas.Handle, PaintRect,
  DFCBUTTON, [DFCSBUTTONRADIO, DFCSCHECKED]);

And no, I don’t have a magic trick that will make this work better.

So the take-home for this two-part article is this:

It is possible to create type-safe imports for Windows API functions that are every bit as natural to use as native Delphi functions. Some functions make strange use of flags and these can either use some of the described techniques to work around the hardship, or could be imported using good ol’ Cardinal.

As always, the situation and your own better judgement should serve as a guide.

Advertisements

2 Comments »

  1. […] Part 2 has now been […]

  2. mikeg said

    Perhaps you could try at http://refactormycode.com/codes/recent/delphi – who knows, perhaps someone comes up with a brilliant idea?

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: