1

I try to get the main window handle using following code in Lazarus (Free Pascal):

function FindMainWindow(Pid: LongWord): LongWord;
type
  TParam = record
    Window: HWnd;
    Test: Integer;
    Pid: LongWord;
  end;
  PParam = ^TParam;
var
  Params: TParam;
  function _FindMainWindow(Wnd: HWnd; MyLParam: PParam): Bool; stdcall;
  var
    WinPid: DWord;
  begin
    with MyLParam^ do
    begin
      Test := 2;
      GetWindowThreadProcessID(Wnd, @WinPid);
      Result := (WinPid <> Pid) or (not IsWindowVisible(Wnd))
        or (not IsWindowEnabled(Wnd));
      if not Result then begin
        Window := Wnd;
      end;
    end;
  end;
begin
  Params.Pid := Pid;
  Params.Test := 1;
  EnumWindows(@_FindMainWindow, LParam(@Params));
  ShowMessage('Done!');
  ShowMessage(IntToStr(Params.Test));
  Result := Params.Window;
end; 

The Problem is that Params.Test is still 1 after running the callback. I want to modify the Params in the _FindMainWindow function.

Note: I could not access Params in _FindMainWindow directly because i get an "Access violation" error.

Maximilian Ruta
  • 518
  • 7
  • 18
  • When I tested this code it show 2 after 'Done!' ? – SimaWB Jun 22 '12 at 10:54
  • How do you compiled it? I have `{$mode DELPHI}` in the header of the unit to compile it with the freepascal compiler. (if i use `{$mode objfpc}{$H+}` the compiler says: `Error: Incompatible type for arg no. 1: Got "
    ", expected ""` Demo Project: http://cloud.jupiter.xtain.net/apps/files_sharing/get.php?token=10640e0439849926ef1594808749a4b42ad04c1b
    – Maximilian Ruta Jun 22 '12 at 11:08
  • Why are you nesting this function? What happens when you don't do that? You are targeting x64 right? – David Heffernan Jun 22 '12 at 11:15
  • Yes i targeting x64. I nest the function because i dont want to expose _FindMainWindow to the world. But this is not a must have. If i dont nest the function it works! (Question: Why?) Thank you man! If you write an answer i accept it. – Maximilian Ruta Jun 22 '12 at 11:21
  • The user parameter is usually only used to pass the address of the caller object, as the procedure is global. Then in the callback proc you can access to your oject by using **with TMyCallerType(LParam) do**. This is the equivalent to the EAX hidden parameter in the prototype **procedure (...) of Object**. So I would recommend you to put the callback proc outside the procedure. – az01 Jun 23 '12 at 17:02

1 Answers1

3

Certainly in Delphi, and it appears FPC too, nested functions are not valid for use as callback functions. When using the 32 bit compiler, it so happens that nested functions can be used as callbacks. But such code is only accepted by the compiler because the callback functions are declared in the Windows unit as untyped pointers. If the Windows unit declared them as procedural types, you find that the compiler objects to using nested functions.

For the 64 bit Delphi compiler, you simply cannot use nested functions as callbacks at all. Whilst the compiler lets you go ahead, because of the use of untyped pointers in the Windows unit (see above), the callback functions are not called correctly. Apparently that is true for FPC too. You will have to stop using nested functions for your callbacks.

It's interesting that both FPC and Delphi compilers have the same characteristics here. My guess is that the x64 calling convention, which is a register based convention as opposed to the stack based x86 stdcall is the driving force behind this issue. I bet that if you tried to use a nested x86 register function as a callback, then that would fail at runtime too.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • You are opening a can of worms there. Nested is a classically complicated story in Pascal. This because Borland derived versions only implement the functionality in a very limited way. Basically, an ISO Pascal nested function, like a method function variable is a two-pointer type. FPC started supporting this with 2.6.0, see http://wiki.freepascal.org/FPC_New_Features_2.6.0#Support_for_nested_procedure_variables Of course for external interfacing, other languages ABI's are observed. – Marco van de Voort Jun 22 '12 at 20:29
  • @Marco The `lpEnumFunc` parameter of `EnumWindows` is not a two-pointer type though. Since I know nothing of FPC, please do feel free to write your own answer, or edit mine, to supply the FPC details which I cannot. – David Heffernan Jun 22 '12 at 20:33
  • My addendum is that the "stdcall" that forces the compatibility, since that triggers "OS ABI", rather that FPC vs Delphi. The default calling convention depends on compiler mode. Delphi mode is currently compatible enough to make Delphi "assembler" code run, in ISO mode, the procedure variables behaviour is more classic. – Marco van de Voort Jun 22 '12 at 20:45
  • @MarcovandeVoort `stdcall` doesn't mean anything though on x64. Only one calling convention per platform. – David Heffernan Jun 22 '12 at 22:05
  • If you consider one pointer as a limit per call, yes:-) But as said even Delphi has its procedure of object and anonymous methods. Nested procedures (ISO style) are just one more. And the oldest..... – Marco van de Voort Jun 22 '12 at 22:15
  • @Marco All very interesting but a little tangential to Win32 callbacks and C function pointers. – David Heffernan Jun 22 '12 at 22:25
  • The common ground is calling convention/ABI, and the question you ask yourself in the last paragraph of your post. It isn't as general as it seems, you just chose a convenient subset. (one pointer calling conventions on x86_64) – Marco van de Voort Jun 22 '12 at 23:56