User handle leak in TOleControl.CMDialogKey calling IOleControl.GetControlInfo

In versions of Delphi prior
to Delphi 2006 there is a bug in TOleControl.CMDialogKey which can cause a USER
handle leak when certain keys are pressed. The issue is that the method always
calls IOleControl.GetControlInfo(…)
which takes a CONTROLINFO
structure that has an hAccel member which is, according to MSDN, a:

Handle to an array of Windows ACCEL structures, each structure
describing a keyboard mnemonic. The array is allocated with the GlobalAlloc
function. The control always maintains the memory for this array; the caller of
IOleControl::GetControlInfo should not attempt to free the memory.

The problem with calling IOleControl.GetControlInfo is that each time it’s
called you get a new Windows USER handle allocated. In Delphi 2005, the problem
is apparent when the Welcome page is visible and you press the Enter key when
the focus is on some other window like the structure pane. This results in the
“loss” of a Windows USER handle with each keypress. In Delphi 2006, we’ve
addressed this by adding a TControlInfo data member to TOleControl and caching
the result from the first call to IOleControl.GetControlInfo in
TOleControl.CMDialogKey. So, for example if you’ve been using the TWebBrowser
control and are experiencing loss of Windows USER handles at an alarming rate
this could/should help explain why.

Update #1:

To be perfectly clear, this bug was fixed in
TOleControl in the RTM release of BDS 2006 which means subsequently the
TWebBrowser component no longer suffers from this problem.

Update #2:

Here is the code that was changed to correct
this problem:

Add the following data member to TOleControl:

  TOleControl = class(TWinControl, IUnknown, IOleClientSite,
IOleControlSite, IOleInPlaceSite, IOleInPlaceFrame, IDispatch,
IPropertyNotifySink, ISimpleFrameSite, IServiceProvider)
private
...
FControlInfo: TControlInfo; // Added new data member
...
end;

Change TOleControl.CreateControl by inserting the following two lines
(marked below):

procedure TOleControl.CreateControl;
var
Stream: IStream;
CS: IOleClientSite;
X: Integer;
begin
if FOleControl = nil then
try
try // work around ATL bug
...
OleCheck(FOleObject.QueryInterface(IOleControl, FOleControl));
FControlInfo.cb := SizeOf(FControlInfo); //*** Inserted ***
FOleControl.GetControlInfo(FControlInfo); //*** Inserted ***
OleCheck(FOleObject.QueryInterface(IDispatch, FControlDispatch));
...
except
DestroyControl;
raise;
end;
end;

Change the TOleControl.CMDialogKey method to read:

procedure TOleControl.CMDialogKey(var Message: TMessage);
var
Msg: TMsg;
Cmd: Word;
begin
if CanFocus then
begin
if (FControlInfo.cAccel <> 0) then
begin
FillChar(Msg, SizeOf(Msg), 0);
Msg.hwnd := Handle;
Msg.message := WM_KEYDOWN;
Msg.wParam := Message.WParam;
Msg.lParam := Message.LParam;
if IsAccelerator(FControlInfo.hAccel,
FControlInfo.cAccel, @Msg, Cmd) then
begin
FOleControl.OnMnemonic(@Msg);
Message.Result := 1;
Exit;
end;
end;
end;
inherited;
end;

That’s it. Now, when TOleControl.CMDialogKey is called it won’t try to
fetch the control info again and thus prevents the USER handle lleak.