编程日常:创建枚举窗体的线程, 结果无法找到目标窗体

 [简介]
常用网名: 猪头三
出生日期: 1981.XX.XX
个人网站: http://www.x86asm.com
QQ交流: 643439947
编程生涯: 2001年~至今[共15年]
职业生涯: 13年
开发语言: C/C++、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python
开发工具: Visual Studio、Delphi、XCode、Eclipse
技能种类: 逆向 驱动 磁盘 文件
研发领域: Windows应用软件安全/Windows系统内核安全/Windows系统磁盘数据安全
项目经历: 磁盘性能优化/文件系统数据恢复/文件信息采集/敏感文件监测跟踪/网络安全检测

[序言]
最近一直在用MFC来写程序. 由于5~6年没有触碰C/C++了, 导致犯下很多低级错误. 再次撰写一篇文章来记录一个非常低级且无知错误.

[请看下面代码, 这个代码是错误的, 如果你一眼就能看出错误, 那么这个文章你就不用往下看了]
// 1.枚举所有窗体并找到目标窗体
BOOL CALLBACK fun_EnumWindowsProc(HWND hwnd_param_Window, LPARAM lParam)
{
    // 获取窗体标题
	wchar_t wchar_Window_Cpation[1024];
	::GetWindowText(hwnd_param_Window,
                    wchar_Window_Cpation,
                    sizeof(wchar_Window_Cpation)*sizeof(wchar_t));

	// 找到"80x86汇编小站"窗体
	if (StrStr(wchar_Window_Cpation, L"80x86汇编小站"))
	{
		return FALSE;
	}

	return TRUE;

}// End fun_EnumWindowsProc()
这是一个枚举目标窗体的代码, 需要查找目标窗体“80x86汇编小站”, 代码很简单, 1分钟就写完了, 然后放在主线程测试. 结果非常完美, 找到目标窗体.

[怪异的现象出现]
源码运行成功, 为了软件有更好的体验, 我把枚举窗体的代码放入子线程去执行, 也就是用AfxBeginThread开一个线程来运行枚举窗体功能. 结果让我犯傻, 竟然找不到目标窗体. 找不到也就算了, 还不会报错, 这简直是扯淡. 就因为这样这个问题, 导致我花费了6个小时才解决.

[我是如何在6个小时解决这个问题]
1> 我没有详细检查代码, 这个是非常愚蠢的行为. 而是直接启动调试器, 单步跟踪, 结果是一个悲剧. Windows10默认就有几百个窗体被创建, 这时我就像傻子一样, 单击鼠标左键几百次,  好在傻子有傻福, 竟然在单步跟踪能触发错误. 结果是heapfree.lock出现了问题. 这时我马上查看堆栈器, 结果又一次傻了, 异常竟然没有在我的代码内部触发.

2> heapfree.lock错误是一个很关键的信息. 这时我脑海马上反射出: 肯定又是数组溢出了, 因为我有一个数组: wchar_t wchar_Window_Cpation[1024]; 好吧, 既然断定是数组溢出, 那我就把1024修改成1024*10, 问题依然没解决.
3> 既然加大数值不行, 那肯定是线程的堆栈不够大, 默认是1MB. 想到这个问题我就急忙马上去修改10485760, 然后再一次测试, 结果问题依旧.

经过以上的方式排除, 我实在没辙了, 各种郁闷. 又去翻遍了整个互联网和MSDN, 都没有提示说窗体枚举不能用在子线程, 然后自己又开始胡思乱想, 什么 回调函数递归太深, 回调函数是否是异步, 等等, 尼玛这简直是各种犯贱.

[还是回归正途进行代码审查]
各种所谓装逼的经验, 都派不上用场, 折腾了将近3个多小时. 最后还是决定好好检查代码. 首先检查GetWindowText()这个函数, 结果发现“sizeof(wchar_Window_Cpation)*sizeof(wchar_t)” 看到这个代码让我瞬间想把电脑砸烂了, 不是"*"呀,应该是用"/"呀, 马上修正过来"sizeof(wchar_Window_Cpation)/sizeof(wchar_t)" , 再测试一下, 运行成功了. 这下简直是哭死我. 我就是一个大傻逼.

[继续优化代码-1]
既然发现是数组溢出的问题, 那么就不要再偷懒wchar_t wchar_Window_Cpation[1024]这样的写法了, 万一有些窗体标题超过1024个字符呢? 所以需要修改动态长度且代码需要改进, 封装一个函数来获取窗体标题并且支持更长的标题. 函数名为:mpr_sffun_GetWindowsText()
// 2.枚举所有窗体并找到目标窗体
BOOL CALLBACK fun_EnumWindowsProc(HWND hwnd_param_Window, LPARAM lParam)
{
    // 获取窗体标题
	CStringW str_Window_Caption;
    mpr_sffun_GetWindowsText(hwnd_param_Window, str_Window_Caption);

	if (str_Window_Caption.GetLength() != 0)
	{
        // 找到"80x86汇编小站"窗体
		if (str_Window_Caption.Find(L"80x86汇编小站") != -1)
		{
			return FALSE;
		}
	}

	return TRUE;

}// End fun_EnumWindowsProc()
// 获取窗体标题
void mpr_sffun_GetWindowsText(HWND hwnd_param_Window, CStringW & str_param_WindowText)
{
	int int_TextLen;
	PWSTR pstr_WindowText = NULL;
	str_param_WindowText  = CStringW(L"");

	int_TextLen = ::GetWindowTextLength(hwnd_param_Window);
	if (int_TextLen != 0)
	{
		pstr_WindowText = (PWSTR)::VirtualAlloc((LPVOID)NULL,
			                                    (DWORD)(int_TextLen + 1),
			                                    MEM_COMMIT,
			                                    PAGE_READWRITE);

		::GetWindowText(hwnd_param_Window, pstr_WindowText, int_TextLen + 1);

		if (pstr_WindowText != NULL)
		{
			str_param_WindowText = CStringW(pstr_WindowText);
		}

		::VirtualFree(pstr_WindowText, 0, MEM_RELEASE);
	}

	return;

}// End mpr_sffun_GetWindowsText()

[改进之后的代码太繁琐了, 继续优化代码-2]
经过上面的代码改进, 非常健壮了, 但是太臃肿, 5~6年前玩MFC, 记得不用写这么麻烦的. 因为我知道HWND句柄可以转换为CWnd类, 然后通过CWnd类来操作整个窗体. 根据这个经验, 打开google搜索关键字: hwnd to cwnd , 非常棒, 一下出现了我想要的东西: CWnd::FromHandle , 这下好了, 马上继续优化代码如下:
BOOL CALLBACK fun_EnumWindowsProc(HWND hwnd_param_Window, LPARAM lParam)
{
    // 获取窗体标题
	CStringW str_Window_Caption;
    CWnd::FromHandle(hwnd_param_Window)->GetWindowTextW(str_Window_Caption);

	if (str_Window_Caption.GetLength() != 0)
	{
        // 找到"80x86汇编小站"窗体
		if (str_Window_Caption.Find(L"80x86汇编小站") != -1)
		{
			return FALSE;
		}
	}

	return TRUE;

}// End fun_EnumWindowsProc()
这下非常漂亮了, 省去了mpr_sffun_GetWindowsText()的自定义函数, 爆爽.

[回头思考, 为什么一个错误的代码在主线程下能正常运行, 而在子线程下不能正常运行]
其实原因很简单, 我这里说一下自己的想法, 仅供参考. 因为主线程和子线程所拥有的堆栈环境是不一样, 尤其是各种变量的布局. 一个数组溢出, 如果没有破坏到周边的变量环境, 那么程序是不会报错. 比如主线程的内部没有遭到破坏, 因此程序可以正常运行下去, 虽然你有一点的溢出. 但是如果换到子线程, 内部布局又不一样了, 正好可以马上触发. 根据这样的逻辑思考, 其实还可以在继续发散思维到各种场景: 比如debug版本可以正常运行, 但是为什么release版本会报错. 再比如为什么在A电脑可以正常运行, 在B电脑运行就报错? 呵呵,是不是这些问题跟我现在遇到的问题有雷同呢?

[结尾]
通过记录这次的编程日常, 让我头脑更加清醒, 然后顺便把细节记录下来, 时刻提醒自己. 同时也向更多的编程初学者分享一下我是如何思考问题和解决问题的. 希望对你们有所帮助.


你可能感兴趣的:(MFC,枚举窗体,C/C++语言)