Как избежать уничтожения результата функции функцией Free внутри?

Этот код создает 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 происходит потому, что освобождение списка строк также уничтожает результат функции. Но как этого избежать?


person user1580348    schedule 18.12.2015    source источник
comment
Нет необходимости владеть объектами. Они не являются объектами, им не выделена куча памяти.   -  person MBo    schedule 18.12.2015


Ответы (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 перетасовку.

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 при создании списка строк и установить его на True только непосредственно перед вызовом Free для него.

person David Heffernan    schedule 18.12.2015
comment
Давид, спасибо за подробное объяснение! Как бы вы решили проблему GetWindowText? - person user1580348; 18.12.2015
comment
Вы можете узнать в документах для этой функции. Они говорят вам передать длину буфера. У вас длина 1025. - person David Heffernan; 18.12.2015