如何避免函数结果被 Free inside 函数破坏?
How to avoid function result being destroyed by Free inside function?
此代码创建一个 AV:
function PAIsMainAppWindow(Wnd: THandle): Boolean;
var
ParentWnd: THandle;
ExStyle: DWORD;
begin
if IsWindowVisible(Wnd) then
begin
ParentWnd := THandle(GetWindowLongPtr(Wnd, GWLP_HWNDPARENT));
ExStyle := GetWindowLongPtr(Wnd, GWL_EXSTYLE);
Result := ((ParentWnd = 0) or (ParentWnd = GetDesktopWindow)) and
((ExStyle and WS_EX_TOOLWINDOW = 0) or (ExStyle and WS_EX_APPWINDOW <> 0));
end
else
Result := False;
end;
function PAEnumTaskWindowsProc(Wnd: THandle; List: TStrings): Boolean; stdcall;
var
Caption: array [0..1024] of Char;
begin
if PAIsMainAppWindow(Wnd) and (GetWindowText(Wnd, Caption, SizeOf(Caption)) > 0) then
List.AddObject(ExtractFileName(GetProcessNameFromWnd(Wnd)), Pointer(Wnd));
Result := True;
end;
function PAGetTaskWindowHandleFromProcess(const AProcessName: string): THandle;
var
sl: TStringList;
i: Integer;
begin
Result := 0;
sl := TStringList.Create(True); // stringlist owns objects
try
if EnumWindows(@PAEnumTaskWindowsProc, LPARAM(sl)) then
begin
for i := 0 to sl.Count - 1 do
begin
if SameText(AProcessName, sl[i]) then
begin
Result := THandle(sl.Objects[i]);
BREAK;
end;
end;
end;
finally
sl.Free; // AV!
end;
end;
ChromeHandle := PAGetTaskWindowHandleFromProcess('chrome.exe');
很明显,AV 的发生是因为释放字符串列表也破坏了函数结果。但是如何避免呢?
首先,让我们看一下实际的代码。字符串列表不包含对象。它拥有 window 个句柄。所以 OwnsObjects
根本不合适。这将假设 Objects[]
中的事物是 classes 的 Delphi 个实例,并在这些实例上调用 Free
。那就是失败发生的地方。
您不拥有这些 window 把手,因此您不应试图破坏它们。
所以,不要将 OwnsObjects
设置为 True
,问题就会消失。即替换此行:
sl := TStringList.Create(True); // stringlist owns objects
有了这个:
sl := TStringList.Create;
此外,您正在将这些对象转换为 THandle
。这是错误的,但这并不重要。但从语义上讲,这些是 window 句柄,所以将它们转换为 HWND
。事实上,无论你在哪里使用 THandle
你都应该使用 HWND
.
还有其他错误。当您调用 use GetWindowText
时,您传递的是缓冲区的大小而不是它的长度。这意味着你在说缓冲区有多长。因为这些是宽字符,所以缓冲区只有您声称的一半长。寻找以桌面 window 为父级的 windows 感觉不对。
让我们假设,为了论证,您的字符串列表确实包含对象。在这种情况下,在理想情况下,字符串列表 class 将提供一个 Extract
方法,这是从拥有容器中删除对象而不破坏该对象的常规方法。因此,您可以执行 OwnsObjects
随机播放。
if SameText(AProcessName, sl[i]) then
begin
sl.OwnsObjects := False;
Result := TSomeObject(sl.Objects[i]);
sl.Objects[i] := nil;
sl.OwnsObjects := True;
BREAK;
end;
如果您愿意,可以在创建字符串列表时将 OwnsObjects
设置为 False
,并且仅在调用 [=15] 之前将其设置为 True
=] 就可以了。
此代码创建一个 AV:
function PAIsMainAppWindow(Wnd: THandle): Boolean;
var
ParentWnd: THandle;
ExStyle: DWORD;
begin
if IsWindowVisible(Wnd) then
begin
ParentWnd := THandle(GetWindowLongPtr(Wnd, GWLP_HWNDPARENT));
ExStyle := GetWindowLongPtr(Wnd, GWL_EXSTYLE);
Result := ((ParentWnd = 0) or (ParentWnd = GetDesktopWindow)) and
((ExStyle and WS_EX_TOOLWINDOW = 0) or (ExStyle and WS_EX_APPWINDOW <> 0));
end
else
Result := False;
end;
function PAEnumTaskWindowsProc(Wnd: THandle; List: TStrings): Boolean; stdcall;
var
Caption: array [0..1024] of Char;
begin
if PAIsMainAppWindow(Wnd) and (GetWindowText(Wnd, Caption, SizeOf(Caption)) > 0) then
List.AddObject(ExtractFileName(GetProcessNameFromWnd(Wnd)), Pointer(Wnd));
Result := True;
end;
function PAGetTaskWindowHandleFromProcess(const AProcessName: string): THandle;
var
sl: TStringList;
i: Integer;
begin
Result := 0;
sl := TStringList.Create(True); // stringlist owns objects
try
if EnumWindows(@PAEnumTaskWindowsProc, LPARAM(sl)) then
begin
for i := 0 to sl.Count - 1 do
begin
if SameText(AProcessName, sl[i]) then
begin
Result := THandle(sl.Objects[i]);
BREAK;
end;
end;
end;
finally
sl.Free; // AV!
end;
end;
ChromeHandle := PAGetTaskWindowHandleFromProcess('chrome.exe');
很明显,AV 的发生是因为释放字符串列表也破坏了函数结果。但是如何避免呢?
首先,让我们看一下实际的代码。字符串列表不包含对象。它拥有 window 个句柄。所以 OwnsObjects
根本不合适。这将假设 Objects[]
中的事物是 classes 的 Delphi 个实例,并在这些实例上调用 Free
。那就是失败发生的地方。
您不拥有这些 window 把手,因此您不应试图破坏它们。
所以,不要将 OwnsObjects
设置为 True
,问题就会消失。即替换此行:
sl := TStringList.Create(True); // stringlist owns objects
有了这个:
sl := TStringList.Create;
此外,您正在将这些对象转换为 THandle
。这是错误的,但这并不重要。但从语义上讲,这些是 window 句柄,所以将它们转换为 HWND
。事实上,无论你在哪里使用 THandle
你都应该使用 HWND
.
还有其他错误。当您调用 use GetWindowText
时,您传递的是缓冲区的大小而不是它的长度。这意味着你在说缓冲区有多长。因为这些是宽字符,所以缓冲区只有您声称的一半长。寻找以桌面 window 为父级的 windows 感觉不对。
让我们假设,为了论证,您的字符串列表确实包含对象。在这种情况下,在理想情况下,字符串列表 class 将提供一个 Extract
方法,这是从拥有容器中删除对象而不破坏该对象的常规方法。因此,您可以执行 OwnsObjects
随机播放。
if SameText(AProcessName, sl[i]) then
begin
sl.OwnsObjects := False;
Result := TSomeObject(sl.Objects[i]);
sl.Objects[i] := nil;
sl.OwnsObjects := True;
BREAK;
end;
如果您愿意,可以在创建字符串列表时将 OwnsObjects
设置为 False
,并且仅在调用 [=15] 之前将其设置为 True
=] 就可以了。