主文件: #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的設置,完成下載,不用考慮代理情況。
關閉