Search code examples
delphiwinapidelphi-11-alexandria

Calculating window handle from module file path gets wrong results


In a 32-bit VCL Application in Windows 10 in Delphi 11 Alexandria, I am trying to get the window handle of a running main task from the task's module path:

type
  TFindWindowRec = record
  ModuleToFind: string;
  FoundHWnd: HWND;
end;

function EnumWindowsCallBack(aHandle: HWND; var FindWindowRec: TFindWindowRec): BOOL; stdcall;
const
  C_FileNameLength = 256;
var
  WinFileName: string;
  PID, hProcess: DWORD;
  Len: Byte;
begin
  Result := True;

  SetLength(WinFileName, C_FileNameLength);
  GetWindowThreadProcessId(aHandle, PID);
  hProcess := OpenProcess(PROCESS_ALL_ACCESS, False, PID);
  Len := GetModuleFileNameEx(hProcess, 0, PChar(WinFileName), C_FileNameLength);
  if Len > 0 then
  begin
    SetLength(WinFileName, Len);
    if SameText(WinFileName, FindWindowRec.ModuleToFind) then
    begin
      Result := False;
      FindWindowRec.FoundHWnd := aHandle;
    end;
  end;
end;

var
  FindWindowRec: TFindWindowRec;

function TformMain.GetmainWindowHandleFRomProcessPath(aProcessPath: string): HWND;
begin
  Result := 0;
  CodeSite.Send('TformMain.GetmainWindowHandleFRomProcessPath: aProcessPath', aProcessPath);
  FindWindowRec.ModuleToFind := aProcessPath;
  FindWindowRec.FoundHWnd := 0;
  EnumWindows(@EnumWindowsCallback, Integer(@FindWindowRec));
  if FindWindowRec.FoundHWnd <> 0 then
  begin
    Result := FindWindowRec.FoundHWnd;
    CodeSite.Send('TformMain.GetmainWindowHandleFRomProcessPath: Result', Result);
  end;
end;

When I do this with:

GetmainWindowHandleFRomProcessPath('c:\windows\system32\notepad.exe');

... then I get the correct window handle.

When I do this with:

GetmainWindowHandleFRomProcessPath('C:\Program Files (x86)\Embarcadero\Studio\22.0\bin\bds.exe');

... then I get a WRONG (non-existing) window handle!

Why is this happening? How do I get the correct window handle?


Solution

  • The discussion with Remy and Andreas lead me to this successful working answer:

    type
      TFindWindowRec = record
      ModuleToFind: string;
      FoundHWnd: HWND;
    end;
    
    // The `RzShellUtils` unit is from Ray Konopka's Signature Library available from GetIt:
    function PathsAreSamePIDL(const Path1, Path2: string): Boolean;
    begin
      var AIL1: PItemIdList;
      var AIL2: PItemIdList;
      RzShellUtils.ShellGetIdListFromPath(Path1, AIL1);
      RzShellUtils.ShellGetIdListFromPath(Path2, AIL2);
      var CompResult:= RzShellUtils.CompareAbsIdLists(AIL1, AIL2);
      Result := CompResult = 0;
    end;
    
    function EnumWindowsCallBack(aHandle: HWND; var FindWindowRec: TFindWindowRec): BOOL; stdcall;
    const
      C_FileNameLength = MAX_PATH;
      PROCESS_QUERY_LIMITED_INFORMATION = $1000;
    var
      WinFileName: string;
      PID, hProcess: DWORD;
      Len: Byte;
    begin
      Result := True;
    
      SetLength(WinFileName, C_FileNameLength);
      GetWindowThreadProcessId(aHandle, PID);
      hProcess := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, PID);
      Len := GetModuleFileNameEx(hProcess, 0, PChar(WinFileName), C_FileNameLength);
      CloseHandle(hProcess);
      if Len > 0 then
      begin
        SetLength(WinFileName, Len);
        //if SameText(WinFileName, FindWindowRec.ModuleToFind) then
        if PathsAreSamePIDL(WinFileName, FindWindowRec.ModuleToFind) then
        begin
          var IsVisible := IsWindowVisible(aHandle);
          if not IsVisible then EXIT;
          var IsOwned := GetWindow(aHandle, GW_OWNER) <> 0;
          if IsOwned then EXIT;
          var IsAppWindow := GetWindowLongPtr(aHandle, GWL_EXSTYLE) and WS_EX_APPWINDOW <> 0;
          if not IsAppWindow then EXIT;
    
          Result := False;
          FindWindowRec.FoundHWnd := aHandle;
        end;
      end;
    end;
    
    function TformMain.GetMainWindowHandleFromProcessPath(aProcessPath: string): HWND;
    var
      FindWindowRec: TFindWindowRec;
    begin
      Result := 0;
      FindWindowRec.ModuleToFind := aProcessPath;
      FindWindowRec.FoundHWnd := 0;
      EnumWindows(@EnumWindowsCallback, LPARAM(@FindWindowRec));
      if FindWindowRec.FoundHWnd <> 0 then
      begin
        Result := FindWindowRec.FoundHWnd;
      end;
    end;
    

    I don't understand why the person who moved the discussion to another page deleted the latest comments. Was there anything forbidden in those deleted comments?

    Again: Thank you to Remy and Andreas!