URLDownloadToFile 实现进度条(IBindStatusCallback)

28 篇文章 0 订阅

转自:http://hi.baidu.com/qazssaeooiciqsr/item/eaeb813a284ef0677c034b85


最近使用 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类,类的声明:

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; }

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下载即可
void CUrlDownloadToFileCallbackTestDlg::DownloadThread()
{
CBindCallback cbc;
cbc.m_pdlg = this;
CString szURL;
m_url.GetWindowText(szURL);

m_url.EnableWindow(FALSE);
(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))
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的设置,完成下载,不用考虑代理情况。




  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
urldownloadtofile和ibindstatuscallback是Windows操作系统中使用的一对接口函数,主要用于在下载文件的过程中提供状态和进度的回调功能。 urldownloadtofile函数用于将指定的URL资源下载到本地文件。该函数需要传入下载文件的URL地址、保存文件的本地路径以及一个用于接收下载进度和状态的回调接口指针参数。函数执行时,会创建一个后台线程,使用HTTP协议发送请求并接收响应,将服务器上的文件内容写入本地文件。 ibindstatuscallback接口作为urldownloadtofile函数的回调接口,用于接收下载过程中的各种状态和进度信息。通过该接口,可以获得下载开始、下载进度、下载完成等状态通知,并可以根据需要实时更新UI界面或执行其他操作。该接口主要包含OnStartBinding、OnProgress和OnStopBinding等方法,分别对应下载开始、下载进行中和下载结束。 使用urldownloadtofile和ibindstatuscallback可以实现简单而灵活的文件下载功能。我们可以在OnStartBinding方法中进行一些下载准备工作,比如初始化进度条控件;在OnProgress方法中更新下载进度,比如更新进度条的当前进度;在OnStopBinding方法中进行下载完成后的处理,比如关闭进度条、显示下载完成提示等。 总之,urldownloadtofile和ibindstatuscallback是在Windows操作系统中用于实现文件下载和状态回调的一对接口函数,使用它们可以方便地进行文件下载,并通过回调接口获取下载进度和状态信息,以实现更好的用户交互体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值