为 URLDownloadToFile 实现进度条
2015-07-17 08:55
489 查看
最近使用 MFC的CInternetSession类下载文件时遇到一些问题, CInternetSession::OpenUrl 这个方法在使用个别代理的网络环境或者网速慢得要死的情况下,它会抛一个连接超时的异常,虽然catch 这个异常可以避免runtime error ,但下载也中止了。不过MSDN中提到SetOption可以设置超时时间,结果很悲剧的一次又一次的依然超时,后来才看到网上说SetOption无法如帮助文档中说的那样可以设置超时时间是MS已经确认的bug了,官方的解决方案是使用wininet系列函数或者将Openurl这部分代码放到一个线程里,如果线程超时就释放资源,关闭句柄,但这个也只能是防止内存泄露,对已经中断的下载依然于事无补。
URLDownloadToFile这个函数,我一开始只是用它来下载配置文件,主要考虑到如果下载较大的文件这个函数会阻塞很长时间,全部下载完毕后才返回,而且无法显示下载进度,今天才知道自己火星了,人家的最后一个参数提供了相关的接口(以前看都不看直接传NULL),查了些资料总算搞清楚怎么用了,记录一下。
1。创建一个IBindStatusCallback的派生类,声明IBindStatusCallback的8个方法。由于IBindStatusCallback继承自IUnknown,所以还要声明IUnknown的3个方法。下面的为个人使用.具体方法的原型声明可以参照MSDN,
2。可以控制显示进度条的是IBindStatusCallback::Onprogress,只要实现这个方法就行,IBindStatusCallback的其他7个方法IE是不会调用的,直接告诉IE这个我没实现,通通 return E_NOTIMPL (not implemented)。另外IUnknown的AddRef 和 Release 分别是给调用接口增加引用计数 和 减少引用计数的,也用不到直接都返回0就可以了,IUnknown的另一个方法
QueryInterface也 return E_NOTIMPL。
3。派生类创建好之后,就很简单了,直接给URLDownloadToFile的最后一个参数传个指向派生类实例的指针就大功告成了
我定义了一个CBindCallback类,类的声明:
[b][cpp] view
plaincopyprint?[/b]
void CUrlDownloadToFileCallbackTestDlg::DownloadButton()//下载按钮,也可以为线程
{
CBindCallback cbc;
cbc.m_pdlg = this;
(this->GetDlgItem(IDC_START))->EnableWindow(FALSE);//禁用下载按钮
//在url后添加随机数,防止从IE缓存中读取。url后加随机数不会影响下载的。
//如果想要从缓存中提取那么就把下面的注释掉
CString szRand;
szRand.Format(_T("?skq=%d"),GetTickCount());
szUrl += szRand;
if(S_OK == URLDownloadToFile(NULL,szURL,szPath,0,&cbc))//szURL,szPath为全局变量
MessageBox("finished");
}
Some Tips:
原文地址:http://hi.baidu.com/buaa%5Fdep6/blog/item/95fdc402dcc92d82d43f7cfc.html
URLDownloadToFile这个函数,我一开始只是用它来下载配置文件,主要考虑到如果下载较大的文件这个函数会阻塞很长时间,全部下载完毕后才返回,而且无法显示下载进度,今天才知道自己火星了,人家的最后一个参数提供了相关的接口(以前看都不看直接传NULL),查了些资料总算搞清楚怎么用了,记录一下。
1。创建一个IBindStatusCallback的派生类,声明IBindStatusCallback的8个方法。由于IBindStatusCallback继承自IUnknown,所以还要声明IUnknown的3个方法。下面的为个人使用.具体方法的原型声明可以参照MSDN,
2。可以控制显示进度条的是IBindStatusCallback::Onprogress,只要实现这个方法就行,IBindStatusCallback的其他7个方法IE是不会调用的,直接告诉IE这个我没实现,通通 return E_NOTIMPL (not implemented)。另外IUnknown的AddRef 和 Release 分别是给调用接口增加引用计数 和 减少引用计数的,也用不到直接都返回0就可以了,IUnknown的另一个方法
QueryInterface也 return E_NOTIMPL。
3。派生类创建好之后,就很简单了,直接给URLDownloadToFile的最后一个参数传个指向派生类实例的指针就大功告成了
我定义了一个CBindCallback类,类的声明:
[c-sharp] view plaincopyprint?class CBindCallback : public IBindStatusCallback { public: CBindCallback(); virtual ~CBindCallback(); //接受显示进度窗口的句柄 CUrlDownloadToFileCallbackTestDlg* m_pdlg; //IBindStatusCallback的方法。除了OnProgress 外的其他方法都返回E_NOTIMPL STDMETHOD(OnStartBinding) ( DWORD dwReserved, IBinding __RPC_FAR *pib) { return E_NOTIMPL; } STDMETHOD(GetPriority) ( LONG __RPC_FAR *pnPriority) { return E_NOTIMPL; } STDMETHOD(OnLowResource) ( DWORD reserved) { return E_NOTIMPL; } //OnProgress在这里 STDMETHOD(OnProgress) ( ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR wszStatusText); STDMETHOD(OnStopBinding) ( HRESULT hresult, LPCWSTR szError) { return E_NOTIMPL; } STDMETHOD(GetBindInfo) ( DWORD __RPC_FAR *grfBINDF, BINDINFO __RPC_FAR *pbindinfo) { return E_NOTIMPL; } STDMETHOD(OnDataAvailable) ( DWORD grfBSCF, DWORD dwSize, FORMATETC __RPC_FAR *pformatetc, STGMEDIUM __RPC_FAR *pstgmed) { return E_NOTIMPL; } STDMETHOD(OnObjectAvailable) ( REFIID riid, IUnknown __RPC_FAR *punk) { return E_NOTIMPL; } // IUnknown方法.IE 不会调用这些方法的 STDMETHOD_(ULONG,AddRef)() { return 0; } STDMETHOD_(ULONG,Release)() { return 0; } STDMETHOD(QueryInterface) ( REFIID riid, void __RPC_FAR *__RPC_FAR *ppvObject) { return E_NOTIMPL; } }; //只需实现OnProgress方法,类的实现: CBindCallback::CBindCallback() { } CBindCallback::~CBindCallback() { } //////仅实现OnProgress成员即可 LRESULT CBindCallback::OnProgress(ULONG ulProgress, ULONG ulProgressMax, ULONG ulSatusCode, LPCWSTR szStatusText) { CProgressCtrl* m_prg = (CProgressCtrl*)m_pdlg->GetDlgItem(IDC_PROGRESS); m_prg->SetRange32(0,ulProgressMax); m_prg->SetPos(ulProgress); CString szText; szText.Format("已下载%d%%", (int)(ulProgress * 100.0 / ulProgressMax)); (m_pdlg->GetDlgItem(IDC_STATUS))->SetWindowText(szText); return S_OK; } 调用URLDownloadToFile下载即可
[b][cpp] view
plaincopyprint?[/b]
void CUrlDownloadToFileCallbackTestDlg::DownloadButton()//下载按钮,也可以为线程
{
CBindCallback cbc;
cbc.m_pdlg = this;
(this->GetDlgItem(IDC_START))->EnableWindow(FALSE);//禁用下载按钮
//在url后添加随机数,防止从IE缓存中读取。url后加随机数不会影响下载的。
//如果想要从缓存中提取那么就把下面的注释掉
CString szRand;
szRand.Format(_T("?skq=%d"),GetTickCount());
szUrl += szRand;
if(S_OK == URLDownloadToFile(NULL,szURL,szPath,0,&cbc))//szURL,szPath为全局变量
MessageBox("finished");
}
Some Tips:
1。下载代码最好放到一个线程里,否则URLDownloadToFile下载过程中等待返回时会阻塞,使UI失去响应。 2。OnProgress返回S_OK表示正常,还可以通过返回E_ABORT使下载中断,所以可以设置个超时时间,如果超时的话,就让OnProgress返回E_ABORT。另外下次再开始从同一个url下载同一个文件时会直接由IE缓存中读取已下载的部分,达到“断点续传”的效果。
3。实际测试过程中发现URLDownloadToFile读IE缓存中已经下载的文件会有很大的安全隐患,如果哪次下载的文件发生问题,那么在不清除缓存的情况下,这个函数以后会一直读取损坏的文件而不重新下载。网上搜了一下解决方案,大概有三种:
a.下载前用FindFirstUrlCacheEntry,FindNextUrlCacheEntry,DeleteUrlCacheEntry清除cache,这个代码网上很多。
b.重载IBindStatusCallback的GetBindInfo方法,指定BINDF_GETNEWESTVERSION和BINDF_NOWRITECACHE属性,但是我测试发现即使指定这两个属性UrlDownloadToFile还是会很执着的读缓存,郁闷。
c.还有一种方法比较猥琐,在要下载的文件地址后加一个随机字符串,这样既不会影响正常下载(下载时会被指向正确的地址)而且由于每次传给URLDownloadToFile的url都不同,在cache中没有地址匹配的文件,所以会重新下载。上面的代码就使用了这种方法,个人感觉比较省事而且经测试有效。
4。CBindCallback有个成员变量用来传递进度条所在的窗口句柄m_pdlg,当然这个也可以用其他方式实现。 5。URLDownloadToFile的好处在于它会自动使用IE的设置,完成下载,不用考虑代理情况。
原文地址:http://hi.baidu.com/buaa%5Fdep6/blog/item/95fdc402dcc92d82d43f7cfc.html
相关文章推荐
- PHP脚本实现凯撒加(解)密
- Heavy Transportation
- 编译原理语法分析(文本输入)源程序
- 编程名言集锦
- STM32学习笔记(一) 如何新建一个STM32工程模板
- VS2008 Find and Replace功能失效
- 7月29日微软Windows 10发布会粉丝庆祝活动 中国地区开放报名
- 基于jQuery左右滑动切换特效
- Tcp通信中的三次握手和四次挥手
- Android广播消息及BroadcastReceiver
- 编译原理词法分析(文本输入)源程序
- iOS C语言5_二维数组
- 原因和解决方案都已经找到
- C#如何连接SqlServer2005非默认实例
- [转载] linux 速查表
- 国行Win10专业版价格曝光 包括港行版台湾版
- poj 1631 nologn LIS
- HDU 2196 Computer (经典树形DP)
- 解决eclipse启动插件时PermGen space问题
- .NET三层架构与EF