ページ 11

非同期的にWeb上からファイルをダウンロードさせたい

Posted: 2011年6月12日(日) 21:01
by PATOLE
はじめまして。
はじめて利用させていただきます。

C++についての質問ですが、Web上にあるファイルを、非同期的にダウンロードしたいと思いました。
そして、URLDownloadToFile という関数を見つけたのですが、これは、同期的処理になってしまい、
容量の大きいファイルであると、応答なしになってしまいます。(DXライブラリのウィンドウなどを使用しています)

URLDownloadToFileの一番最後の引数、LPBINDSTATUSCALLBACK lpfnCB に何かをしてやれば、進捗バーなども表示できて、非同期通信できるらしい、などとも調べてみたのですが、
それ以上がわかりませんでした。

URLDownloadToFileを使用して、非同期的にファイルをダウンロードしたり、進捗バーを表示(DXライブラリの描画でも構わない)するには具体的にはどうすればいいでしょうか。

お力を貸していただけるとうれしいです。

Re: 非同期的にWeb上からファイルをダウンロードさせたい

Posted: 2011年6月12日(日) 22:14
by softya(ソフト屋)
LPBINDSTATUSCALLBACK lpfnCBはコールバック関数を定義するためのパラメータですね。
ただ、これだけでDXライブラリを処理するのは無理がありますので、スレッドを併用されることをお勧めします。

コールバックに関してはATLの実装サンプルはありますが、VisualStudio有料版限定のATLは使えますか?
「ASYNC サンプル:データを非同期にダウンロードします。」
http://msdn.microsoft.com/ja-jp/library/df8t23cc

【追記】
ファイルダウンロードのDXライブラリだけで実装も可能かもしれません。
これならスレッドも必要ないでしょう。こまめに小さいサイズで読み込んで入ればメッセージループに支障も出ずに読み込めるのでは無いでしょうか?
ぜひ、実験してみてください。

DXライブラリのHTTP通信のサンプルの過去ログ。
http://www.play21.jp/board/formz.cgi?ac ... &from=tree

Re: 非同期的にWeb上からファイルをダウンロードさせたい

Posted: 2011年6月13日(月) 18:12
by a5ua
画像を別スレッドでダウンロードするサンプルを書いてみました。
メインスレッドでは、進捗率を表示し、ダウンロード後にその画像を表示しています。
また、描画にはDXライブラリを使用しています。

私が作成したクラスは、
  • CriticalSection
  • ProgressCallback
  • URLDownloader
の3つです。

CriticalSectionはスレッド間で共有する変数へのアクセスに対する排他制御用です。

ProgressCallbackはIBindStatusCallbackインターフェースを実装しています。
URLDownloadToFileの内部で、
IBindStatusCallback::OnProgressメソッドが呼ばれているので、
ProgressCallback::OnProgess内で、進捗率を更新しています。

URLDownloaderはURLDownloadToFileを別スレッドで呼び出しています。

URLDownloader download(url, file_name);
とすれば、別スレッドでダウンロードが開始します。
スレッドが終了したかどうかは
download.finished()で判定します。
ダウンロードの進捗率は、
download.progress()で取得します。

ダウンロードするサイズが小さいと、進捗はほとんど表示されないかもしれません。

以下、ソースコードを記載します。(作成環境はVisual Studio 2010です。)

【CriticalSection.h】

コード:

#pragma once

#include <Windows.h>

// 排他制御のためのクリティカルセクション
// 
// ちなみに、VC2010なら<concrt.h>には、Concurrency::critical_sectionがあり、
// boostライブラリには、boost::mutexがあるので、
// 使えるなら、そちらを使ったほうが良い
class CriticalSection
{
public:
	CriticalSection()
	{
		InitializeCriticalSection(&cs);
	}

	~CriticalSection()
	{
		DeleteCriticalSection(&cs);
	}

	void lock()
	{
		EnterCriticalSection(&cs);
	}

	void unlock()
	{
		LeaveCriticalSection(&cs);
	}

	class ScopeGuard
	{
	public:
		ScopeGuard(CriticalSection &critical_section) : cs(critical_section)
		{
			cs.lock();
		}
		~ScopeGuard()
		{
			cs.unlock();
		}
	private:
		CriticalSection &cs;
	};

private:
	CriticalSection(const CriticalSection &);
	CriticalSection &operator=(const CriticalSection &);
	CRITICAL_SECTION cs;
};
【URLDownloader.h】

コード:

#pragma once

#include <Windows.h>
#include <UrlMon.h>
#include <process.h>
#include <exception>
#include "CriticalSection.h"

// IBindStatusCallbackを実装
// OnProgressだけ実装して、残りは空の実装
class ProgessCallback : public IBindStatusCallback
{
public:
	ProgessCallback(CriticalSection &cs) : mutex(cs), m_progress(0)
	{
	}

    virtual HRESULT STDMETHODCALLTYPE OnStartBinding( 
        /* [in] */ DWORD dwReserved,
        /* [in] */ __RPC__in_opt IBinding *pib)
	{
		return E_NOTIMPL;
	}

    virtual HRESULT STDMETHODCALLTYPE GetPriority( 
        /* [out] */ __RPC__out LONG *pnPriority)
	{
		return E_NOTIMPL;
	}
        
    virtual HRESULT STDMETHODCALLTYPE OnLowResource( 
        /* [in] */ DWORD reserved)
	{
		return E_NOTIMPL;
	}
        
    virtual HRESULT STDMETHODCALLTYPE OnProgress( 
        /* [in] */ ULONG ulProgress,
        /* [in] */ ULONG ulProgressMax,
        /* [in] */ ULONG ulStatusCode,
        /* [unique][in] */ __RPC__in_opt LPCWSTR szStatusText)
	{
		CriticalSection::ScopeGuard guard(mutex);
		// 進捗率を更新
		if (ulProgressMax == 0) {
			m_progress = -1.0;
		} else {
			m_progress = static_cast<double>(ulProgress) / ulProgressMax;
		}
		return S_OK;
	}
        
    virtual HRESULT STDMETHODCALLTYPE OnStopBinding( 
        /* [in] */ HRESULT hresult,
        /* [unique][in] */ __RPC__in_opt LPCWSTR szError)
	{
		return E_NOTIMPL;
	}
        
    virtual /* [local] */ HRESULT STDMETHODCALLTYPE GetBindInfo( 
        /* [out] */ DWORD *grfBINDF,
        /* [unique][out][in] */ BINDINFO *pbindinfo)
	{
		return E_NOTIMPL;
	}
        
    virtual /* [local] */ HRESULT STDMETHODCALLTYPE OnDataAvailable( 
        /* [in] */ DWORD grfBSCF,
        /* [in] */ DWORD dwSize,
        /* [in] */ FORMATETC *pformatetc,
        /* [in] */ STGMEDIUM *pstgmed)
	{
		return E_NOTIMPL;
	}
        
    virtual HRESULT STDMETHODCALLTYPE OnObjectAvailable( 
        /* [in] */ __RPC__in REFIID riid,
        /* [iid_is][in] */ __RPC__in_opt IUnknown *punk)
	{
		return E_NOTIMPL;
	}

	virtual HRESULT STDMETHODCALLTYPE QueryInterface( 
        /* [in] */ REFIID riid,
        /* [iid_is][out] */ __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObject)
	{
		return E_NOTIMPL;
	}

    virtual ULONG STDMETHODCALLTYPE AddRef(void)
	{
		return 0;
	}

    virtual ULONG STDMETHODCALLTYPE Release(void)
	{
		return 0;
	}

	// 進捗率を返す
	double progress() const
	{
		CriticalSection::ScopeGuard guard(mutex);
		return m_progress;
	}

private:
	mutable CriticalSection &mutex;
	double m_progress;
};

// コンストラクタで、URLとファイル名を指定すると
// ダウンロードスレッドが起動する
class URLDownloader
{
public:
	URLDownloader(LPCTSTR url, LPCTSTR file) : m_finished(false), m_url(url), m_file(file), callback(mutex)
	{
		// スレッドを起動
		m_handle = reinterpret_cast<HANDLE>(_beginthreadex(0, 0, &URLDownloader::invoke, this, 0, 0));
		if (m_handle == INVALID_HANDLE_VALUE) {
			throw std::exception("スレッドの起動に失敗");
		}
	}

	~URLDownloader()
	{
		if (m_handle != INVALID_HANDLE_VALUE) {
			CloseHandle(m_handle);
		}
	}

	// ダウンロードが終わったかどうか
	bool finished() const
	{
		CriticalSection::ScopeGuard guard(mutex);
		return m_finished;
	}

	// 進捗率を返す
	double progress() const
	{
		CriticalSection::ScopeGuard guard(mutex);
		return callback.progress();
	}

private:

	// 実際の処理
	void run()
	{
		URLDownloadToFile(0, m_url, m_file, 0, &callback);
		CriticalSection::ScopeGuard guard(mutex);
		m_finished = true;
	}

	// run()に丸投げ
	static unsigned int __stdcall invoke(void *p)
	{
		static_cast<URLDownloader *>(p)->run();
		_endthreadex(0);
		return 0;
	}

	mutable CriticalSection mutex;	// 排他制御用

	ProgessCallback callback;		// 進捗率表示用

	LPCTSTR m_url;
	LPCTSTR m_file;

	bool m_finished;
	HANDLE m_handle;
};
【WinMain.cpp】

コード:

#include <DxLib.h>
#include <string>
#include "URLDownloader.h"

#pragma comment(lib, "UrlMon.lib")

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
	ChangeWindowMode(TRUE);
	if (DxLib_Init() != 0) {
		return 0;
	}
	SetDrawScreen(DX_SCREEN_BACK);

	// ダウンロード済かどうかのフラグ
	bool downloaded = false;

	// ダウンロードするファイルのURL
	const LPCTSTR TargetURL = _T("http://dixq.net/img/index_top.png");

	// 保存するファイル名
	const LPCTSTR FileName = _T("title.png");

	// ダウンロードスレッドを起動
	URLDownloader download(TargetURL, FileName);

	// 画像ハンドル
	int image = -1;

	while (ProcessMessage() == 0) {
		if (CheckHitKey(KEY_INPUT_ESCAPE)) {
			break;
		}
		ClearDrawScreen();

		if (!downloaded) {
			// ダウンロードが終っていない場合
			if (download.finished()) {
				// ダウンロードが終わったら画像を読み込む
				downloaded = true;
				image = LoadGraph(FileName);
			} else {
				// 進捗表示
				double p = download.progress();
				if (0 <= p) {
					int w = static_cast<int>(640 * p);
					int h = 20;
					DrawBox(0, 0, w, h, GetColor(192, 128, 0), TRUE);
					DrawFormatString(300, 0, GetColor(255, 255, 255), _T("%d%%"), static_cast<int>(p * 100));
				} else {
					DrawString(0, 0, _T("unknown"), GetColor(255, 255, 255));
				}
			}
		}

		// ダウンロードした画像を表示
		if (image != -1) {
			DrawGraph(0, 0, image, FALSE);
		}

		ScreenFlip();
	}

	DxLib_End();
	return 0;
}

Re: 非同期的にWeb上からファイルをダウンロードさせたい

Posted: 2011年6月14日(火) 17:35
by PATOLE
>>softya(ソフト屋) さん

情報ありがとうございます!!
そのような方法もあるのですね、参考になります。

>>a5ua さん

おおおおお!
これは、一番望んでいた実行結果です!
貴重なコードありがとうございます。
参考にして、自分なりに作ってみたいと思います。


皆様、貴重な意見ありがとうございました。
おかげで解決することができました。