画像を別スレッドでダウンロードするサンプルを書いてみました。
メインスレッドでは、進捗率を表示し、ダウンロード後にその画像を表示しています。
また、描画には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;
}