【问题标题】:How to avoid function result being destroyed by Free inside function?如何避免函数结果被 Free inside 函数破坏?
【发布时间】:2016-03-25 09:49:33
【问题描述】:

这段代码创建了一个 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 的发生是因为释放 stringlist 也会破坏函数结果。但是如何避免呢?

【问题讨论】:

  • 不需要拥有对象。它们不是对象,它们没有分配堆内存。

标签: delphi delphi-10-seattle


【解决方案1】:

首先,让我们看一下实际的代码。字符串列表不包含对象。它拥有窗户把手。所以OwnsObjects 根本不合适。这将假设Objects[] 中的东西是类的Delphi 实例,并在这些实例上调用Free。这就是发生故障的地方。

您不拥有这些窗口句柄,因此您不应尝试销毁它们。

所以,不要将OwnsObjects 设置为True,问题就会消失。那就是替换这一行:

sl := TStringList.Create(True); // stringlist owns objects

用这个:

sl := TStringList.Create;

此外,您将这些对象投射到THandle。那是错误的,这并不重要。但从语义上讲,这些是窗口句柄,所以将它们转换为 HWND。事实上,无论你在哪里使用THandle,你都应该使用HWND

还有其他错误。当您调用 use GetWindowText 时,您传递的是缓冲区的大小而不是其长度。这意味着您在缓冲区的长度上撒谎。因为这些是宽字符,所以缓冲区是您声称的长度的一半。寻找以桌面窗口为父级的窗口感觉不对。


为了论证,我们假设您的字符串列表确实包含对象。在这种情况下,在理想情况下,字符串列表类将提供Extract 方法,这是从拥有的容器中删除对象而不破坏该对象的常规方法。因此,您可以改为执行OwnsObjects shuffle。

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,并且仅在调用Free 之前将其设置为True

【讨论】:

  • 大卫,感谢您的广泛解释!您将如何解决 GetWindowText 问题?
  • 您可以在文档中找到该功能。他们告诉你传递缓冲区的长度。你的长度为 1025。
猜你喜欢
  • 2020-09-21
  • 2019-06-13
  • 1970-01-01
  • 2020-02-17
  • 2021-07-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-29
相关资源
最近更新 更多