C++ 正在等待URLDownloadToFile()结束

C++ 正在等待URLDownloadToFile()结束,c++,urlmon,C++,Urlmon,我想做一个程序,从互联网上下载网页,并对其进行解析。第二部分很简单,问题是第一部分 我想使用URLDownloadToFile()函数。但默认情况下,它不会等待下载完成。MSDN说最后一个参数是一种回调函数,但我找不到关于如何使用它的任何信息(何时调用它以及它必须做什么,甚至它是什么类型的函数)。有人能告诉我最后一个参数是什么,以及如何使用它(在C++中)让我的应用程序等待吗?如果您只想同步下载文件,下面的示例就可以做到这一点: HRESULT hRez = URLDownloadToFile(

我想做一个程序,从互联网上下载网页,并对其进行解析。第二部分很简单,问题是第一部分


我想使用URLDownloadToFile()函数。但默认情况下,它不会等待下载完成。MSDN说最后一个参数是一种回调函数,但我找不到关于如何使用它的任何信息(何时调用它以及它必须做什么,甚至它是什么类型的函数)。有人能告诉我最后一个参数是什么,以及如何使用它(在C++中)让我的应用程序等待吗?

如果您只想同步下载文件,下面的示例就可以做到这一点:

HRESULT hRez = URLDownloadToFile( NULL, _T(<url>), _T(<file>), 0, NULL );
if( hRez == 0 ){
 // download ok
}
else{
 // download failed
}
HRESULT hRez=URLDownloadToFile(NULL,_T(),_T(),0,NULL);
如果(hRez==0){
//下载ok
}
否则{
//下载失败
}
这可能会有所帮助


文档中说,最后一个参数是指向“调用方的IBindStatusCallback接口”的指针。这意味着作为调用方,您需要提供一个指向实现该接口的东西的指针。您可以从以下实现开始:

class CBindStatusCallback: public IBindStatusCallback
{
public:
  STDMETHODIMP OnProgress(ULONG ulProgress, ULONG ulProgressMax,
    ULONG ulStatusCode, LPCWSTR szStatusText)
  {
    // write your implementation here
  }
  // Override GetBindInfo and the other IBindStatusCallback methods
  // by simply returning E_NOTIMPL, like this:
  STDMETHODIMP GetBindInfo(DWORD* /*grfBINDF*/, BINDINFO* /*pbindinfo*/)
  {
    return E_NOTIMPL;
  }

  // Provide the usual implementations for these IUnknown methods.
  STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
  STDMETHODIMP_(ULONG) AddRef();
  STDMETHODIMP_(ULONG) Release();
};
CBindStatusCallback* obj = new CBindStatusCallback;
IBindStatusCallback* callback = NULL;
HResult hr = obj->QueryInterface(IID_IBindStatusCallback, &callback);
obj = NULL;
hr = URLDownloadToFile(..., callback);
callback->Release();
callback = NULL;
创建其实例,获取其IBindStatusCallback接口指针,并将其传递给API函数。大概是这样的:

class CBindStatusCallback: public IBindStatusCallback
{
public:
  STDMETHODIMP OnProgress(ULONG ulProgress, ULONG ulProgressMax,
    ULONG ulStatusCode, LPCWSTR szStatusText)
  {
    // write your implementation here
  }
  // Override GetBindInfo and the other IBindStatusCallback methods
  // by simply returning E_NOTIMPL, like this:
  STDMETHODIMP GetBindInfo(DWORD* /*grfBINDF*/, BINDINFO* /*pbindinfo*/)
  {
    return E_NOTIMPL;
  }

  // Provide the usual implementations for these IUnknown methods.
  STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
  STDMETHODIMP_(ULONG) AddRef();
  STDMETHODIMP_(ULONG) Release();
};
CBindStatusCallback* obj = new CBindStatusCallback;
IBindStatusCallback* callback = NULL;
HResult hr = obj->QueryInterface(IID_IBindStatusCallback, &callback);
obj = NULL;
hr = URLDownloadToFile(..., callback);
callback->Release();
callback = NULL;

您可能希望将某种信息传递给对象的构造函数,以便它知道如何通知程序的其余部分下载已终止。在您的程序收到该通知之前,您可以让它在消息泵中处于通常的空闲状态。

您必须创建一个实现IBindStatusCallback接口的类。大多数方法都可以返回E_NOTIMPL。使用OnProgress()显示进度。下面是一个完成此任务的示例程序:

#include "stdafx.h"
#include <windows.h>
#include <iostream>
#pragma comment(lib, "urlmon.lib")
using namespace std;

class DownloadProgress : public IBindStatusCallback {
public:
    HRESULT __stdcall QueryInterface(const IID &,void **) { 
        return E_NOINTERFACE;
    }
    ULONG STDMETHODCALLTYPE AddRef(void) { 
        return 1;
    }
    ULONG STDMETHODCALLTYPE Release(void) {
        return 1;
    }
    HRESULT STDMETHODCALLTYPE OnStartBinding(DWORD dwReserved, IBinding *pib) {
        return E_NOTIMPL;
    }
    virtual HRESULT STDMETHODCALLTYPE GetPriority(LONG *pnPriority) {
        return E_NOTIMPL;
    }
    virtual HRESULT STDMETHODCALLTYPE OnLowResource(DWORD reserved) {
        return S_OK;
    }
    virtual HRESULT STDMETHODCALLTYPE OnStopBinding(HRESULT hresult, LPCWSTR szError) {
        return E_NOTIMPL;
    }
    virtual HRESULT STDMETHODCALLTYPE GetBindInfo(DWORD *grfBINDF, BINDINFO *pbindinfo) {
        return E_NOTIMPL;
    }
    virtual HRESULT STDMETHODCALLTYPE OnDataAvailable(DWORD grfBSCF, DWORD dwSize, FORMATETC *pformatetc, STGMEDIUM *pstgmed) {
        return E_NOTIMPL;
    }        
    virtual HRESULT STDMETHODCALLTYPE OnObjectAvailable(REFIID riid, IUnknown *punk) {
        return E_NOTIMPL;
    }

    virtual HRESULT __stdcall OnProgress(ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText)
    {
        wcout << ulProgress << L" of " << ulProgressMax;
        if (szStatusText) wcout << " " << szStatusText;
        wcout << endl;
        return S_OK;
    }
};


int _tmain(int argc, _TCHAR* argv[])
{
    DownloadProgress progress;
    HRESULT hr = URLDownloadToFile(0, 
        L"http://sstatic.net/stackoverflow/img/sprites.png?v=3", 
        L"c:/temp/test.png", 0,
        static_cast<IBindStatusCallback*>(&progress));
    return 0;
}

函数可能会因为错误而立即返回

如果将LPBINDSTATUSCALLBACK lpfnCB设置为NULL,URLDownloadToFile()肯定是同步函数

它是如此的“同步”,即使网络连接失败并阻塞您的线程,在下载完成之前,它永远不会结束。TerminateThread()函数终止正在运行URLDownloadToFile()的线程将导致资源泄漏和对系统DLL的子调用未完成,并且经过几次URLDownloadToFile()将拒绝在当前进程的上下文中工作

在不使用回调函数的情况下可靠地使用URLDownloadToFile()的唯一方法是为其派生单独的进程,并在下载暂停(这会消耗资源)时终止该进程

URLDownloadToFile()下载行为与IE完全相同,此函数运行的用户配置文件中的所有IE代理和网络设置也将应用于此函数

此外,URLDownloadToFile()即使使用回调函数也不会立即返回。我考虑在单独的线程中启动URLLoad LoopToFor()以安全地控制和中止网络下载。 下面是回调函数的简单示例

要获得安全下载,您应至少使用以下方式升级代码:

private:
    int progress, filesize;
    int AbortDownload;

public:

STDMETHOD(OnStartBinding)(
    { 
        AbortDownload=0;
        progress=0;
        filesize=0;
        return E_NOTIMPL; }

    STDMETHOD(GetProgress)()
        { return progress; }

    STDMETHOD(GetFileSize)()
        { return filesize; }
STDMETHOD(AbortDownl)()
    { 
        AbortDownload=1;
        return E_NOTIMPL; }

HRESULT DownloadStatus::OnProgress ( ULONG ulProgress, ULONG ulProgressMax,ULONG ulStatusCode, LPCWSTR wszStatusText )
{
    progress=ulProgress;
    filesize=ulProgressMax;
    if (AbortDownload) return E_ABORT;
    return S_OK;
}
因此,您可以随时中止下载并检查下载进度

即使在URLDownloadToFile()函数返回的S_OK指示下载已完成后,您也必须比较progress==filesize值,因为URLDownloadToFile()可能会错误地使用S_OK放弃下载,例如,如果通过本地网络接口的网桥进行连接,并且网桥由于某种原因已损坏


此外,您还必须注意DeleteUrlCacheEntry()函数与URLDownloadToFile()配合使用,以便在下载后释放磁盘空间,因为根据IE缓存策略,默认情况下,所有加载的内容都会缓存在磁盘上。

如果async对您不起作用,为什么不直接使用URLOpenBlockingStream?因为我对它了解得更少。我想(基于ISTRAM),它需要使用我不熟悉的托管C++。XIdUS使用接口(例如ISReAM)不需要使用托管C++。Windows早在.Net发明之前就开始使用界面了。COM大量使用接口,Internet Explorer API大量使用COM。URLDownloadToFile本身有两个接口参数。我很确定这个函数是同步的……嗯?返回值为零(S_OK)表示下载已成功启动。它并不表示下载已经完成。它将返回S_OK“即使无法创建文件并且下载被取消。”我在我的一个应用程序中使用此功能,并且必须在另一个线程中运行它,因为它肯定是同步的。可能只有在您传递IBindStatusCallback指针(我没有)的情况下,它才能异步工作。虽然一般技术是有效的,但我对细节不太清楚,因此我已将此“社区wiki”标记给其他人以填补空白。我花了一段时间才开始工作,如果看起来像是我复制了您的答案,很抱歉。+1它有帮助。另外,是否可以处理由于表单提交而发生的文件下载,例如,谢谢可以删除,对吗?