一直以来,写了控件后用起来很正常,最近,客户要把这控件用在web页面上,那么问题它就来了。
大多数时候的运行环境:
os: win7、win10
浏览器:ie8、ie11,这里不考虑ie之外的其它浏览器
问题表现:
打开A.htm页面,然后通过其中的链接打开B.htm,其中B.htm页面中加载本控件。然后关闭B.htm,再点击A.htm中的链接打开B.htm,结果ie挂掉!
开发IDE:vs2010中文版
开发语言:VC++
控件的基本结构:
在CxxCtrl中创建一个对话框CMainDs m_mds,并且设置m_mds的父窗口为CxxCtrl,大致的过程如下:
int CxxCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (COleControl::OnCreate(lpCreateStruct) == -1)
return -1;
m_mds.Create(CMainDs::IDD);
m_mds.SetParent(this);
return 0;
}
问题跟踪:
注:环境为win7+ie11
因为是在页面加载的时候挂掉的,所以这个OnCreate()入口便是第一个要找的地方,通过添加打印发现,在挂掉的时候,是因为m_mds.Create(CMainDs::IDD);的接口调用失败,即对话框没有创建成功,则下面的一行自然报错!
在创建对话框时,不管是否成功,都会有下面的打印:
Warning: Creating dialog from within a COleControlModule application is not a supported scenario.
这个打印,应该是只在debug版本下才会有,在release下是被屏蔽掉的。其实既然创建成功的时候也会有这一行的打印,那说明这一行所说的问题并不是核心问题。
通过进一步跟踪发现,在关闭页面的时候,其进程并没有退出,在经过大约60秒~70秒后,进程才从任务管理器中消失,所以初步怀疑是因为控件退出有问题,控件退出太慢导致进程退出慢,在进程退出之前又打开页面的时候,ie却又使用了之前的那个进程,但奇怪的是,即使是同一个进程,那只是创建一个对话框,第一次成功,那第二次就会失败?
做为测试,在关闭页面后,一直等待进程退出。结果在进程退出时候有很多输出的内容,但都是表示内存泄露:
00000118 75.85210419 [7180] Detected memory leaks!
00000119 75.85218811 [7180] Dumping objects ->
00000120 75.85226440 [7180] {334}
00000121 75.85233307 [7180] normal block at 0x0FEFA8A8, 24 bytes long.
00000122 75.85242462 [7180] Data: <h > 68 D3 E4 0B 03 00 00 00 03 00 00 00 01 00 00 00
00000123 75.85247040 [7180] {331}
00000124 75.85254669 [7180] normal block at 0x0FEFA860, 26 bytes long.
00000125 75.85263062 [7180] Data: <h > 68 D3 E4 0B 04 00 00 00 04 00 00 00 01 00 00 00
00000126 75.85269165 [7180] {325}
00000127 75.85276031 [7180] normal block at 0x0FBDEB28, 44 bytes long.
00000128 75.85284424 [7180] Data: <h > 68 D3 E4 0B 0D 00 00 00 0D 00 00 00 01 00 00 00
00000129 75.85291290 [7180] f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\wincore.cpp(4553) :
00000130 75.85298157 [7180] {297}
00000131 75.85305023 [7180] client block at 0x0FBDEEC0, subtype c0, 56 bytes long.
00000132 75.85317230 [7180] a CObject object at $0FBDEEC0, 56 bytes long
00000133 75.85324860 [7180] f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\wincore.cpp(4553) :
00000134 75.85330963 [7180] {285}
00000135 75.85337830 [7180] client block at 0x0FBDEC90, subtype c0, 56 bytes long.
00000136 75.85347748 [7180] a CObject object at $0FBDEC90, 56 bytes long
00000137 75.85354614 [7180] f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\ctlcore.cpp(574) :
00000138 75.85361481 [7180] {277}
00000139 75.85367584 [7180] client block at 0x0FBDE410, subtype c0, 120 bytes long.
00000140 75.85377502 [7180] a CWnd object at $0FBDE410, 120 bytes long
00000141 75.85385132 [7180] f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\ctlview.cpp(239) :
00000142 75.85391235 [7180] {271}
00000143 75.85397339 [7180] normal block at 0x0FBDE9D0, 12 bytes long.
00000144 75.85405731 [7180] Data: < h > 01 00 00 00 00 00 00 00 E4 68 19 07
00000145 75.85412598 [7180] {266}
00000146 75.85419464 [7180] normal block at 0x0FBDEA90, 102 bytes long.
00000147 75.85427856 [7180] Data: <h & * > 68 D3 E4 0B 26 00 00 00 2A 00 00 00 02 00 00 00
00000148 75.85433960 [7180] {262}
00000149 75.85440826 [7180] normal block at 0x0FBDE998, 8 bytes long.
00000150 75.85448456 [7180] Data: < > A0 C4 EF 0F 00 00 00 00
00000151 75.85454559 [7180] {261}
00000152 75.85461426 [7180] normal block at 0x0FEFB0B0, 32 bytes long.
00000153 75.85469818 [7180] Data: < > B0 B0 EF 0F B0 B0 EF 0F B0 B0 EF 0F CD CD CD CD
00000154 75.85475922 [7180] {260}
00000155 75.85482788 [7180] normal block at 0x0FBDE960, 8 bytes long.
00000156 75.85490417 [7180] Data: <p > 70 C4 EF 0F 00 00 00 00
00000157 75.85496521 [7180] {259}
00000158 75.85503387 [7180] normal block at 0x0FBDE900, 52 bytes long.
00000159 75.85511780 [7180] Data: < > 00 E9 BD 0F 00 E9 BD 0F 00 E9 BD 0F CD CD CD CD
00000160 75.85518646 [7180] {258}
00000161 75.85525513 [7180] normal block at 0x0FBDE8C8, 8 bytes long.
00000162 75.85532379 [7180] Data: < > 14 C3 EF 0F 00 00 00 00
00000163 75.85539246 [7180] {257}
00000164 75.85546112 [7180] normal block at 0x0FBDE890, 8 bytes long.
00000165 75.85553741 [7180] Data: < > DC C2 EF 0F 00 00 00 00
00000166 75.85560608 [7180] e:\work\plat\xxctrl.cpp(14) :
00000167 75.85566711 [7180] {256}
00000168 75.85572815 [7180] client block at 0x0FEFBFF8, subtype c0, 2048 bytes long.
00000169 75.85584259 [7180] a CsilvideocliCtrl object at $0FEFBFF8, 2048 bytes long
00000170 75.85590363 [7180] Object dump complete.
这个打印中,
1. 我电脑上没有 f:\dd\vctools\vc7libs\ 这个路径,不知道为什么会有这个打印出来
2. xxctrl.cpp(14) 这一行非常重要,这一行的内容是:
IMPLEMENT_DYNCREATE(CxxCtrl, COleControl)
这是创建控件类的代码,是建立工程时向导自己加上去的,退出时它自己不删除,关我何事?事实证明,关系很大,它会让客户不厌其烦地找你,给你压力!
因为这控件依赖了很多其它的动态库,难道是因为某些库退出慢才导致了控件的退出慢?
把控件依赖的动态库逐个地屏蔽掉再测试,结果依旧,直到把所有添加上去的代码全部删除掉,接口什么的也全部删除掉,还是一样!难道是哪里没有恢复原状导致的?立马建立一个全新的控件,自己不写一行代码直接编译,然后加载到web页面中,结果仍然无法及时退出!
费了九牛二虎之力查找是哪个库的问题,结果不是我的问题!话说,这向导、这ie不可能会有这么严重的bug吧?我用的少,那其他开发人员还不早就疯了?这说明还得自己找问题去。
再进行测试。不启动A.htm页面,而是直接双击B.htm页面启动ie,然后再关闭,结果这个退出很快,进程也立马消失,但是仍然还是一堆的内存泄露打印。
在CxxCtrl的析构函数中添加打印,发现web页面关闭直到其进程退出,仍然没有走到析构函数中,但是我启动使用的exe后关闭,它就可以。意思是说,exe没问题,而web就有问题,客户说,早点就不应该承诺支持web,这下麻烦大了,出力不讨好,我说,这确实是个好主意,不过机会已经错过,后悔咋办?冰办。
通过以上的多种测试,这要等使进程快速退出,估计是很困难了,因为ie对控件的加载和卸载,完全不受控件的控制。只好调整方向。
对比两次创建对话框的不同,似乎是只有CxxCtrl类是否存在的差异,所以如果在关闭页面时,能够把CxxCtrl的实例删除掉,岂不美哉?这个办法其实之前也有试过,但是由于方向没有定下来,加的位置导致退出时程序挂掉而不了了之,现在只好重新考虑这个办法了。
对比ie的退出和exe退出的打印可以发现,exe的退出,会调用CxxCtrl::OnFinalRelease()函数,通过单步跟踪发现,在调用完这个函数后,有如下一行代码:
delete this;
也就是说,还是有删除自己的这一动作存在。但是在web退的时候,不会调用这个接口,也没有调用delete this,那就只好自己添加了。
好在web退出时,会调用CxxCtrl::OnClose()函数,添加delete this应该就和它有关了,但是之前在这个函数中直接添加delete this就是导致在页面关闭时ie挂掉,这硬的不行,只能来软的了,变通一下方法,在这里向自己pose一个消息。
因为PostMessage()的优先级很低,它发的消息会在最后才处理,所以这里不用SendMessage()。
#define UM_CHGFUN (WM_USER+101)
void CxxCtrl::OnClose(DWORD dwSaveOption)
{
// TODO: Add your message handler code here and/or call default
EndCtrol(); // 反初始化,关闭所有任务,清空所有变量及内存
PostMessage(UM_CHGFUN, 11);
COleControl::OnClose(dwSaveOption);
//delete this; // 这里删除自己会导致ie挂掉!
}
LRESULT CxxCtrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == UM_CHGFUN && wParam == 11)
{
delete this;
return 0;
}
return COleControl::WindowProc(message, wParam, lParam);
}
通过以上的修改,再测试已经可以了。这是初步成功,是否还有其它问题,目前还没有进行更多的测试,所以还不能确定,但至少发现的这个退出和加载的问题是暂时解决了。
开始的时候,是通过发送销毁消息:
void CxxCtrl::OnClose(DWORD dwSaveOption)
{
// TODO: Add your message handler code here and/or call default
EndCtrol(); // 反初始化,关闭所有任务,清空所有变量及内存
PostMessage(WM_DESTROY);
COleControl::OnClose(dwSaveOption);
//delete this; // 这里删除自己会导致ie挂掉!
}
LRESULT CxxCtrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_DESTROY)
{
delete this;
return 0;
}
return COleControl::WindowProc(message, wParam, lParam);
}
这样,在web上退出是没有问题了,但在exe上退出时,会在CxxCtrl::OnFinalRelease()中挂掉,所以发送WM_DESTROY的消息是不行的,当然也可能是我处理的不对,这就没有深究了