If you read the MSDN documentation of WM_KEYDOWN and WM_KEYUP you can see that those message require us to interpret lParam as a bitfield:

lParam
Specifies the repeat count, scan code, extended-key flag, context code, previous key-state flag, and transition-state flag, as shown in the following table.

0-15
Specifies the repeat count for the current message. The value is the number of times the keystroke is autorepeated as a result of the user holding down the key. The repeat count is always one for a WM_KEYUP message.
16-23
Specifies the scan code. The value depends on the OEM.
24
Specifies whether the key is an extended key, such as the right-hand ALT and CTRL keys that appear on an enhanced 101- or 102-key keyboard. The value is 1 if it is an extended key; otherwise, it is 0.
25-28
Reserved; do not use.
29
Specifies the context code. The value is always 0 for a WM_KEYUP message.
30
Specifies the previous key state. The value is always 1 for a WM_KEYUP message.
31
Specifies the transition state. The value is always 1 for a WM_KEYUP message.

I was looking for a convenient way to get and read the bits and this is what I made up:

  TlParamRecord = record
    case Integer of
      0: (
        RepeatCount: Word;
        ScanCode: Byte;
        Flags: Set of (lpfExtended, lpfReserved=4, lpfContextCode,
          lpfPreviousKeyState, lpfTransitionState);
      );
      1: (lParam: LPARAM);
  end;

Note that I use a set to emulate a bitfield (as described here). Below a usage example:

var
  lpr: TlParamRecord;
begin
  lpr.lParam := 0;
  lpr.Flags := [lpfPreviousKeyState, lpfTransitionState];
  lpr.ScanCode := MapVirtualKey(VK_F2, 0);
  Result := PostMessage(hWnd, WM_KEYUP, VK_F2, lpr.lParam);

I also wrote some helper functions that might be helpfull:

function PostKeyDownMessage(const hWnd: HWND; const VirtualKey: Integer; const Modifier: Integer=0): Boolean;
var
  lpr: TlParamRecord;
begin
  lpr.lParam := 0;
  if Modifier > 0 then
  begin
    lpr.ScanCode := MapVirtualKey(Modifier, 0);
    Result := PostMessage(hWnd, WM_KEYDOWN, Modifier, lpr.lParam);
    if not Result then Exit;
  end;
  lpr.ScanCode := MapVirtualKey(VirtualKey, 0);
  Result := PostMessage(hWnd, WM_KEYDOWN, VirtualKey, lpr.lParam);
end;

function PostKeyUpMessage(const hWnd: HWND; const VirtualKey: Integer; const Modifier: Integer=0): Boolean;
var
  lpr: TlParamRecord;
begin
  lpr.lParam := 0;
  lpr.Flags := [lpfPreviousKeyState, lpfTransitionState];
  lpr.ScanCode := MapVirtualKey(VirtualKey, 0);
  Result := PostMessage(hWnd, WM_KEYUP, VirtualKey, lpr.lParam);

  if Result and (Modifier > 0) then
  begin
    lpr.ScanCode := MapVirtualKey(Modifier, 0);
    Result := PostMessage(hWnd, WM_KEYUP, Modifier, lpr.lParam);
  end;
end;

function PostKeyDownUpMessage(const hWnd: HWND; const VirtualKey: Integer; const Modifier: Integer=0): Boolean;
begin
  Result := PostKeyDownMessage(hWnd, VirtualKey, Modifier);
  if not Result then Exit;
  Result := PostKeyUpMessage(hWnd, VirtualKey, Modifier);
end;

Related posts:

  1. Supporting for in loop in TObjectList descendants
  2. Working with bitfields in Delphi
  3. Converting a volume name to a device name
  4. Locking a workstation – part 2
  5. Sending Ctrl-Alt-Del / Simulate SAS in Windows Vista