Recent

Author Topic: WinAPI | EnumChildWindows causes SIGSEGV/SIGILL  (Read 4286 times)

Ash

  • Newbie
  • Posts: 6
WinAPI | EnumChildWindows causes SIGSEGV/SIGILL
« on: March 03, 2021, 06:03:37 pm »
Hi.
First time poster, long time lurker.

I'm getting a runtime error in my program by a call to EnumChildWindows.
Targetting x86_64 gives me a SIGSEGV error. Targetting i386 gives me a SIGILL error.

Tested with: Lazarus 2.0.12 + FPC 3.2.0 (x86_64-win64-win32/win64) and Lazarus 2.0.8 + FPC-3.0.4 (x86_64-win64-win32/win64).
Tested On: Windows 10 Pro 64bit and Windows 7 Pro 64bit.

Test code below produces these errors, for me, with FPC.
However, it works fine when tested with Delphi 2007 and C++, C#, VB (Visual Studio 2019).
Code: Pascal  [Select][+][-]
  1. program EnumChildWindows_test;
  2.  
  3. {$mode objfpc}
  4.  
  5. uses windows;
  6.  
  7. function EnumProc(hWnd: HWND; lParam: LPARAM): BOOL stdcall;
  8. begin
  9.   if (hWnd <> 0) then
  10.     EnumChildWindows(hWnd, ENUMWINDOWSPROC(@EnumProc), hWnd);
  11. (* Also of note:  EnumChildWindows doesn't always call its Callback function. *)
  12.  
  13.   result := true;
  14. end;
  15.  
  16. begin
  17.   EnumWindows(ENUMWINDOWSPROC(@EnumProc), 0);
  18. end.
  19.  

Hoping someone can point out if I'm doing it wrong or maybe perhaps a bug in FPC.

Thank you.

balazsszekely

  • Guest
Re: WinAPI | EnumChildWindows causes SIGSEGV/SIGILL
« Reply #1 on: March 03, 2021, 07:02:43 pm »
Enumerate all top level windows:
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   Windows;
  7.  
  8. var
  9.   wList: String;
  10.  
  11. function EnumWindowsProc(hWnd: HWND; {%H-}lParam: LPARAM): BOOL stdcall;
  12. var
  13.   Title: array[0..255] of Char;
  14.   ClassName: array[0..255] of Char;
  15. begin
  16.   GetWindowText(hWnd, Title, 255);
  17.   GetClassName(hWnd, ClassName, 255);
  18.   if IsWindowVisible(hWnd) then
  19.     wList := wList + String(Title) + ' - ' + String(ClassName) + sLineBreak;
  20.   Result := True;
  21. end;
  22.  
  23. begin
  24.   wList := '';
  25.   EnumWindows(@EnumWindowsProc, 0);
  26.   MessageBox(0, PChar(wLIst), PChar('Top level windows("Title" - "ClassName")'), 0);
  27. end.


Create a window and a button with pure api, then enumerate all top level windows on button click:
Code: Pascal  [Select][+][-]
  1. program EnumWindow;
  2.  
  3. {$mode Delphi}
  4.  
  5. uses windows;
  6.  
  7. var
  8.   wClass: TWndClass;
  9.   hInst: HWND;
  10.   hWindow: HWND;
  11.   hButton: HWND;
  12.   Msg: TMSG;
  13.   wList: String;
  14.  
  15. function EnumWindowsProc(hWnd: HWND; {%H-}lParam: LPARAM): BOOL stdcall;
  16. var
  17.   Title: array[0..255] of Char;
  18.   ClassName: array[0..255] of Char;
  19. begin
  20.   GetWindowText(hWnd, Title, 255);
  21.   GetClassName(hWnd, ClassName, 255);
  22.   if IsWindowVisible(hWnd) then
  23.     wList := wList + String(Title) + ' - ' + String(ClassName) + sLineBreak;
  24.   Result := True;
  25. end;
  26.  
  27. procedure DoOnclick;
  28. begin
  29.   wList := '';
  30.   EnumWindows(@EnumWindowsProc, 0);
  31.   MessageBox(hWindow, PChar(wLIst), PChar('Top level windows("Title" - "ClassName")'), 0);
  32. end;
  33.  
  34. procedure DoDestroy;
  35. begin
  36.   UnRegisterClass('My class', hInst);
  37.   ExitProcess(hInst);
  38. end;
  39.  
  40. procedure DoPaint;
  41. begin
  42.   //
  43. end;
  44.  
  45. function WindowProc(hWnd, Msg, wParam, lParam: DWord): Longint; stdcall;
  46. begin
  47.   Result := DefWindowProc(hWnd, Msg, wParam, lParam);
  48.   case Msg of
  49.      WM_COMMAND: if lparam = hButton then DoOnClick;
  50.      WM_DESTROY: DoDestroy;
  51.      WM_PAINT: DoPaint;
  52.   end;
  53. end;
  54.  
  55. begin
  56.   hInst := GetModuleHandle(nil);
  57.   with wClass do
  58.   begin
  59.     hIcon := LoadIcon(hInst, 'MAINICON');
  60.     lpfnWndProc:= @WindowProc;
  61.     hInstance := hInst;
  62.     hbrBackground := COLOR_BTNFACE;
  63.     lpszClassName := 'My class';
  64.   end;
  65.   RegisterClass(wClass);
  66.   hWindow := CreateWindow('My class', 'My main window', WS_SYSMENU or WS_VISIBLE or WS_SIZEBOX, 10, 10, 400, 300, 0, 0, hInst, nil);
  67.   hButton := CreateWindow('Button', 'Enum windows', WS_CHILD or BS_PUSHLIKE or BS_TEXT or WS_VISIBLE, 275, 125, 125, 24, hWindow, 0, hInst, nil);
  68.   SetFocus(hButton);
  69.   while(GetMessage({%H-}Msg, hWindow, 0, 0))do
  70.   begin
  71.     TranslateMessage(Msg);
  72.     DispatchMessage(Msg);
  73.   end;
  74. end.


The second example is interesting because LCL is not needed, the exe size is very small(69KB). The only issue with this approach is productivity, the lack of productivity to be more precise. :)
« Last Edit: March 03, 2021, 07:04:25 pm by GetMem »

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1314
    • Lebeau Software
Re: WinAPI | EnumChildWindows causes SIGSEGV/SIGILL
« Reply #2 on: March 03, 2021, 07:08:34 pm »
Enumerate all top level windows:
...
Create a window and a button with pure api, then enumerate all top level windows on button click:
...

In no way whatsoever does this even remotely try to answer Ash's question about why calling EnumChildWindows() inside of a EnumWindows() callback is crashing.
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1314
    • Lebeau Software
Re: WinAPI | EnumChildWindows causes SIGSEGV/SIGILL
« Reply #3 on: March 03, 2021, 07:12:45 pm »
I'm getting a runtime error in my program by a call to EnumChildWindows.

Off-hand, aside from the unnecessary type-casts, I see nothing wrong with the code.  Do you have the same problem if you use separate callbacks for EnumWindows() and EnumChildWindows()?

Code: Pascal  [Select][+][-]
  1. program EnumChildWindows_test;
  2.  
  3. {$mode objfpc}
  4.  
  5. uses windows;
  6.  
  7. function EnumChildProc(hWnd: HWND; lParam: LPARAM): BOOL stdcall;
  8. begin
  9.   EnumChildWindows(hWnd, @EnumChildProc, 0);
  10.   Result := TRUE;
  11. end;
  12.  
  13. function EnumProc(hWnd: HWND; lParam: LPARAM): BOOL stdcall;
  14. begin
  15.   EnumChildWindows(hWnd, @EnumChildProc, 0);
  16.   Result := TRUE;
  17. end;
  18.  
  19. begin
  20.   EnumWindows(@EnumProc, 0);
  21. end.
  22.  
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11447
  • FPC developer.
Re: WinAPI | EnumChildWindows causes SIGSEGV/SIGILL
« Reply #4 on: March 03, 2021, 07:25:59 pm »
With 64-bit Windows target, try FPC 3.2.0, there were quite some 64-bit fixes to the windows headers in 3.2.0

balazsszekely

  • Guest
Re: WinAPI | EnumChildWindows causes SIGSEGV/SIGILL
« Reply #5 on: March 03, 2021, 07:43:33 pm »
In no way whatsoever does this even remotely try to answer Ash's question about why calling EnumChildWindows() inside of a EnumWindows() callback is crashing.
Ok. I got carried away and I forget to add the EnumChildWindow part. Saying that my post has nothing to do with Ash's question is a gross exaggeration. Anyways here is the complete answer. Tested with Lazarus Trunk/FPC 3.2.0(64 bit), works fine. Regarding the unnecessary typecast, it's a bad habit from pre- FPC 3.0.0 era. 


Code: Pascal  [Select][+][-]
  1. program EnumWindow;
  2.  
  3. {$mode Delphi}
  4.  
  5. uses windows;
  6.  
  7. var
  8.   wClass: TWndClass;
  9.   hInst: HWND;
  10.   hWindow: HWND;
  11.   hButton: HWND;
  12.   Msg: TMSG;
  13.  
  14. function EnumChildWindowsProc(hWnd: HWnd; lParam: LPARAM): Bool; stdcall;
  15. var
  16.   Title: array[0..255] of Char;
  17. begin
  18.   GetWindowText(hWnd, Title, 255);
  19.   if IsWindowVisible(hWnd) and (String(Title) <> '') then
  20.     MessageBox(0, PChar(Title), PChar('Child window'), 0);
  21.   if GetWindow(hWnd, GW_CHILD) > 0 then
  22.     Enumchildwindows(hWnd, @EnumChildWindowsProc, 0);
  23.   Result := True;
  24. end;
  25.  
  26. function EnumWindowsProc(hWnd: HWND; {%H-}lParam: LPARAM): BOOL stdcall;
  27. var
  28.   Title: array[0..255] of Char;
  29. begin
  30.   GetWindowText(hWnd, Title, 255);
  31.   if IsWindowVisible(hWnd) and (String(Title) <> '') then
  32.     MessageBox(0, PChar(Title), PChar('Top window'), 0);
  33.   EnumChildWindows(hWnd, @EnumChildWindowsProc, 0);
  34.   Result := True;
  35. end;
  36.  
  37. procedure DoOnclick;
  38. begin
  39.   EnumWindows(@EnumWindowsProc, 0);
  40. end;
  41.  
  42. procedure DoDestroy;
  43. begin
  44.   UnRegisterClass('My class', hInst);
  45.   ExitProcess(hInst);
  46. end;
  47.  
  48. procedure DoPaint;
  49. begin
  50.   //
  51. end;
  52.  
  53. function WindowProc(hWnd, Msg, wParam, lParam: DWord): Longint; stdcall;
  54. begin
  55.   Result := DefWindowProc(hWnd, Msg, wParam, lParam);
  56.   case Msg of
  57.      WM_COMMAND: if lparam = hButton then DoOnClick;
  58.      WM_DESTROY: DoDestroy;
  59.      WM_PAINT: DoPaint;
  60.   end;
  61. end;
  62.  
  63. begin
  64.   hInst := GetModuleHandle(nil);
  65.   with wClass do
  66.   begin
  67.     hIcon := LoadIcon(hInst, 'MAINICON');
  68.     lpfnWndProc:= @WindowProc;
  69.     hInstance := hInst;
  70.     hbrBackground := COLOR_BTNFACE;
  71.     lpszClassName := 'My class';
  72.   end;
  73.   RegisterClass(wClass);
  74.   hWindow := CreateWindow('My class', 'My main window', WS_SYSMENU or WS_VISIBLE or WS_SIZEBOX, 10, 10, 400, 300, 0, 0, hInst, nil);
  75.   hButton := CreateWindow('Button', 'Enum windows', WS_CHILD or BS_PUSHLIKE or BS_TEXT or WS_VISIBLE, (400 - 125) div 2, 125, 125, 24, hWindow, 0, hInst, nil);
  76.   SetFocus(hButton);
  77.   while(GetMessage({%H-}Msg, hWindow, 0, 0))do
  78.   begin
  79.      TranslateMessage(Msg);
  80.      DispatchMessage(Msg);
  81.   end;
  82. end.
  83.  
« Last Edit: March 03, 2021, 07:45:33 pm by GetMem »

Ash

  • Newbie
  • Posts: 6
Re: WinAPI | EnumChildWindows causes SIGSEGV/SIGILL
« Reply #6 on: March 03, 2021, 07:58:41 pm »
Off-hand, aside from the unnecessary type-casts, I see nothing wrong with the code.  Do you have the same problem if you use separate callbacks for EnumWindows() and EnumChildWindows()?

Unless I include directive {$T+}, the compiler will give me the 'Incompatible type' error. (EDIT: Still need to use typecasts on some things even after including {$T+}.)
Yes, I get the same errors. Tried it before creating the thread. By the way, does it work for you?

--

With 64-bit Windows target, try FPC 3.2.0, there were quite some 64-bit fixes to the windows headers in 3.2.0

I'm using the latest version of the compiler which is fpc v3.2.0.

--

Just noticed that if I use {$mode delphi} instead of {$mode objfpc} i don't get the runtime errors. I guess i can live with that.
The irony of it is a little funny though (for me anway) because I'm converting an old project I wrote in Delphi 7 to Free Pascal and I have to use the Delphi mode for it to work.

So is something broken on my end or with mode objfpc?

--

Thanks for the responses.
« Last Edit: March 03, 2021, 08:10:35 pm by Ash »

Ash

  • Newbie
  • Posts: 6
Re: WinAPI | EnumChildWindows causes SIGSEGV/SIGILL
« Reply #7 on: March 03, 2021, 08:03:36 pm »
Ok. I got carried away and I forget to add the EnumChildWindow part. Saying that my post has nothing to do with Ash's question is a gross exaggeration. Anyways here is the complete answer. Tested with Lazarus Trunk/FPC 3.2.0(64 bit), works fine. Regarding the unnecessary typecast, it's a bad habit from pre- FPC 3.0.0 era. 

Code: Pascal  [Select][+][-]
  1. program EnumWindow;
  2.  
  3. {$mode Delphi}
  4.  
  5. uses windows;
  6.  
  7. var
  8.   wClass: TWndClass;
  9.   hInst: HWND;
  10.   hWindow: HWND;
  11.   hButton: HWND;
  12.   Msg: TMSG;
  13.  
  14. function EnumChildWindowsProc(hWnd: HWnd; lParam: LPARAM): Bool; stdcall;
  15. var
  16.   Title: array[0..255] of Char;
  17. begin
  18.   GetWindowText(hWnd, Title, 255);
  19.   if IsWindowVisible(hWnd) and (String(Title) <> '') then
  20.     MessageBox(0, PChar(Title), PChar('Child window'), 0);
  21.   if GetWindow(hWnd, GW_CHILD) > 0 then
  22.     Enumchildwindows(hWnd, @EnumChildWindowsProc, 0);
  23.   Result := True;
  24. end;
  25.  
  26. function EnumWindowsProc(hWnd: HWND; {%H-}lParam: LPARAM): BOOL stdcall;
  27. var
  28.   Title: array[0..255] of Char;
  29. begin
  30.   GetWindowText(hWnd, Title, 255);
  31.   if IsWindowVisible(hWnd) and (String(Title) <> '') then
  32.     MessageBox(0, PChar(Title), PChar('Top window'), 0);
  33.   EnumChildWindows(hWnd, @EnumChildWindowsProc, 0);
  34.   Result := True;
  35. end;
  36.  
  37. procedure DoOnclick;
  38. begin
  39.   EnumWindows(@EnumWindowsProc, 0);
  40. end;
  41.  
  42. procedure DoDestroy;
  43. begin
  44.   UnRegisterClass('My class', hInst);
  45.   ExitProcess(hInst);
  46. end;
  47.  
  48. procedure DoPaint;
  49. begin
  50.   //
  51. end;
  52.  
  53. function WindowProc(hWnd, Msg, wParam, lParam: DWord): Longint; stdcall;
  54. begin
  55.   Result := DefWindowProc(hWnd, Msg, wParam, lParam);
  56.   case Msg of
  57.      WM_COMMAND: if lparam = hButton then DoOnClick;
  58.      WM_DESTROY: DoDestroy;
  59.      WM_PAINT: DoPaint;
  60.   end;
  61. end;
  62.  
  63. begin
  64.   hInst := GetModuleHandle(nil);
  65.   with wClass do
  66.   begin
  67.     hIcon := LoadIcon(hInst, 'MAINICON');
  68.     lpfnWndProc:= @WindowProc;
  69.     hInstance := hInst;
  70.     hbrBackground := COLOR_BTNFACE;
  71.     lpszClassName := 'My class';
  72.   end;
  73.   RegisterClass(wClass);
  74.   hWindow := CreateWindow('My class', 'My main window', WS_SYSMENU or WS_VISIBLE or WS_SIZEBOX, 10, 10, 400, 300, 0, 0, hInst, nil);
  75.   hButton := CreateWindow('Button', 'Enum windows', WS_CHILD or BS_PUSHLIKE or BS_TEXT or WS_VISIBLE, (400 - 125) div 2, 125, 125, 24, hWindow, 0, hInst, nil);
  76.   SetFocus(hButton);
  77.   while(GetMessage({%H-}Msg, hWindow, 0, 0))do
  78.   begin
  79.      TranslateMessage(Msg);
  80.      DispatchMessage(Msg);
  81.   end;
  82. end.
  83.  

This code does indeed work, but only when using {$mode delphi}. It still produces runtime errors when using {$mode objfpc}.

ASBzone

  • Hero Member
  • *****
  • Posts: 678
  • Automation leads to relaxation...
    • Free Console Utilities for Windows (and a few for Linux) from BrainWaveCC
Re: WinAPI | EnumChildWindows causes SIGSEGV/SIGILL
« Reply #8 on: March 03, 2021, 08:05:11 pm »
Just noticed that if I use {$mode delphi} instead of {$mode objfpc} i don't get the runtime errors. I guess i can live with that.
The irony of it is a little funny though (for me anway) because I'm converting an old project I wrote in Delphi 7 to Free Pascal and I have to use the Delphi mode for it to work.

So is something broken on my end or with mode objfpc?

{$mode delphi} is the mode to use for maximum Delphi compatibility.  {$mode objfpc} behaves differently in some areas for a variety of reasons, none of which means that this is a broken mode.


Sometimes a feature already exists, and making it Delphi compatible means running in a mode where it will behave differently than it would otherwise.   

For instance:  https://www.freepascal.org/docs-html/prog/progse74.html
-ASB: https://www.BrainWaveCC.com/

Lazarus v2.2.7-ada7a90186 / FPC v3.2.3-706-gaadb53e72c
(Windows 64-bit install w/Win32 and Linux/Arm cross-compiles via FpcUpDeluxe on both instances)

My Systems: Windows 10/11 Pro x64 (Current)

balazsszekely

  • Guest
Re: WinAPI | EnumChildWindows causes SIGSEGV/SIGILL
« Reply #9 on: March 03, 2021, 08:09:12 pm »
@Ash

Go with {$mode delphi} as @ASBzone suggested, nothing wrong with that.

Ash

  • Newbie
  • Posts: 6
Re: WinAPI | EnumChildWindows causes SIGSEGV/SIGILL
« Reply #10 on: March 03, 2021, 08:20:38 pm »
I will be using {$mode delphi} for that particular unit in my project.
I don't think this is a 'feature' issue between the two modes. Calling a WinAPI function should behave the same in either mode but causes runtime errors in the objfpc mode. Am I missing something here? I'm just curious if there's a problem on my end or is there some bug in the compiled code when using the objfpc mode. Which doesn't make sense to me anyway so i'm thinking something is broken on my end.

ASBzone

  • Hero Member
  • *****
  • Posts: 678
  • Automation leads to relaxation...
    • Free Console Utilities for Windows (and a few for Linux) from BrainWaveCC
Re: WinAPI | EnumChildWindows causes SIGSEGV/SIGILL
« Reply #11 on: March 03, 2021, 08:49:15 pm »
I will be using {$mode delphi} for that particular unit in my project.
I don't think this is a 'feature' issue between the two modes. Calling a WinAPI function should behave the same in either mode but causes runtime errors in the objfpc mode. Am I missing something here? I'm just curious if there's a problem on my end or is there some bug in the compiled code when using the objfpc mode. Which doesn't make sense to me anyway so i'm thinking something is broken on my end.

There's obviously a difference in the code path that has an impact on your app.

It might take a little bit of digging to determine if it is a bug in FPC, or the natural implication of that code path on some other aspect of your code.
-ASB: https://www.BrainWaveCC.com/

Lazarus v2.2.7-ada7a90186 / FPC v3.2.3-706-gaadb53e72c
(Windows 64-bit install w/Win32 and Linux/Arm cross-compiles via FpcUpDeluxe on both instances)

My Systems: Windows 10/11 Pro x64 (Current)

440bx

  • Hero Member
  • *****
  • Posts: 4023
Re: WinAPI | EnumChildWindows causes SIGSEGV/SIGILL
« Reply #12 on: March 03, 2021, 08:56:31 pm »
so i'm thinking something is broken on my end.
and you are right to think that something is broken on your end.  The problem resides in the fact that you are using the same callback routine for both EnumWindows and EnumChildWindows.  That causes your callback routine to be called recursively for every child window.  That API was _not_ designed to be called recursively (see MSDN.)    EnumChildWindows will list all the children, grandchildren and grandgrand...grandchildren without having to call it for every child.  The fact that it is being called recursively is conceptually incorrect and it is actually surprising that doing so works in some cases (I'm taking your word for it that it worked with Delphi using the code you posted.)

Attached to this post you'll find an example that lists all windows in the system (those returned by EnumWindows which unfortunately excludes the desktop window) and their children.  Notice that the example does _not_ cause EnumWindows and/or EnumChildWindows to be called recursively.  Since, it is often convenient to show the "genealogy" of a window, for that purpose, the example uses GetAncestor, IOW, it doesn't try to establish the genealogy using recursion.

Also, the example works with both FPC 32 and 64 bit as well as with Delphi (tested with Delphi 2 only) no "mode whatever" needed.

HTH.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1314
    • Lebeau Software
Re: WinAPI | EnumChildWindows causes SIGSEGV/SIGILL
« Reply #13 on: March 04, 2021, 12:37:21 am »
The problem resides in the fact that you are using the same callback routine for both EnumWindows and EnumChildWindows.  That causes your callback routine to be called recursively for every child window.  That API was _not_ designed to be called recursively (see MSDN.)    EnumChildWindows will list all the children, grandchildren and grandgrand...grandchildren without having to call it for every child.  The fact that it is being called recursively is conceptually incorrect and it is actually surprising that doing so works in some cases (I'm taking your word for it that it worked with Delphi using the code you posted.)

Good catch!  I forgot about that detail.
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

440bx

  • Hero Member
  • *****
  • Posts: 4023
Re: WinAPI | EnumChildWindows causes SIGSEGV/SIGILL
« Reply #14 on: March 04, 2021, 01:40:20 am »
Good catch!  I forgot about that detail.
It's a very easy to forget detail because its description (EnumChildWindows) doesn't make that clear at all.  Instead, it is only in the "remarks" section that they point it out.

Another interesting comment in the remarks section implies that the API is a "snapshot" type of function, i.e, it builds a window list upfront then verifies that the window hasn't been destroyed before returning it.  The fact that it builds the window list upfront also explains why it won't return any child window that was created during the enumeration.

NOTE: disassembly of the API confirms that it is a snapshot type function as the remarks section implies.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

 

TinyPortal © 2005-2018