You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EnumWindows回调中使用GetMem分配PChar引发内存损坏问题排查

枚举窗口时GetMem分配缓冲区引发内存异常的问题分析

在根据进程ID和窗口标题枚举窗口的场景中,你实现了两个回调函数:

  • 第一个使用固定大小Char数组作为缓冲区,运行正常;
  • 第二个使用GetMem分配PChar缓冲区,退出GetWindowHandleByPID函数后,后续内部GetMem操作会抛出异常。

问题代码

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;

核心错误分析

  1. 回调函数参数签名不符合Windows API规范
    EnumWindows要求的回调函数标准签名为:

    function EnumWindowsProc(Wnd: HWND; lParam: LPARAM): BOOL; stdcall;
    

    你将第二个参数声明为var EI: TEnumWindowsInfo,会导致参数传递逻辑错误:

    • stdcall约定下,var参数以指针形式传递,但EnumWindows实际传递的是EI结构体的地址(作为整数类型的LPARAM)。这种类型不匹配会触发非法内存访问,直接破坏堆的结构完整性,导致后续GetMem/FreeMem操作抛出异常。
  2. 局部变量titleMatch未初始化
    当len <= 1(窗口无标题或标题为空)时,titleMatch未被赋值就直接参与逻辑运算。Pascal局部变量默认未初始化,其值为随机垃圾值,会导致枚举逻辑出现不可预测的行为,甚至进一步加剧内存破坏。

修正后的回调函数示例

function OnEnumWindowsProc_Fixed(Wnd: HWND; lParam: LPARAM): BOOL; stdcall;
var
  EI: ^TEnumWindowsInfo;
  pidMatch, titleMatch: BOOL;
  buffer: PChar;
  len: Integer;
  titleAsUpper: string;
  windowPID: DWORD;
begin
  EI := Pointer(lParam);
  pidMatch := False;
  titleMatch := False;

  // 补全PID匹配逻辑
  GetWindowThreadProcessId(Wnd, @windowPID);
  pidMatch := (windowPID = EI^.ProcessID);

  // 检查窗口标题
  len := GetWindowTextLength(Wnd) + 1;
  if len > 1 then
  begin
    GetMem(buffer, len);
    try
      GetWindowText(Wnd, buffer, len);
      titleAsUpper := AnsiUpperCase(buffer);
      titleMatch := (EI^.TitleUp = titleAsUpper);
    finally
      FreeMem(buffer);
    end;
  end;

  // 未匹配到目标窗口则继续枚举
  Result := not (pidMatch and titleMatch);
  if not Result then
  begin
    EI^.HWND := Wnd;
  end;
end;

调用修正后的回调时,保持原有调用逻辑即可:

EnumWindows(@OnEnumWindowsProc_Fixed, LPARAM(@EI));

内容的提问来源于stack exchange,提问作者casiosmu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 21:55:15