Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi XE2 EnumWindows not working properly

Using Delphi XE2 update 3 or update 4 on Win7 64 bit.

Calling enumwindows does not work like it used to work in Delphi 6.

In Delphi 6 enumwindows processed windows until the callback function returned False. That is what the documentation says it should do:

"To continue enumeration, the callback function must return TRUE; to stop enumeration, it must return FALSE."

Making a call to enumwindows as follows:

procedure TForm1.Button1Click(Sender: TObject);
begin
  EnumWindows(@FindMyWindow,0);
  if GLBWindowHandle <> 0 then begin
    ShowMessage('found');
  end;
end;

Here is the callback function:

function FindMyWindow(hWnd: HWND; lParam: LPARAM): boolean; stdcall;
var TheText : array[0..150] of char;
str : string;
begin
Result := True;
GLBWindowHandle := 0;
if (GetWindowText(hWnd, TheText, 150) <> 0) then
   begin
   str := TheText;
   if str = 'Form1' then
      begin
      GLBWindowHandle := hWnd;
      Result := False;
      end
   else
      result := True;
   end;
end;

Just to be clear the callback function is defined in code BEFORE the buttonclick event so it is found by the compiler without needing to be defined in the interface section.

If this is run using Delphi 6 the enumeration of windows stops once the False result is returned and GLBWindowHandle is not zero

If this is run using Delphi XE2 the enumeration continues after the False result is returned and GLBWindowHandle is always zero.

WTF? Anybody have any ideas why the enumeration is not stopping like the documentation states it should and how it used to in Delphi 6?

Cheers!

like image 862
TJ Asher Avatar asked Mar 01 '12 18:03

TJ Asher


1 Answers

This declaration is incorrect:

function FindMyWindow(hWnd: HWND; lParam: LPARAM): boolean; stdcall;

It should be:

function FindMyWindow(hWnd: HWND; lParam: LPARAM): BOOL; stdcall;

You have to be careful not to mix up Boolean and BOOL since they are not the same thing. The former is a single byte, the latter is 4 bytes. This mismatch between what EnumWindows expects and what your callback function delivers is enough to cause the behaviour you observe.


In addition, Rob Kennedy contributed this excellent comment:

The compiler can help find this error if you get out of the habit of using the @ operator before the function name when you call EnumWindows. If the function signature is compatible, the compiler will let you use it without @. Using @ turns it into a generic pointer, and that's compatible with everything, so the error is masked by unnecessary syntax. In short, using @ to create function pointers should be considered a code smell.


Discussion

Unfortunately the Windows.pas header translation defines EnumWindows in a most unhelpful manner, like this:

function EnumWindows(lpEnumFunc: TFNWndEnumProc; lParam: LPARAM): BOOL; stdcall;

Now, the problem is in the definition of TFNWndEnumProc. It is defined as:

TFarProc = Pointer;
TFNWndEnumProc = TFarProc;

This means that you have to use the @ operator to make a generic pointer, because the function needs a generic pointer. If TFNWndEnumProc were declared like this:

TFNWndEnumProc = function(hWnd: HWND; lParam: LPARAM): BOOL; stdcall;

then the compiler would have been able to find the error.

type
  TFNWndEnumProc = function(hWnd: HWND; lParam: LPARAM): BOOL; stdcall;

function EnumWindows(lpEnumFunc: TFNWndEnumProc;
  lParam: LPARAM): BOOL; stdcall; external 'user32';

function FindMyWindow(hWnd: HWND; lParam: LPARAM): Boolean; stdcall;
begin
  Result := False;
end;

....
EnumWindows(FindMyWindow, 0);

The compiler rejects the call to EnumWindows with the following error:

[DCC Error] Unit1.pas(38): E2010 Incompatible types: 'LongBool' and 'Boolean'

I think I will QC this issue and try my luck at persuading Embarcadero to stop using TFarProc.

like image 129
David Heffernan Avatar answered Oct 25 '22 17:10

David Heffernan