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;
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
.