EnumWindows 中 GetMem 后内存损坏

cas*_*smu 0 delphi

在通过进程 ID 和窗口标题枚举窗口时,我有两种回调函数的实现。

第一个使用固定大小的字符数组作为缓冲区并且工作正常(据我所知)。

另一个使用 PChar 缓冲区并使用 GetMem 分配它,GetMem在离开 GetWindowHandleByPID 函数后,我在不同的内部出现异常。

我的第二个错误在哪里?

TEnumWindowsInfo = record
  ProcessID: DWORD;
  HWND: THandle;
  TitleUp: string;
end;

function GetWindowHandleByPID(const hPID: THandle): THandle;
var
  EI: TEnumWindowsInfo;
begin
  EI.ProcessID := hPID;
  EI.HWND := 0;
  EI.TitleUp := 'BLA';
  EnumWindows(@OnEnumWindowsProc, Integer(@EI));
  Result := EI.HWND;
end;


function OnEnumWindowsProc(Wnd: DWORD; var EI: TEnumWindowsInfo): BOOL; stdcall;
var
  pidMatch, titleMatch: BOOL;
  buf: array [0..255] of Char;
  titleAsUpper: string;
begin
  // check PID: GetWindowThreadProcessID
  pidMatch := true;

  // check window title
  GetWindowText(Wnd, buf, 255);
  titleAsUpper := AnsiUpperCase(buf);
  titleMatch := EI.TitleUp = titleAsUpper;

  Result := not (pidMatch and titleMatch);  // continue if not found

  if not Result then
  begin
    EI.HWND := WND;
  end;
end;

function OnEnumWindowsProc_MEM_CORRUPT(Wnd: DWORD; var EI: TEnumWindowsInfo): BOOL; stdcall;
var
  pidMatch, titleMatch: BOOL;
  buffer: PChar;
  len: Integer;
  titleAsUpper: string;
begin
  // check PID: GetWindowThreadProcessID
  pidMatch := true;

  // check window title
  len := GetWindowTextLength(Wnd) + 1; // +1 -> null-terminated
  if len > 1 then
  begin
    try
      GetMem(buffer, len);
      GetWindowText(Wnd, buffer, len);
      titleAsUpper := AnsiUpperCase(buffer);
    finally
      FreeMem(buffer);
    end;
    titleMatch := EI.TitleUp = titleAsUpper;
  end;

  Result := not (pidMatch and titleMatch);  // continue if not found

  if not Result then
  begin
    EI.HWND := WND;
  end;
end;
Run Code Online (Sandbox Code Playgroud)

Rem*_*eau 7

在 中OnEnumWindowsProc(),您要传递到的缓冲区GetWindowText()已根据您声明的大小进行充分分配GetWindowText()。您分配了 256Char秒,但声明了 255Char秒,这意味着GetWindowText()最多可以检索 254Char秒,加上空终止符。因此,不存在破坏内存的机会。

但是,在 中OnEnumWindowsProc_MEM_CORRUPT(),您没有为缓冲区分配足够的内存。GetMem()对字节而不是字符进行操作。在 Delphi 2009 及更高版本中,GetWindowText()映射到GetWindowTextW(),这意味着它输出 Unicode UTF-16 文本而不是 ANSI 文本。是Sizeof(Char)2 个字节,而不是代码假设的 1 个字节。因此,您只分配了缓冲区所需的一半字节,但声明了完整大小GetWindowText/W(),因此GetWindowText/W()最终会溢出缓冲区,损坏内存。要解决这个问题,您需要将分配的大小乘以SizeOf(Char)

GetMem(buffer, len * SizeOf(Char));
Run Code Online (Sandbox Code Playgroud)

就我个人而言,我不会使用GetMem()这种代码,我会使用动态Char数组,甚至使用 aString代替,例如:

function OnEnumWindowsProc_MEM_CORRUPT(Wnd: DWORD; var EI: TEnumWindowsInfo): BOOL; stdcall;
var
  ...
  buffer: array of Char; // or String
  len: Integer;
  ...
begin
  ...

  len := GetWindowTextLength(Wnd);
  if len > 0 then
  begin
    // for a Char array:
    Inc(len);
    SetLength(buffer, len);
    GetWindowText(Wnd, PChar(buffer), len);

    // for a String:
    SetLength(buffer, len);
    GetWindowText(Wnd, PChar(buffer), len+1);

    ...
  end;

  ...
end;
Run Code Online (Sandbox Code Playgroud)

附注:

调用时EnumWindows(),您应该使用LPARAM(...)而不是在对第二个参数中的地址Integer(...)进行类型转换时使用@EI,否则如果编译为 64 位可执行文件,您的代码将无法正常工作。

此外,您不需要使用AnsiUpperCase()后跟来不operator=区分大小写地比较两个字符串。RTL 具有用于此目的的函数,例如SysUtils.SameText()

titleMatch := SameText(buffer, EI.TitleUp);
Run Code Online (Sandbox Code Playgroud)


mra*_*bat 6

我假设您使用的是Delphi2009或更高版本。在这种情况下,GetWindowText 是 Unicode,而 Getmem 应该是

GetMem(buffer, len*sizeof(char));
Run Code Online (Sandbox Code Playgroud)

在另一种情况下,我猜你很幸运,因为你在堆栈上保留了足够大的缓冲区。