Ожидание окончания URLDownloadToFile () - PullRequest
3 голосов
/ 13 марта 2011

Я хочу сделать программу, которая загружает страницу из интернета и делает на ней несколько разборов. Вторая часть проста, проблема первая.

Я хочу использовать функцию URLDownloadToFile (). Но по умолчанию он не ждет завершения загрузки. MSDN говорит, что последний параметр является своего рода функцией обратного вызова, но я не могу найти какую-либо информацию о том, как его использовать (когда он вызывается и что он должен делать, даже какой это тип функции). Может кто-нибудь объяснить мне, что это за последний параметр и как его использовать (в C ++), чтобы заставить мое приложение ждать?

Ответы [ 5 ]

9 голосов
/ 23 января 2013

Вероятно, функция немедленно возвращается из-за ошибки.

URLDownloadToFile () определенно является синхронной функцией, если вы установите LPBINDSTATUSCALLBACK lpfnCB как NULL .

Это настолько "синхронно", что никогда не закончится до завершения загрузки, даже если сетевое соединение не удастся и заблокирует ваш поток. Уничтожение потока с помощью URLDownloadToFile (), выполняемого функцией TerminateThread (), приведет к утечке ресурсов и незавершенным дочерним вызовам системных библиотек, и через пару раз URLDownloadToFile () откажется работать в контексте текущего процесса.

Единственный способ надежного использования URLDownloadToFile () без функции обратного вызова - это разветвить отдельный процесс и завершить его, если загрузка останавливается, что потребляет ресурсы.

URLDownloadToFile () загружает поведение точно так же, как IE, все прокси-серверы IE и настройки сети в профиле пользователя, в контексте которых эта функция работает, также будут применяться к этой функции.

Также URLDownloadToFile () не возвращается сразу, даже с функцией обратного вызова. Я считаю необходимым запустить URLDownloadToFile () в отдельном потоке, чтобы безопасно контролировать и прерывать загрузку по сети.

Простой пример функции обратного вызова: https://github.com/choptastic/OldCode-Public/blob/master/URLDownloadToFile/URLDownloadToFile.cpp

.

Чтобы получить безопасную загрузку, вы должны обновить код хотя бы примерно так:

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;
}

, чтобы вы всегда могли прервать загрузку и проверить ход загрузки.

Даже после того, как загрузка была указана как выполненная с помощью S_OK, возвращенного функцией URLDownloadToFile (), вы должны сравнивать значения progress == filesize, потому что URLDownloadToFile () может по ошибке прервать загрузку с S_OK, например, если соединение установлено через сетевой мост локальных сетевых интерфейсов и мостов по какой-то причине упали.

Также следует обратить внимание на функцию DeleteUrlCacheEntry () в паре с URLDownloadToFile (), чтобы освободить место на диске после загрузки, поскольку весь загружаемый контент по умолчанию кэшируется на диске в соответствии с политикой кэширования IE.

7 голосов
/ 13 марта 2011

Вы должны создать класс, который реализует интерфейс 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;
}

Вывод:

0 of 0 sstatic.net
0 of 0 64.34.119.12
0 of 0
0 of 0 image/x-png
3550 of 16542 http://sstatic.net/stackoverflow/img/sprites.png?v=3
3550 of 16542 C:\Users\hpassant\AppData\Local\Microsoft\Windows\Temporary Inter
et Files\Content.IE5\NRPH4KHK\sprites[1].png
7330 of 16542 http://sstatic.net/stackoverflow/img/sprites.png?v=3
8590 of 16542 http://sstatic.net/stackoverflow/img/sprites.png?v=3
12370 of 16542 http://sstatic.net/stackoverflow/img/sprites.png?v=3
13630 of 16542 http://sstatic.net/stackoverflow/img/sprites.png?v=3
16542 of 16542 http://sstatic.net/stackoverflow/img/sprites.png?v=3
2 голосов
/ 13 марта 2011

В документации говорится, что последний параметр - это указатель на «интерфейс вызывающего абонента 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();
};

Создайте экземпляр этого, получите его указатель на интерфейс IBindStatusCallback и передайте его функции API. Примерно так:

CBindStatusCallback* obj = new CBindStatusCallback;
IBindStatusCallback* callback = NULL;
HResult hr = obj->QueryInterface(IID_IBindStatusCallback, &callback);
obj = NULL;
hr = URLDownloadToFile(..., callback);
callback->Release();
callback = NULL;

Возможно, вы захотите передать какую-то информацию конструктору объекта, чтобы он знал, как уведомить остальную часть вашей программы о том, что загрузка прервана. До тех пор, пока ваша программа не получит это уведомление, вы можете просто оставить его в обычном состоянии простоя в своем насосе сообщений.

2 голосов
/ 13 марта 2011

Что-то простое, как в примере ниже, поможет, если вы хотите просто загрузить файл синхронно:

HRESULT hRez = URLDownloadToFile( NULL, _T(<url>), _T(<file>), 0, NULL );
if( hRez == 0 ){
 // download ok
}
else{
 // download failed
}
0 голосов
/ 13 марта 2011
...