Enumerate all top level windows:
...
Create a window and a button with pure api, then enumerate all top level windows on button click:
...
I'm getting a runtime error in my program by a call to EnumChildWindows.
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.
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()?
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
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.
program EnumWindow; {$mode Delphi} uses windows; var wClass: TWndClass; hInst: HWND; hWindow: HWND; hButton: HWND; Msg: TMSG; function EnumChildWindowsProc(hWnd: HWnd; lParam: LPARAM): Bool; stdcall; var Title: array[0..255] of Char; begin GetWindowText(hWnd, Title, 255); if IsWindowVisible(hWnd) and (String(Title) <> '') then MessageBox(0, PChar(Title), PChar('Child window'), 0); if GetWindow(hWnd, GW_CHILD) > 0 then Enumchildwindows(hWnd, @EnumChildWindowsProc, 0); Result := True; end; function EnumWindowsProc(hWnd: HWND; {%H-}lParam: LPARAM): BOOL stdcall; var Title: array[0..255] of Char; begin GetWindowText(hWnd, Title, 255); if IsWindowVisible(hWnd) and (String(Title) <> '') then MessageBox(0, PChar(Title), PChar('Top window'), 0); EnumChildWindows(hWnd, @EnumChildWindowsProc, 0); Result := True; end; procedure DoOnclick; begin EnumWindows(@EnumWindowsProc, 0); end; procedure DoDestroy; begin UnRegisterClass('My class', hInst); ExitProcess(hInst); end; procedure DoPaint; begin // end; function WindowProc(hWnd, Msg, wParam, lParam: DWord): Longint; stdcall; begin Result := DefWindowProc(hWnd, Msg, wParam, lParam); case Msg of WM_COMMAND: if lparam = hButton then DoOnClick; WM_DESTROY: DoDestroy; WM_PAINT: DoPaint; end; end; begin hInst := GetModuleHandle(nil); with wClass do begin hIcon := LoadIcon(hInst, 'MAINICON'); lpfnWndProc:= @WindowProc; hInstance := hInst; hbrBackground := COLOR_BTNFACE; lpszClassName := 'My class'; end; RegisterClass(wClass); hWindow := CreateWindow('My class', 'My main window', WS_SYSMENU or WS_VISIBLE or WS_SIZEBOX, 10, 10, 400, 300, 0, 0, hInst, nil); 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); SetFocus(hButton); while(GetMessage({%H-}Msg, hWindow, 0, 0))do begin TranslateMessage(Msg); DispatchMessage(Msg); end; end.
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?
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.
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.)
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.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.
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
That API was _not_ designed to be called recursively (see MSDN.)I already knew about this.
Am I the only person that is curious as to why EnumChildWindows (when used in the way like the code above) crashes with {$mode objfpc}, but it works as intended with {$mode delphi} in FPC as well as in Delphi(7, 2007), c++, c# and VB?No I'm also curious, but the thing is my previous example does not crash with {$mode ObjFPC}{$H+}. Please test attached project.
No I'm also curious, but the thing is my previous example does not crash with {$mode ObjFPC}{$H+}. Please test attached project.
If I split the callback functions into two like in your example, it works fine.
I think every beginner should study your code, before using the power of form designers like Lazarus and Delphi.Thank you for the kind words.
The only drawback as far as I can see is productivity. When you have a very large project, with hundred or so forms, creating everything with API is time consuming.I know most people here will disagree with me but, in the long run, I think productivity might actually be higher programming directly to the API than using a framework. The reason I say that is because all frameworks have a number of assumptions or conditions that the code has to meet in order to work with it and, quite often those assumptions or conditions are either not well documented or easy to forget.
Thx for sharing.My pleasure.
Although, in my opinion, if called recursively it shouldn't cause any crashes unless you have so many windows open that you run out of memory. Happy to be corrected about this though.It's not reasonable to expect a function that was _not_ designed to be called recursively to operate properly when called recursively. The first reason for that is, a recursive function has to be re-entrant, while a non recursive function does not have to meet that requirement and, if called recursively, the recursive calls will cause its internal data to be corrupted in ways that are hard to predict with consequent results.
{$mode objfpc} // Crashes in this mode.I admit to being somewhat curious too regarding the reason why it _seems_ to work in one mode (delphi) and not in the other but, knowing that the code is inherently wrong greatly subtracts from the desire to spend time to find out why.
//{$mode delphi} // Successful in this mode.
If you percieve this as a rant, ...I understand it's not a rant and, I believe curiosity is a good thing but, in general, my desire to satisfy it is much greater if I believe I did everything right than when I know the problem is at least in part caused by my code being incorrect. In those cases, first I correct my code then, if it still fails, I will go out of my way to satisfy my curiosity to determine why it's failing. :)