C语言使用urlmon.h的DownloadProgressCallback报错及替代方案咨询
问题分析与解决
你遇到的错误翻译为:
线程0x4754已退出(代码: 0x0)。
在urlmon.dll中抛出异常(TestURL.exe中的0x00007FFC2A1045CF): 0xC0000005: 读取位置0xFFFFFFFFFFFFFFFF时发生访问违例。
错误原因
你传入URLDownloadToFile的最后一个参数是普通函数指针,但该函数要求的是**IBindStatusCallback接口的指针**,不是普通回调函数。系统尝试调用接口的虚方法时,传入的无效指针导致了访问违例。
正确使用urlmon实现进度回调的代码
要实现进度回调,必须实现IBindStatusCallback接口(继承自IUnknown),以下是完整可运行的代码:
#include <stdio.h> #include <windows.h> #include <urlmon.h> #include <shlwapi.h> #pragma comment(lib, "urlmon.lib") #pragma comment(lib, "shlwapi.lib") // 实现IBindStatusCallback接口的结构体 typedef struct { IBindStatusCallback lpVtbl; LONG refCount; } DownloadStatusCallback; // IUnknown 方法实现 HRESULT __stdcall DownloadQueryInterface(DownloadStatusCallback* This, REFIID riid, void** ppvObject) { if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IBindStatusCallback)) { *ppvObject = This; This->refCount++; return S_OK; } *ppvObject = NULL; return E_NOINTERFACE; } ULONG __stdcall DownloadAddRef(DownloadStatusCallback* This) { return InterlockedIncrement(&This->refCount); } ULONG __stdcall DownloadRelease(DownloadStatusCallback* This) { ULONG ref = InterlockedDecrement(&This->refCount); if (ref == 0) { HeapFree(GetProcessHeap(), 0, This); } return ref; } // 进度回调核心方法 HRESULT __stdcall DownloadOnProgress(DownloadStatusCallback* This, ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText) { if (ulProgressMax > 0) { float percentage = (float)ulProgress / ulProgressMax * 100.0f; printf("下载进度: %.2f%%\r", percentage); fflush(stdout); } return S_OK; } // 其他IBindStatusCallback方法(默认实现) HRESULT __stdcall DownloadOnStartBinding(DownloadStatusCallback* This, DWORD dwReserved, IBinding* pib) { return S_OK; } HRESULT __stdcall DownloadGetPriority(DownloadStatusCallback* This, LONG* pnPriority) { return E_NOTIMPL; } HRESULT __stdcall DownloadOnLowResource(DownloadStatusCallback* This, DWORD reserved) { return S_OK; } HRESULT __stdcall DownloadOnStopBinding(DownloadStatusCallback* This, HRESULT hresult, LPCWSTR szError) { return S_OK; } HRESULT __stdcall DownloadGetBindInfo(DownloadStatusCallback* This, DWORD* grfBINDF, BINDINFO* pbindinfo) { return E_NOTIMPL; } HRESULT __stdcall DownloadOnDataAvailable(DownloadStatusCallback* This, DWORD grfBSCF, DWORD dwSize, FORMATETC* pformatetc, STGMEDIUM* pstgmed) { return S_OK; } HRESULT __stdcall DownloadOnObjectAvailable(DownloadStatusCallback* This, REFIID riid, IUnknown* punk) { return S_OK; } // 创建回调实例的函数 DownloadStatusCallback* CreateDownloadCallback() { DownloadStatusCallback* cb = HeapAlloc(GetProcessHeap(), 0, sizeof(DownloadStatusCallback)); if (!cb) return NULL; // 初始化虚表 cb->lpVtbl.QueryInterface = (LPQUERYINTERFACE)DownloadQueryInterface; cb->lpVtbl.AddRef = (LPADDREF)DownloadAddRef; cb->lpVtbl.Release = (LPRELEASE)DownloadRelease; cb->lpVtbl.OnStartBinding = DownloadOnStartBinding; cb->lpVtbl.GetPriority = DownloadGetPriority; cb->lpVtbl.OnLowResource = DownloadOnLowResource; cb->lpVtbl.OnProgress = DownloadOnProgress; cb->lpVtbl.OnStopBinding = DownloadOnStopBinding; cb->lpVtbl.GetBindInfo = DownloadGetBindInfo; cb->lpVtbl.OnDataAvailable = DownloadOnDataAvailable; cb->lpVtbl.OnObjectAvailable = DownloadOnObjectAvailable; cb->refCount = 1; return cb; } int main() { HRESULT hr; LPCWSTR url = L"https://example.com/test.zip"; // 替换为实际下载链接 LPCWSTR localFileName = L"C:\\Temp\\test.zip"; // 注意路径用双反斜杠 // 创建回调实例 DownloadStatusCallback* cb = CreateDownloadCallback(); if (!cb) { printf("创建回调实例失败\n"); return 1; } // 创建目录(如果不存在) WCHAR dir[MAX_PATH]; wcscpy_s(dir, localFileName); PathRemoveFileSpec(dir); CreateDirectory(dir, NULL); // 开始下载 hr = URLDownloadToFile(NULL, url, localFileName, 0, (IBindStatusCallback*)cb); if (hr == S_OK) { printf("\n下载完成\n"); } else { printf("\n下载失败,错误码: 0x%08X\n", hr); } // 释放回调实例 cb->lpVtbl.Release(cb); return 0; }
其他获取下载进度的方法:WinINet API
如果你觉得urlmon的接口实现太繁琐,可以使用WinINet API手动处理下载,更灵活且容易控制进度:
#include <stdio.h> #include <windows.h> #include <wininet.h> #pragma comment(lib, "wininet.lib") int main() { HINTERNET hInternet = InternetOpen(L"DownloadClient", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0); if (!hInternet) { printf("InternetOpen 失败,错误码: %d\n", GetLastError()); return 1; } LPCWSTR url = L"https://example.com/test.zip"; HINTERNET hUrl = InternetOpenUrl(hInternet, url, NULL, 0, INTERNET_FLAG_RELOAD, 0); if (!hUrl) { printf("InternetOpenUrl 失败,错误码: %d\n", GetLastError()); InternetCloseHandle(hInternet); return 1; } // 获取文件总大小 DWORD fileSize = 0; DWORD bufferSize = sizeof(fileSize); HttpQueryInfo(hUrl, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &fileSize, &bufferSize, NULL); // 创建本地文件 HANDLE hFile = CreateFile(L"C:\\Temp\\test.zip", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { printf("CreateFile 失败,错误码: %d\n", GetLastError()); InternetCloseHandle(hUrl); InternetCloseHandle(hInternet); return 1; } // 循环读取并写入文件,计算进度 BYTE buffer[4096]; DWORD bytesRead; DWORD totalRead = 0; while (InternetReadFile(hUrl, buffer, sizeof(buffer), &bytesRead) && bytesRead > 0) { WriteFile(hFile, buffer, bytesRead, &bytesRead, NULL); totalRead += bytesRead; if (fileSize > 0) { float percentage = (float)totalRead / fileSize * 100.0f; printf("下载进度: %.2f%%\r", percentage); fflush(stdout); } } printf("\n下载完成\n"); // 清理资源 CloseHandle(hFile); InternetCloseHandle(hUrl); InternetCloseHandle(hInternet); return 0; }
内容的提问来源于stack exchange,提问作者Mediblo
相关产品推荐
相关产品推荐

