c++のurlmon实现下载文件并进度回调
主文件: #include "stdafx.h" #include <UrlMon.h> #pragma comment(lib, "urlmon.lib") #include <tchar.h> #include "cbindCallBack.h" #include "iostream" #include <CString> int main() { //在url后添加随机数,防止从IE缓存中读取。url后加随机数不会影响下载的。 //如果想要从缓存中提取那么就把下面的注释掉 DWORD rand= GetTickCount(); CBindCallback cbc; HRESULT hr=URLDownloadToFile(NULL, _T("http://dldir1.qq.com/qqfile/qq/QQ8.9/19983/QQ8.9.exe"), _T("e:\\download\\qq8.9.exe"), NULL, &cbc); if (hr==S_OK) { std::cout << "下载完成!" << std::endl; system("e:\\download\\qq8.9.exe"); } else if (hr== E_OUTOFMEMORY) { std::cout << "缓冲区长度无效,或内存不足,无法完成操作!" << std::endl; } else if (hr== E_OUTOFMEMORY) { std::cout << "指定的资源或回调接口无效!" << std::endl; } getchar(); return 0; } cbindCallback.h #pragma once #include "stdafx.h" #include <UrlMon.h> #pragma comment(lib, "urlmon.lib") #include <tchar.h> 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; } }; cbindCallBack.cpp #include "stdafx.h" #include "cbindCallBack.h" #include "iostream" using namespace std; //只需实现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);*/ cout << "文件大小为:" << ulProgressMax /1024/1024 << "MB"<<endl; cout << ulProgress/1024/1024<<"MB" << endl; cout << "已下载:" << ulProgressMax*100.0/ulProgressMax << "%" << endl; return S_OK; }
注意事项:
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的设置,完成下载,不用考虑代理情况。
关闭