URLDownloadToFile (在 urlmon.h/urlmon.lib) 是 Windows 提供的高層下載 API,支援 HTTP/HTTPS/FTP、Proxy、快取、重導向、斷點續傳 (需搭配 IBindStatusCallback)。本範例示範最小化 COM 物件實作 IBindStatusCallback 以接收進度通知。
// URLDownloadDemo.cpp
#define _WIN32_DCOM
#include
#include
#include
#include
#pragma comment(lib, "urlmon.lib")
//=============================================================================
// IBindStatusCallback 實作
//=============================================================================
class CDownloadCallback : public IBindStatusCallback
{
long m_cRef;
public:
CDownloadCallback() : m_cRef(1) {}
~CDownloadCallback() {}
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
*ppv = NULL;
if (riid == IID_IUnknown || riid == IID_IBindStatusCallback) {
*ppv = static_cast(this);
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&m_cRef); }
STDMETHODIMP_(ULONG) Release() {
ULONG c = InterlockedDecrement(&m_cRef);
if (c == 0) delete this;
return c;
}
// IBindStatusCallback - 僅實作必要方法,其餘回傳 E_NOTIMPL
STDMETHODIMP OnProgress(ULONG ulProgress, ULONG ulProgressMax,
ULONG ulStatusCode, LPCWSTR szStatusText) {
if (ulProgressMax > 0) {
int percent = (int)(ulProgress * 100 / ulProgressMax);
_tprintf(_T("\r下載進度: %d%% (%lu / %lu bytes)"), percent, ulProgress, ulProgressMax);
}
return S_OK;
}
STDMETHODIMP OnStopBinding(HRESULT hresult, LPCWSTR szError) {
if (SUCCEEDED(hresult)) _tprintf(_T("\n下載完成。\n"));
else _tprintf(_T("\n下載失敗: 0x%08X %s\n"), hresult, szError);
PostThreadMessage(GetCurrentThreadId(), WM_QUIT, 0, 0); // 結束訊息迴圈
return S_OK;
}
// 其餘方法簡單實作
STDMETHODIMP OnStartBinding(DWORD, IBinding*) { return S_OK; }
STDMETHODIMP GetPriority(LONG*) { return E_NOTIMPL; }
STDMETHODIMP OnDataAvailable(DWORD, DWORD, FORMATETC*, STGMEDIUM*) { return S_OK; }
STDMETHODIMP OnObjectAvailable(REFIID, IUnknown*) { return S_OK; }
STDMETHODIMP OnLowResource(DWORD) { return S_OK; }
STDMETHODIMP GetBindInfo(DWORD*, BINDINFO*) { return E_NOTIMPL; }
};
//=============================================================================
// 主程式
//=============================================================================
int _tmain(int argc, _TCHAR* argv[])
{
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
LPCTSTR url = _T("https://127.0.0.1:30000/download/icon.zip");
LPCTSTR dest = _T("d:\\temp\\icon.zip");
CDownloadCallback* pCallback = new CDownloadCallback();
HRESULT hr = URLDownloadToFile(NULL, url, dest, 0, pCallback);
pCallback->Release();
if (hr == S_OK) {
// 等待回呼發送 WM_QUIT (OnStopBinding)
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
_tprintf(_T("檔案已儲存至 %s\n"), dest);
} else {
_tprintf(_T("URLDownloadToFile 失敗: 0x%08X\n"), hr);
}
CoUninitialize();
return 0;
}
| 項目 | 說明 |
|---|---|
URLDownloadToFile 參數 | pCaller (IUnknown*, 通常 NULL)、szURL、szFileName、dwReserved (0)、pCB (IBindStatusCallback*) |
| 非同步本質 | 函式立即返回,實際下載在背景執行緒進行,透過回呼通知進度/完成/錯誤。 |
OnProgress | 定期呼叫,ulProgress/ulProgressMax 為位元組數,可計算百分比、速度。 |
OnStopBinding | 下載結束 (成功/失敗/取消) 呼叫,hresult 指示結果。 |
| 訊息迴圈 | 主執行緒必須跑 GetMessage/DispatchMessage 迴圈,否則 COM 回呼無法送達 (STA 模式)。 |
| 斷點續傳 | 需在 GetBindInfo 設定 BINDINFO.dwBindVerb = BINDVERB_GET、grfBINDF = BINDF_GETNEWESTVERSION | BINDF_RESYNCHRONIZE,並在 OnProgress 中處理 BSCF_DATAFULLY_AVAILABLE 等旗標,配合 IBinding::Abort/Suspend 實作較複雜,建議改用 WinHTTP/WinInet/libcurl 手動控制 Range 標頭。 |
OnProgress 中 PostMessage(hwnd, WM_USER+1, percent, 0) 通知主視窗更新進度條。OnDownloadProgress 處理訊息,m_Progress.SetPos(percent)。pBinding->Abort() (需在 OnStartBinding 保存 IBinding*)。| API/庫 | 優點 |
|---|---|
| WinHTTP (WinHttpOpen/WinHttpOpenRequest/WinHttpSendRequest/WinHttpReceiveResponse) | 系統級、支援 HTTP/1.1/2.0、非同步、伺服器端適用、無 COM 開銷 |
| WinInet (InternetOpen/InternetOpenUrl/InternetReadFile) | 高層、支援快取/Proxy/Cookie、適合用戶端 GUI |
| IXMLHTTPRequest2 | 現代非同步、支援 HTTP/2、Stream-based、UWP/桌面通用 |
| libcurl | 跨平台、功能最全、支援所有協議/認證/代理/HTTP2/3 |
| C++ REST SDK (Casablanca) | 現代 C++、PPL tasks/await、JSON 整合 |
URLDownloadToFile 適合極簡下載需求、不想引入額外依賴 的場合。複雜下載管理 (多執行緒、佇列、限速、斷點續傳、驗證) 請使用上述現代庫。