在 Windows 網路應用開發中,使用 WinINet API 實現檔案下載是常見需求。本文提供一個封裝良好的 InternetGetFile 函數範例,支援進度條顯示、下載速度計算、斷點續傳基礎(需自行擴展)及錯誤處理,並詳細解析關鍵步驟。
UINT InternetGetFile(
HINTERNET hOpen, // InternetOpen 返回的會話句柄
CHAR* szUrl, // 下載目標 URL
CHAR* szFileName, // 本地儲存路徑
HWND hwndProgress, // 進度條對話框句柄 (可為 NULL)
int idStatusText, // 狀態文字靜態控制項 ID
int idProgressBar // 進度條控制項 ID
);
返回值:0 成功,非零為自定義錯誤碼(INTERNET_ERROR_OPENURL、INTERNET_ERROR_FILEOPEN、INTERNET_ERROR_READFILE 等)。
CHAR szHead[] = "Accept: */*"; // 關鍵:若無此 Header,某些伺服器僅允許文字檔下載
hConnect = InternetOpenUrlA(hOpen, szUrl, szHead, lstrlenA(szHead),
INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_RELOAD, 0);
標誌組合確保:不使用本地快取、發送 Pragma: no-cache、強制從伺服器重新獲取。
DWORD dwByteToRead = 0, dwSizeOfRq = 4;
HttpQueryInfo(hConnect, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER,
&dwByteToRead, &dwSizeOfRq, NULL);
若伺服器不回傳 Content-Length(如動態生成內容),dwByteToRead 為 0,進度條改為「未知總量」模式。
VOID* szTemp[16384]; // 16KB 緩衝區
do {
if (!InternetReadFile(hConnect, szTemp, 16384, &dwSize)) { /* 錯誤處理 */ }
if (!dwSize) break; // EOF
fwrite(szTemp, 1, dwSize, pFile); // 寫入本地檔案
dwBytes += dwSize;
// 更新進度條與速度顯示...
} while (TRUE);
DWORD start = timeGetTime(), end, elapsed;
// 迴圈內:
end = timeGetTime();
elapsed = end - start; if (elapsed == 0) elapsed = 10; // 避免除零
float fSpeed = (dwBytes / 1024.0f) / (elapsed / 1000.0f); // KB/s
// 格式化字串: "已下載 XX KB / 總計 YY KB @ ZZ.Z KB/s"
SendDlgItemMessageA(hwndProgress, idProgressBar, WM_USER+2, (dwBytes*100)/dwByteToRead, 0);
SetDlgItemTextA(hwndProgress, idStatusText, szStatus);
需連結 winmm.lib 並包含 mmsystem.h 以使用 timeGetTime。
所有失敗路徑均需 fclose(pFile)、InternetCloseHandle(hConnect),並返回對應錯誤碼。成功時 fflush 確保緩衝區寫入磁碟。
HttpAddRequestHeaders 發送 Range: bytes=X-,配合 fseek 追加寫入。InternetOpen 使用 INTERNET_OPEN_TYPE_PRECONFIG,WinINet 自動處理 SSL/TLS 握手。PostMessage 更新 UI,避免阻塞主執行緒。