-1

I would like to have a single neat (close and self contained) function (let's call it GetDesktopHandle) that returns a handle to the Desktop window. I use the code below. But it only works in the DeskHandle is a global var.

How to get rid of this global variable? If I make it local I get an AV in getDesktopWnd when I try to DeskHandle := hChild

VAR DeskHandle : HWND;

function GetDesktopHandle: HWND;

  function getDesktopWnd (Handle: HWND; NotUsed: Longint): bool; stdcall;    { Callback function }
  VAR hChild : HWND;
  begin
   if handle <> 0 then
    begin
      hChild := FindWindowEx(handle, 0, 'SHELLDLL_DefView', nil);
      if hChild <> 0 then
       begin
        hChild := FindWindowEx(hChild, 0, 'SysListView32', nil);
        if hChild <> 0
        then DeskHandle := hChild;
       end;
    end;
   Result:= TRUE;
  end;

begin
 DeskHandle := 0;
 EnumWindows(@getDesktopWnd, 0);
 Result:= DeskHandle;
end;

The main question is: can I write this code as a single function or AT LEAST, can I get rid of the external/global var?

Possible solution:
The documentation says that the second parameter is only a IN parameter.

lParam [in] Type: LPARAM An application-defined value to be passed to the callback function.
https://msdn.microsoft.com/en-us/library/windows/desktop/ms633497%28v=vs.85%29.aspx

Would it be wrong to use it to pass the result back?

Gabriel
  • 20,797
  • 27
  • 159
  • 293
  • 1
    Instead of enumerating all the top-level windows looking for the root of the desktop, you could just call `GetShellWindow` to go directly to it. Then use `FindWindowEx` twice as you're already doing. – Rob Kennedy Apr 19 '16 at 12:06
  • See also http://stackoverflow.com/a/5691808/33732 – Rob Kennedy Apr 19 '16 at 12:08
  • There is an existing function to get the Desktop Window Handle and it's called [GetDesktopWindow](https://msdn.microsoft.com/en-us/library/windows/desktop/ms633504(v=vs.85).aspx) – Remko Apr 19 '16 at 12:30
  • @RobKennedy - GetShellWindow will fail on some Windows platforms. Details: http://stackoverflow.com/questions/36718632/a-reliable-way-of-getting-a-handle-to-the-dekstop-window – Gabriel Apr 19 '16 at 12:38
  • @Remko-same for GetDesktopWindow – Gabriel Apr 19 '16 at 12:40
  • 1
    @Remko, that function doesn't fetch the same thing as what we're dealing with here. See the question I linked in my previous comment. – Rob Kennedy Apr 19 '16 at 12:40

2 Answers2

3

Local functions cannot be used as callbacks. If you hadn't used the @ operator to pass your function, the compiler would have told you that. (Using the operator turns the argument into an ordinary untyped pointer, so the compiler can't check anymore.)

You'll have to make your callback be a standalone function.

To pass data between the callback and the caller, use the second parameter, which you've currently named NotUsed. For example, you could pass a pointer to a handle variable, and then the callback could dereference the pointer to return a result.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • Thanks Rob (and +1). I wanted to pass the result bank through the NotUsed variable but the documentation says that variable is to pass parameters from caller to callback, not the other way. https://msdn.microsoft.com/en-us/library/windows/desktop/ms633497%28v=vs.85%29.aspx – Gabriel Apr 19 '16 at 12:01
  • The parameter you're passing is an address, and that address indeed goes from the caller to the receiver. You're in charge of what the receiver does with the value it receives. You can choose to make it dereference that value. – Rob Kennedy Apr 19 '16 at 12:03
1
type
  TMyData = record
    Handle: HWND;
    Pid: DWORD;
    Caption: String;
    ClassName: String;
  end;
  PMyData = ^TMyData;

function GetWindowClass(const Handle: HWND): String;
begin
  SetLength(Result, MAX_PATH);
  SetLength(Result, GetClassName(Handle, PChar(Result), Length(Result)));
end;

function GetWindowCaption(const Handle: HWND): String;
 begin
  SetLength(Result, MAX_PATH);
  SetLength(Result, GetWindowText(Handle, PChar(Result), Length(Result)));
end;

function EnumChildWindowsProc(Handle: THandle; MyData: PMyData): BOOL; stdcall;
var
  ClassName: String;
  Caption: String;
  Pid: DWORD;
begin
  ClassName := GetWindowClass(Handle);
  Caption := GetWindowCaption(Handle);

  Result := (ClassName = 'SysListView32') and (Caption = 'FolderView');
  if Result then
  begin
    MyData.Handle := Handle;
    GetWindowThreadProcessId(Handle, MyData.Pid);
    MyData.Caption := Caption;
    MyData.ClassName := ClassName;
  end;

  // To continue enumeration, the callback function must return TRUE;
  // to stop enumeration, it must return FALSE
  Result := not Result;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  MyData: TMyData;
begin
  ZeroMemory(@MyData, SizeOf(MyData));
  EnumChildWindows(GetDesktopWindow, @EnumChildWindowsProc, NativeInt(@MyData));
  if MyData.Handle > 0 then
  begin
     ShowMessageFmt('Found Window in Pid %d', [MyData.Pid]);
  end
  else begin
    ShowMessage('Window not found!');
  end;
end;
Remko
  • 7,214
  • 2
  • 32
  • 52
  • What's special about `Handle` being positive rather than negative. A clearer test is `if MyData.Handle <> 0 then`. And `Result := not Result` is pretty weird, just for the sake of saving a local variable! – David Heffernan Apr 19 '16 at 15:34
  • @DavidHeffernan `THandle = NativeUInt` so it cannot be negative. About the Result var, I was indeed saving a local variable but it negatively impacts code readability so agree with you there... – Remko Apr 19 '16 at 17:12