【发布时间】:2021-07-31 21:42:09
【问题描述】:
在 Windows 10 的一个 Delphi 10.4.2 win-32 VCL 应用程序中,我使用这段代码来收集一些窗口的句柄和程序路径:
function GetPathFromPID(const PID: Cardinal): string;
var
hProcess: THandle;
Path: array[0..MAX_PATH - 1] of Char;
begin
hProcess := Winapi.Windows.OpenProcess(Winapi.Windows.PROCESS_QUERY_INFORMATION or Winapi.Windows.PROCESS_VM_READ, False, PID);
if hProcess <> 0 then
try
if Winapi.PsAPI.GetModuleFileNameEx(hProcess, 0, Path, Winapi.Windows.MAX_PATH) = 0 then
RaiseLastOSError;
Result := Path;
finally
Winapi.Windows.CloseHandle(hProcess)
end
else
RaiseLastOSError;
end;
function EnumWinProc(wHandle: Winapi.Windows.HWND; aList: TStringList): Winapi.Windows.Bool; stdcall;
var
strPath: string;
IsAppMainWin: Boolean;
ProcId: Cardinal;
begin
IsAppMainWin := IsWindowVisible(wHandle) and // Visible
(GetWindow(wHandle, Winapi.Windows.GW_OWNER) = 0) and // Not owned by other windows
(GetParent(wHandle) = 0) and // Does not have any parent
(GetWindowLong(wHandle, Winapi.Windows.GWL_EXSTYLE) and Winapi.Windows.WS_EX_TOOLWINDOW = 0); // Not a tool window
if IsAppMainWin then
begin
GetWindowThreadProcessID(wHandle, ProcId);
try
strPath := GetPathFromPID(ProcId);
except
strPath := 'UnknownProgramPath';
end;
aList.AddObject(strPath, TObject(wHandle));
end;
Result := True;
end;
procedure ClearList(List: TStringList);
// https://stackoverflow.com/questions/9148659/how-to-free-objects-in-stringlist-in-delphi-7
var
i: Integer;
begin
// crash occurs here!
for i := 0 to pred(List.Count) do
List.Objects[i].Free;
List.Clear;
end;
procedure TformMain.OutputAllAppWindows;
begin
var sl := TStringList.Create;
try
sl.OwnsObjects := True;
EnumWindows(@EnumWinProc, Winapi.Windows.LPARAM(sl));
for var i := 0 to sl.Count - 1 do
begin
CodeSite.Send('window handle', Winapi.Windows.HWND(sl.Objects[i]));
CodeSite.Send('program-path sl[i]', sl[i]);
end;
ClearList(sl); // EDIT: forgot this line!
finally
sl.Free;
end;
end;
EurekaLog 报告了崩溃:
2.1 日期:2021 年 7 月 31 日星期六 22:36:53 +0200
2.2 地址:005509D2
2.5 类型:EInvalidPointer
2.6 消息:应用程序尝试释放无效或未知的内存块:$00010AE2 OBJECT [?] 0 字节。
2.7 编号:B5002468
2.8 计数:1
2.11 发送:0
这是在 EurekaLog 调用堆栈的顶部:
【问题讨论】:
-
你说崩溃发生在
ClearList,但你从来没有调用过ClearList,所以这不是真的。或者,也许您在释放TStringList(在您的实际代码中)之前正在调用ClearList?如果是这样,则错误很明显:您的ClearList将释放列表中的每个对象,但不会清除指向它的指针。因此,这些指针变得悬空。并且由于您设置了OwnsObjects = True,字符串列表的析构函数(在Free调用)将尝试销毁这些悬空指针指向的“对象”。要么自己释放对象,要么让 dtor 去做。 -
哦,还有:您放入列表中的“对象”根本不是 Delphi 对象——它们只是绝对不指向 Delphi 对象的 HWND(整数)。所以没有什么可以免费的。您不能释放不是对象指针的对象指针!不是两次,一次都不是!当你做
aList.AddObject(strPath, TObject(wHandle));你告诉Delphi,“我知道wHandle是一个整数,而不是一个对象,但请假装这个整数是一个对象。我保证当你稍后告诉我它是时我永远不会相信你一个东西”。然后,过了一会儿,你忘记了...... -
所以,tl;dr 版本:(1)您将整数作为对象,因此您不能将这些“对象”视为对象,因为它们不是。特别是,没有任何东西可以释放,你不能释放这些“对象”。不要在“随机”指针上运行 dtor。 (2) 如果您确实将真实对象放入列表中,那么您仍然会遇到错误,因为您首先会在没有
niling 引用它们的情况下自行释放它们,因此字符串列表的 dtor 会尝试“销毁“这些悬空指针所指向的东西。再次非常糟糕。 -
是的,那是因为您将
OwnsObjects设置为True。这告诉Delphi RTL“嘿,字符串列表先生,当你被释放时,请同时释放所有这些我已经给你指针的对象”。而且由于 Delphi RTL 相当听话,它会尝试在每个 HWND 上运行析构函数(它们不是指向 Delphi 对象的指针!)。因此它将尝试在“随机”指针上运行 dtor。碰撞。更简洁:当你告诉字符串列表它拥有对象时,你也告诉它“指针”是对象。但他们不是。它们只是您保存在...中的整数 (HWND)。 -
本质上,你在做
TObject(random integer).Free。与这种情况比较:如果您有aList.AddObject(strPath, TFrog.Create);,您将在每次迭代时创建一个新的TFrog对象,并将地址放入列表中的TFrog堆对象。然后你真的需要释放这些青蛙对象,要么通过告诉 RTL 去做(OwnsObjects = True),要么自己做。两者都不是。
标签: delphi winapi memory-management delphi-10.4-sydney