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;
核心错误分析
回调函数参数签名不符合Windows API规范
EnumWindows要求的回调函数标准签名为:function EnumWindowsProc(Wnd: HWND; lParam: LPARAM): BOOL; stdcall;你将第二个参数声明为
var EI: TEnumWindowsInfo,会导致参数传递逻辑错误:- stdcall约定下,
var参数以指针形式传递,但EnumWindows实际传递的是EI结构体的地址(作为整数类型的LPARAM)。这种类型不匹配会触发非法内存访问,直接破坏堆的结构完整性,导致后续GetMem/FreeMem操作抛出异常。
- stdcall约定下,
局部变量
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
相关产品推荐
相关产品推荐

