Search code examples
delphipointersenumsdelphi-xe4window-handles

Delphi Pointers, Arrays, Handles


I can't get my head around pointers using a custom record and making array of records then a pointer to that record, specifically what I want to achieve is to make a record for each top level window handle with the given classname, meaning there is more than 1, for each window I use EnumChildWindow to obtain handles to child windows with in. I wanted to make record of each of these and pass it to a combobox with title and make the record an object of that item so I can access each recorded later on via selecting it.

My problem is my handling of pointers, I keep getting access denied on the first instance of adding any to one of the array records.

Breaks here

Param[Form1.iEnumWin].MainHwnd:= aHwnd;

here is the full code I am using so people can get a better understanding on what I am trying to do.

implementation

{$R *.dfm}
type
  TMyEnumParam = record
   sTitle: String;
   MainHwnd: Hwnd;
   InTxtHwnd: Hwnd;
   OutTxtHwnd: Hwnd;
   NickListHwnd: Hwnd;
end;
 PMyEnumParam = ^TMyEnumParam;

 type
 ATMyEnumParam = Array[0..9] of PMyEnumParam;
 PATMyEnumParam = ^ATMyEnumParam;

{ Get the window Title based on Hwnd }
function GetWindowTitle(HWND: HWND): string;
begin
  SetLength(Result, 255);
  SetLength(Result, GetWindowText(HWND, PChar(Result), 255));
end;

{ Get the Classname based on Hwnd }
function GetWindowClass(HWND: HWND): string;
begin
  SetLength(Result, 255);
  SetLength(Result, GetClassName(HWND, PChar(Result), 255));
end;

{ EnumChildWidows Callback Add to our records }
Function EnumChildProc(aHwnd: Hwnd; Param: PMyEnumParam): Boolean; stdcall;
begin
     if ((GetDlgCtrlID(aHwnd) = 202) and (isWindowVisible(aHwnd) = True)) then
      Param.InTxtHwnd:= aHwnd;

     if ((GetDlgCtrlID(aHwnd) = 203) and (isWindowVisible(aHwnd) = True)) then
       Param.OutTxtHwnd:= aHwnd;

     if ((GetDlgCtrlID(aHwnd) = 1789) and (isWindowVisible(aHwnd) = True)) then
      Param.NickListHwnd:= aHwnd;

      Result:= True;
end;

{ EnumWindow fill our array of records for each window }
function EnumWindowsProc(aHwnd: HWND; Param: PATMyEnumParam): BOOL; stdcall;
begin
  Result := True;
  if GetWindowClass(aHwnd) = 'DlgGroupChat Window Class' then
  begin
   Param[Form1.iEnumWin].MainHwnd:= aHwnd;
   Param[Form1.iEnumWin].sTitle:=  GetWindowTitle(aHwnd);
   EnumChildWindows(aHwnd, @EnumChildProc, LParam(@Param[Form1.iEnumWin]));
   Form1.cbbRooms.AddItem(Param[Form1.iEnumWin].sTitle, TObject(Param[form1.iEnumWin]));
   inc(Form1.iEnumWin);
  end;
end;

{ On change display room Title for each item }
procedure TForm1.cbbRoomsChange(Sender: TObject);
var
  i: Integer;
  aHwnd: PMyEnumParam;
begin
  i := cbbRooms.ItemIndex;
  if cbbRooms.ItemIndex <> -1 then
  begin
    aHwnd:=  PMyEnumParam(cbbRooms.Items.Objects[i]);
    if aHwnd.MainHwnd > 0 then
    begin
     ShowMessage(aHwnd.sTitle);
    end;
  end;

end;

{ Call EnumWindows and fill our array records }
procedure TForm1.FormCreate(Sender: TObject);
var
 arInfo: PATMyEnumParam;
begin
  iEnumWin:= 0;
  EnumWindows(@EnumWindowsProc, LParam(@arInfo));
end;

Please if anyone can point (no pun intended) me in the right direction I would be grateful.


Solution

  • There are many things wrong with your code. Here's a non-exhaustive list:

    1. You don't allocate any storage for your arrays.2
    2. You pass ^PATMyEnumParam to EnumWindows which you then cast to PATMyEnumParam in the callback.
    3. Your arrays are fixed length and you make no attempt to handle out of bounds access to the arrays.

    But your biggest problem is that your code is attempting to run before you can walk. It has full complexity and all the functionality that you need. Yet you cannot yet manage to make a single successful call to EnumWindows.

    My biggest piece of advice here is not in the detail, but the generality of problem solving. Start by writing a simple piece of code. Understand it. Then enhance it.

    So, in that vein, here is how to make a call to EnumerateWindows:

    program EnumWindowsDemo_17620346;
    
    {$APPTYPE CONSOLE}
    
    uses
      System.SysUtils, Winapi.Windows, Generics.Collections;
    
    type
      TWindowInfo = record
        Handle: HWND;
        // expand with more fields in due course
      end;
    
    function EnumWindowProc(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
    var
      WindowList: TList<TWindowInfo>;
      WindowInfo: TWindowInfo;
    begin
      WindowList := TList<TWindowInfo>(lParam);
      WindowInfo.Handle := hwnd;
      WindowList.Add(WindowInfo);
      Result := True;
    end;
    
    procedure Main;
    var
      WindowList: TList<TWindowInfo>;
      WindowInfo: TWindowInfo;
    begin
      WindowList := TList<TWindowInfo>.Create;
      try
        EnumWindows(@EnumWindowProc, LPARAM(WindowList));
        for WindowInfo in WindowList do
          Writeln(WindowInfo.Handle);
      finally
        WindowList.Free;
      end;
    end;
    
    begin
      Main;
      Readln;
    end.
    

    Starting from here you can expand this concept, because all the tricky parts are already taken care of. Specifically the pointer, casting and memory management.