最近はネット上にさまざまな情報やファイルがある。ブロードバンドの普及により通信速度も向上したこともあり、自分のプログラムからネット上の情報を参照したい場面がよくある。
ここではATL環境でマルチスレッドを利用してHTTPによりファイルをダウンロード/保存するクラスを作成した。
ここでは簡単のためスレッド生成数は1つのみで、キューにURLリストをためておき順次ダウンロードしている。
スレッドの生成にはCDnpThreadImpl、キューはCDnpQueを利用した。
CDnpDownloadThread2 _cThread;
//スレッドの生成とダウンロード開始
{
CAtlString strURL;
CAtlString strFile;
strURL = _T("http://www.usefullcode.net/atom.xml");
strFile = _T("_down.xml");
_cThread.StartDownload(strURL,strFile,m_hWnd,NULL);
strURL = _T("http://www.usefullcode.net/index.html");
strFile = _T("_down.html");
_cThread.StartDownload(strURL,strFile,m_hWnd,NULL);
}
//メッセージハンドラ設定
BEGIN_MSG_MAP(CMainDlg)
MESSAGE_HANDLER(WM_DNP_DOWNLOAD_COMPLETE, OnDnpDownloadComplete)
END_MSG_MAP()
//ダウンロード結果の受信
LRESULT OnDnpDownloadComplete(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
bool ret = lParam ? true : false;
ATLTRACE("■ダウンロード%s\n",(ret ? _T("成功") : _T("失敗")));
return 0;
}
■「DnpDownloadThread2.h」の内容
#pragma once
#include "wininet.h"
#pragma comment(lib,"wininet.lib")
#include "atlstr.h"
#include "atlfile.h"
#include "DnpThreadImpl.h"
#include "DnpQue.h"
#define WM_DNP_DOWNLOAD_COMPLETE (WM_APP + 66)
///
///\brief
/// HTTPを利用したダウンロードスレッド
///
///
///
/** \code
CDnpDownloadThread2 _cThread;
//スレッドの生成とダウンロード開始
{
CAtlString strURL;
CAtlString strFile;
strURL = _T("http://www.usefullcode.net/atom.xml");
strFile = _T("_down.xml");
_cThread.StartDownload(strURL,strFile,m_hWnd,NULL);
strURL = _T("http://www.usefullcode.net/index.html");
strFile = _T("_down.html");
_cThread.StartDownload(strURL,strFile,m_hWnd,NULL);
}
//メッセージハンドラ設定
BEGIN_MSG_MAP(CMainDlg)
MESSAGE_HANDLER(WM_DNP_DOWNLOAD_COMPLETE, OnDnpDownloadComplete)
END_MSG_MAP()
//ダウンロード結果の受信
LRESULT OnDnpDownloadComplete(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
bool ret = lParam ? true : false;
ATLTRACE("■ダウンロード%s\n",(ret ? _T("成功") : _T("失敗")));
return 0;
}
\endcode
*/
///
class CDnpDownloadThread2 : protected CDnpThreadImpl
{
HINTERNET _hInternet; //!< 接続用
HINTERNET _hConnect; //!< 接続用
HANDLE _hEvent; //!< ダウンロード情報追加検出イベント用
///
///\brief
/// ダウンロード情報格納用
///
class CItem
{
public:
CAtlString strURL; //!< ダウンロードURL
CAtlString strFile; //!< 保存先ファイル名
HWND hWnd; //!< ダウンロード結果通知先
WPARAM wParam; //!< 結果通知時のユーザー定義パラメーター
CItem()
{
wParam = NULL;
hWnd = NULL;
}
CItem(LPCTSTR pszURL,LPCTSTR pszFile,HWND hUserWnd,WPARAM wUserParam)
{
strURL = pszURL;
strFile = pszFile;
hWnd = hUserWnd;
wParam = wUserParam;
}
};
//
//ダウンロード情報格納用データとそのクリティカルセクション
//
CDnpCriticalSection _sec_queItems; //!< put_queItems() get_queItems() get_queItemsSize() 以外からはアクセスしないこと
CDnpQue<CItem> _queItems; //!< put_queItems() get_queItems() get_queItemsSize() 以外からはアクセスしないこと
protected:
///
///\brief
/// ダウンロード情報の追加
///
///\param[in] cItem
///ダウンロード情報
///
///\retval true 追加成功
///\retval false 追加失敗
///
bool put_queItems(const CItem& cItem)
{
HRESULT hr;
hr = _sec_queItems.Lock();
if(FAILED(hr))
return false;
_queItems.AddData(cItem);
_sec_queItems.Unlock();
return true;
}
///
///\brief
/// ダウンロード情報の取得
///
///\param[out] cItem
///ダウンロード情報
///
///\retval true 取得成功
///\retval false 取得失敗
///
bool get_queItems(CItem& cItem)
{
bool ret;
HRESULT hr;
hr = _sec_queItems.Lock();
if(FAILED(hr))
return false;
ret = _queItems.GetData(cItem);
_sec_queItems.Unlock();
return ret;
}
///
///\brief
/// まだ残っているダウンロード情報数
///
///\param[out] nSize
///ダウンロード情報数
///
///\retval true 取得成功
///\retval false 取得失敗
///
bool get_queItemsSize(size_t& nSize)
{
HRESULT hr;
hr = _sec_queItems.Lock();
if(FAILED(hr))
{
nSize = 0;
return false;
}
nSize = _queItems.GetSize();
_sec_queItems.Unlock();
return true;
}
public:
CDnpDownloadThread2()
{
_hEvent = NULL;
_hInternet = NULL;
_hConnect = NULL;
}
virtual ~CDnpDownloadThread2()
{
////スレッドが破棄されていなければASSERT
//ATLASSERT(_dwThreadID == 0 && _hThread == 0);
DestroyThread(2000); //スレッド終了を最大2秒待つ
}
///
///\brief
/// ダウンロード情報の追加およびダウンロード開始
///
///ほかにダウンロード中のファイルがある場合はそのダウンロードが終わるまで
///ダウンロードは開始しない
///
///\param[in] pszURL
///ダウンロードしたいURL
///
///\param[in] pszFile
///保存先ファイル
///
///\param[in] hWnd
///結果の通知先HWND
///
///\param[in] wParam
///結果通知時のユーザー定義パラメーター
///
///\retval true 成功(情報の追加成功であって、ダウンロード結果ではない)
///\retval false 失敗
///
bool StartDownload(LPCTSTR pszURL,LPCTSTR pszFile,HWND hWnd,WPARAM wParam)
{
bool ret;
ret = put_queItems(CItem(pszURL,pszFile,hWnd,wParam));
if(ret == false)
return false;
if(_hEvent == NULL)
_hEvent = ::CreateEvent(NULL,FALSE,TRUE,_T("CDnpDownloadThread2"));
::SetEvent(_hEvent);
if(IsThreadRunning())
return true;
return CreateThread(NULL) ? true : false;
}
///
///\brief
/// ダウンロードの中断
///
bool AbortDownload(void)
{
return AbortThread();
}
protected:
///
///\brief
/// スレッドメイン処理
///
/// CDnpThreadImpl::ThradMain_() から呼ばれる
///ユーザーは呼び出さないこと
///
///\param pParam
/// CreateThread() で渡したユーザー定義のパラメータ
///
virtual DWORD ThreadMain(void* pParam)
{
bool ret;
size_t nSize;
HANDLE hEvent;
hEvent = ::OpenEvent(EVENT_ALL_ACCESS,FALSE,_T("CDnpDownloadThread2"));
while(1)
{
//500ミリ秒ごともしくはイベントセット時に処理する
::WaitForSingleObject(hEvent,500);
// IsAbort() でスレッド中断を検出して関数から抜ける
if(IsAbort())
break;
ret = get_queItemsSize(nSize);
if(ret && nSize == 0)
{
::ResetEvent(hEvent);
continue;
}
{
CItem cItem;
bool ret;
HANDLE hFile;
CAtlFile cFile;
ret = get_queItems(cItem);
if(ret == false)
continue;
ret = false;
cFile.Create(cItem.strFile,GENERIC_WRITE,0,CREATE_ALWAYS);
hFile = cFile.Detach();
if(hFile)
{
ret = Download(cItem.strURL,hFile);
::CloseHandle(hFile);
}
if(cItem.hWnd && ::IsWindow(cItem.hWnd))
::PostMessage(cItem.hWnd,WM_DNP_DOWNLOAD_COMPLETE,cItem.wParam,(LPARAM)ret);
}
}
::CloseHandle(hEvent);
return 0;
}
///
///\brief
/// スレッド中断処理開始用
///
///スレッドを中断したいときに呼び出す
///
///\retval true 設定成功
///\retval false 失敗
///
bool AbortThread(void)
{
if(_hEvent)
::SetEvent(_hEvent);
if(_hConnect)
::InternetCloseHandle(_hConnect);
if(_hInternet)
::InternetCloseHandle(_hInternet);
_hConnect = NULL;
_hInternet = NULL;
if(_hEvent)
::CloseHandle(_hEvent);
_hEvent = NULL;
return CDnpThreadImpl::AbortThread();
}
///
///\brief
/// HTTPを利用したダウンロード関数
///
///ダウンローが終了(もしくは失敗や中断)するまでreturnしない
///
///\param[in] pszURL
///ダウンロードするURL
///
///\param hFile
///保存先ファイルのハンドル
///
///\param[in] dwBuffSize
///受信バッファーサイズ
///
///\retval true ダウンロードは正常に終了
///\retval false 失敗
///
///
virtual bool Download(LPCTSTR pszURL,HANDLE hFile,DWORD dwBuffSize=1024)
{
TCHAR pszHeader[] = _T("Accept: */*");
BOOL ret;
DWORD dwReadSize;
DWORD dwWrittenSize;
BYTE* pcbBuff;
pcbBuff = new BYTE[dwBuffSize];
if(pcbBuff == NULL || IsAbort())
return false;
_hInternet = ::InternetOpen(NULL,INTERNET_OPEN_TYPE_PRECONFIG,NULL,NULL,0);
if(_hInternet == NULL || IsAbort())
{
delete pcbBuff;
return false;
}
_hConnect = ::InternetOpenUrl(_hInternet,pszURL,pszHeader,lstrlen(pszHeader),INTERNET_FLAG_DONT_CACHE,0);
if(_hConnect == NULL || IsAbort())
{
::InternetCloseHandle(_hInternet);
_hInternet = NULL;
delete pcbBuff;
return false;
}
while(1)
{
//if(IsAbort())
// break;
::Sleep(0);
ret = ::InternetReadFile(_hConnect,pcbBuff,dwBuffSize,&dwReadSize);
if(ret == FALSE)
break;
if(dwReadSize == 0 || IsAbort())
break;
ret = ::WriteFile(hFile,pcbBuff,dwReadSize,&dwWrittenSize,NULL);
if(ret == FALSE)
break;
}
if(ret)
ret = ::FlushFileBuffers(hFile);
::InternetCloseHandle(_hConnect);
::InternetCloseHandle(_hInternet);
_hConnect = NULL;
_hInternet = NULL;
delete pcbBuff;
return (ret == FALSE || IsAbort()) ? false : true;
}
};
