You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 07:47:23