Search code examples
arrayswindowsdelphicallbackdelphi-xe6

Can dynamic arrays be used as parameters to Windows callback functions?


I am redoing some code to be 64-bit ready. This uses EnumWindows with callback to return a list of running delphi applications (apart from the IDE and itself) which are then killed off. Originally it used a TStringlist to hold the handles of those applications. I wanted to change it to collect the handles directly in numeric form. I have arrived at a very satisfactory solution shown below using a generic TList to collect the handles.

Along the way, I initially tried using a dynamic array - it did not work. After verifying the TList solution, I revisited it, out of academic interest, and tried every which way to implement it using a dynamic array instead - all without success. I could not find any prohibition in the documentation, though I did come across this note in Rudy V's blog: "Delphi strings and dynamic arrays should not be passed as reference counted types to API functions anyway..."

So, I am just seeking a "ruling" that dynamic arrays can or cannot be used as parameters to callback functions.

type
  THandleList=Tlist<THandle>;
const
  ReqdClass: string = 'TApplication' ;


procedure KillWindowViaHandle(Ahwnd:THandle; Amsg: Cardinal=WM_CLOSE);
begin
  PostMessage(Ahwnd, Amsg, 0, 0);
end;

// Get Active "User" Applications (except for bds.exe & caller). Relies on top
// level window having classname of TApplication. Returns list of handles.

function FindActiveUSERApps(AHandle: HWND; AList: lparam): BOOL ; stdcall;
var
  classname: string;
  pid: DWORD;
  imagename: string;
begin
  Result := true;         // keep it going .. want them all
  GetWindowThreadProcessID(AHandle, @pid);  // not interested in ThreadID returned
  imagename := GetProcessFileName(pid) ;
  SetLength(ClassName, 255);
  SetLength(ClassName, GetClassName(AHandle, PChar(className), Length(className)));
  if ( ansicontainstext(classname, ReqdClass) ) and
     ( not ansisametext(ImageName, 'bds.exe')) and
     ( not ansisametext(ImageName, ExtractFileName(Application.ExeName))) then
    THandleList(Alist).Add(AHandle) ;
end;


function GetActiveUSERApps(AList: THandleList): boolean;
begin
  AList.Clear;
  EnumWindows(@FindActiveUSERApps, lparam(AList) );
  result  := Alist.Count > 0;
end;


function KillActiveUSERApps: boolean;
var
  i : integer;
  ActiveList: THandleList;
begin
  result := false;
  ActiveList := THandleList.Create;
  try
    GetActiveUSERApps(ActiveList);
    for i:= 0 to activelist.Count - 1   do
      KillWindowviaHandle( ActiveList[i] );

    // noticed that some processes were resistant to being killed via WM_CLOSE.
    // So try gentle approach first, and then if necessary, use the big stick.
    GetActiveUSERApps(activeList);
    for i:= 0 to activelist.Count - 1   do
      KillWindowviaHandle( ActiveList[i], WM_QUIT );

    result  := true;
  finally
    ActiveList.Free;
  end;
end;

Solution

  • Without actually seeing your implementation using a dynamic array, I assume you add elements to this array by expanding it with SetLength. This in turn changes the array variable which internally is a pointer. Thus the calling method still uses the old pointer variable to the no more existent dynamic array it passed as a parameter.

    You can overcome this by using a pointer to a dynamic array.

    type
      THandleList = TArray<THandle>;
      PHandleList = ^THandleList;
    
    function FindActiveUSERApps(AHandle: HWND; AList: lparam): BOOL ; stdcall;
    var
      ...
      PList: PHandleList;
    begin
      ...
      PList := PHandleList(AList);
      SetLength(PList^, Length(PList^) + 1);
      PList^[High(PList^)] := AHandle;
    end;
    
    
    function GetActiveUSERApps(var AList: THandleList): boolean;
    begin
      AList.Clear;
      EnumWindows(@FindActiveUSERApps, lparam(@AList) );
      result  := Alist.Count > 0;
    end;
    

    That said, I personally prefer the TList approach for simplicity and clarity. Especially as you can easily return a dynamic array from it with AList.ToArray.