Win32 TreeView添加节点时突发内存异常(代码未改动)
内存异常问题排查:长期稳定代码突发
SendDlgItemMessage访问违规 问题背景
一段存储在Git仓库、稳定运行2年以上未改动的代码,今年6月突发内存访问违规(错误码0xC0000005),读取无效内存地址0x00000000000000E4。异常发生在调用SendDlgItemMessage插入树节点时,而此前向同一控件发送TVM_SETIMAGELIST消息设置图像列表的操作可正常执行。
异常相关代码细节
引发异常的getTreeNode函数
HTREEITEM getTreeNode(HWND hDlg, char *name, HTREEITEM parent) { TV_INSERTSTRUCT tvinsert = {}; tvinsert.hParent = parent; tvinsert.hInsertAfter = parent ? TVI_SORT : TVI_ROOT; tvinsert.item.mask = TVIF_TEXT | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE; tvinsert.item.pszText = name; tvinsert.item.lParam = -1; tvinsert.item.iImage = 0; tvinsert.item.iSelectedImage = 1; if (IsWindow(hDlg)) LogError("**** window ok"); else LogError("**** Window issue...."); HWND presetHDlg = GetDlgItem(hDlg, IDC_PRESET_TREE); if (IsWindow(presetHDlg)) LogError("**** Preset Tree window ok"); else LogError("**** Preset Tree Window issue...."); HTREEITEM newNode = (HTREEITEM)SendDlgItemMessage(hDlg, IDC_PRESET_TREE, TVM_INSERTITEM, 0, (LPARAM)&tvinsert); if (nodes == NULL) { // 后续代码省略 } }
前置正常执行的addPresetsTree函数
void addPresetsTree(HWND hDlg) { // Load bitmaps used in tree view HIMAGELIST hImageList = ImageList_Create(16, 16, ILC_COLOR16, 2, 10); HBITMAP hBitMap = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_OPEN_FOLDER)); if (hBitMap == NULL) LogError("*** Failed to load bitmap for IDB_OPEN_FOLDER"); else { ImageList_Add(hImageList, hBitMap, NULL); DeleteObject(hBitMap); } hBitMap = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_CLOSED_FOLDER)); if (hBitMap == NULL) LogError("*** Failed to load bitmap for IDB_CLOSED_FOLDER"); else { ImageList_Add(hImageList, hBitMap, NULL); DeleteObject(hBitMap); } hBitMap = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_PRESET)); if (hBitMap == NULL) LogError("*** Failed to load bitmap for IDB_PRESET"); else { ImageList_Add(hImageList, hBitMap, NULL); DeleteObject(hBitMap); } hBitMap = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_SELECTED_PRESET)); if (hBitMap == NULL) LogError("*** Failed to load bitmap for IDB_SELECTED_PRESET"); else { ImageList_Add(hImageList, hBitMap, NULL); DeleteObject(hBitMap); } LRESULT res = SendDlgItemMessage(hDlg, IDC_PRESET_TREE, TVM_SETIMAGELIST, 0, (LPARAM)hImageList); HTREEITEM Parent = NULL; char p[] = "RootNode"; Parent = getTreeNode(hDlg, p, Parent); }
代码调用时机
上述代码在对话框过程的WM_INITDIALOG消息中触发:
BOOL CALLBACK FindPresetDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG: InitCommonControls(); // make our tree control work // Populate preset tree and select current entry, if available addPresetsTree(hDlg); break; // 其他消息处理省略 } }
异常特征
- 两次
IsWindow()调用均返回TRUE,确认对话框和树控件句柄有效 - 4月构建版本曾正常运行,6月后所有Git旧版本均触发异常
- 栈跟踪指向
KernelBase.dll、comctl32.dll等系统模块 - 用相同代码创建的最小化测试程序可正常运行
- 重装Windows 11后异常仍存在,无法追溯系统/Visual Studio更新历史
已知分析
异常为访问违规(0xC0000005),因读取无效内存地址
0x00000000000000E4。GitHub Copilot推测直接原因是getTreeNode中SendDlgItemMessage使用无效HWND,但与实际IsWindow验证结果矛盾。其提出的可能根本原因包括:
GetDlgItem获取的IDC_PRESET_TREE句柄无效(与当前验证结果冲突)hDlg参数未初始化或损坏(同样与验证结果冲突)
建议方向:调用GetDlgItem前验证hDlg有效性;检查对话框资源中IDC_PRESET_TREE是否存在
排查方向与可能原因
- 系统控件库行为变更:Windows更新可能修改了
comctl32.dll中树控件处理TVM_INSERTITEM消息的逻辑,比如对TV_INSERTSTRUCT字段的校验更严格。需确认tvinsert的所有字段是否符合当前系统控件的要求,比如pszText的内存有效性、字段取值范围等。 - 控件初始化时序问题:
WM_INITDIALOG期间调用InitCommonControls的时序可能无法满足系统更新后的控件初始化要求。尝试将InitCommonControls移至程序启动时执行,而非对话框初始化阶段。 - 图像列表生命周期问题:
addPresetsTree中创建的hImageList在设置给树控件后,可能因系统更新后的引用逻辑变化,导致插入节点时图像列表已无效。可尝试将hImageList设为全局变量,确保其生命周期覆盖树节点插入过程。 - 编译器/链接器行为变更:Visual Studio 2022更新可能改变了代码生成逻辑,比如结构体对齐、栈布局变化。检查
TV_INSERTSTRUCT的内存布局是否与系统期望一致,可显式指定结构体对齐方式(如#pragma pack(push, 8))。 - 对话框资源隐性变更:虽然代码未改动,但Visual Studio更新可能隐性修改了对话框资源文件(.rc),比如
IDC_PRESET_TREE的样式、类别属性。对比正常版本与异常版本的.rc文件,检查树控件定义是否一致。 - 内存隐性损坏:系统更新后底层内存分配逻辑变化,导致此前未触发的内存越界、句柄误用等问题显现。启用Visual Studio的地址 sanitizer 或使用Application Verifier工具,排查内存 corruption 问题。
- 字符集/编码问题:系统更新后默认字符集变化,导致
char*类型的pszText编码与控件期望不匹配。尝试将char*改为TCHAR*,并使用TEXT宏包裹字符串,确保与项目字符集设置一致。
内容的提问来源于stack exchange,提问作者John D
相关产品推荐
相关产品推荐

