我想制作从互联网下载页面并对其进行一些解析的程序。第二部分很容易,问题是第一。
我想使用 URLDownloadToFile() 函数。但默认情况下,它不会等待完成下载。MSDN 说最后一个参数是一种回调函数,但我找不到有关如何使用它的任何信息(何时调用它以及它必须做什么,甚至它是什么类型的函数)。有人可以解释一下最后一个参数是什么以及如何使用它(在 C++ 中)让我的应用程序等待吗?
您必须创建一个实现 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
由于错误,函数可能会立即返回。
URLDownloadToFile() 绝对是同步函数,如果您将LPBINDSTATUSCALLBACK lpfnCB设置为NULL。
它是如此“同步”,在下载完成之前它永远不会结束,即使网络连接失败并会阻塞你的线程。通过 TerminateThread() 函数正在使用 URLDownloadToFile() 杀死线程将导致资源泄漏和对系统 dll 的子调用未完成,并且在几次之后 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;
}
因此您可以随时中止下载并检查下载进度。
即使在 URLDownloadToFile() 函数返回的 S_OK 指示下载完成后,您也必须比较 progress==filesize 值,因为 URLDownloadToFile() 可能会错误地使用 S_OK 丢弃下载,例如,如果通过本地网络的网桥进行连接由于某种原因,接口和桥接器已倒塌。
此外,您必须注意与 URLDownloadToFile() 配对的 DeleteUrlCacheEntry() 函数以在下载后释放磁盘空间,因为根据 IE 缓存策略,所有下载的内容默认缓存在磁盘上。
如果您只想同步下载文件,那么像下面的示例这样简单的东西应该可以解决问题:
HRESULT hRez = URLDownloadToFile( NULL, _T(<url>), _T(<file>), 0, NULL );
if( hRez == 0 ){
// download ok
}
else{
// download failed
}
文档说最后一个参数是指向“调用者的 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;
您可能希望将某种信息传递给对象的构造函数,以便它知道如何通知程序的其余部分下载已终止。在您的程序收到该通知之前,您可以让它在其消息泵中处于通常的空闲状态。
这可能会有所帮助。