为 URLDownloadToFile 函数实现进度条

来源:互联网 发布:xampp是什么软件 编辑:程序博客网 时间:2024/05/22 17:29
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; }    //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;  }  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下载即可
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");    }  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的设置,完成下载,不用考虑代理情况。
0 0
原创粉丝点击