« 2006年11月 | メイン | 2007年01月 »


2006年12月 記事一覧

2006年12月01日

ファイルをゴミ箱に移動する

ファイルをゴミ箱に移動したいときはSHFileOperationを利用する。この関数は複数のファイルのコピーをコピー、移動、名前変更、削除できるように設計されている。ここではSHFileOperationの機能を一部だけ利用して、1ファイルの削除を行う処理を実装した。

注意しなければならないことは、SHFileOperationは処理するファイルを複数指定できるように設計されている。そのためファイルパスをNULL区切りで渡し、最後のファイルパス後にNULL文字を2つ付加しなければならない。このとき非ユニコードビルドのときはNULLを1バイト付加して合計2バイトのNULL文字が並ぶようにする。ユニコードビルド時は合計4バイトのNULLが並ぶようにする。

今回は1つのファイルを処理するため、動的に確保したメモリにファイルパスをコピーし、NULL文字を追加している。そのため作成したDeleteFileToRecycleBin関数にファイル名を指定するときは従来どおりのファイル名を渡せばいい。

#include "shellapi.h"

//
//	ファイルをゴミ箱へ削除
//
//SHFileOperationは複数ファイルを一括して処理できるが、ここでは1ファイルのみ削除できるようにした
//pszFileのファイル名部分にワイルドカード「*」や「?」を使うこともできる
//削除処理中の進行状況表示画面などは表示しない
//
bool	DeleteFileToRecycleBin(LPCTSTR pszFile)
{
	int		ret;
	size_t	nLen;
	TCHAR*	pszFrom;
	SHFILEOPSTRUCT	sShFileOp;

	if(pszFile == NULL)
		return	false;

	//ファイルパスの文字数取得
	nLen = _tcslen(pszFile);
	if(nLen == 0)
		return	false;

	//メモリ確保とファイルパスコピー
	pszFrom = new TCHAR[nLen + 10];
	if(pszFrom == NULL)
		return	false;
	_tcscpy_s(pszFrom,nLen + 10,pszFile);

	//ファイルパスの後ろにNULL文字を付加(Unicodeビルド時は2つのNULLバイトが付加される)
	pszFrom[nLen + 1] = NULL;

	//ファイル削除
	::ZeroMemory(&sShFileOp,sizeof(SHFILEOPSTRUCT));
	sShFileOp.wFunc		= FO_DELETE;
	sShFileOp.pFrom		= pszFrom;
	sShFileOp.fFlags	= FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT;
	ret = ::SHFileOperation(&sShFileOp);

	//メモリ開放
	delete	pszFrom;

	return	(ret == 0) ? true : false;
}

void	Test(void)
{
	bool	ret;

	ret = DeleteFileToRecycleBin(_T("c:\\test.txt"));

	if(ret)
		::MessageBox(NULL,_T("削除しました"),_T(""),MB_OK);
	else
		::MessageBox(NULL,_T("削除に失敗しました"),_T(""),MB_OK);
}

プロジェクトファイルをダウンロード

ゴミ箱を空にする

ファイルなどをゴミ箱に削除するのはSHFileOperationを、ゴミ箱を空にするにはSHEmptyRecycleBinを利用する。
SHEmptyRecycleBinは各ドライブごとに存在するゴミ箱を個別に削除できるが、ここでは一括前ドライブのゴミ箱を空にしている。

#include "shellapi.h"

//
//	ゴミ箱を空にする
//
//ゴミ箱が既に空でもtrueが返る
//
bool	EmptyRecycleBin(void)
{
	HRESULT	hr;

	hr = ::SHEmptyRecycleBin(NULL,NULL,SHERB_NOCONFIRMATION | SHERB_NOPROGRESSUI | SHERB_NOSOUND);

	return	SUCCEEDED(hr) ? true : false;
}



void	Test(void)
{
	bool	ret;

	ret = EmptyRecycleBin();

	if(ret)
		::MessageBox(NULL,_T("ゴミ箱を空にしました"),_T(""),MB_OK);
	else
		::MessageBox(NULL,_T("ゴミ箱を空にできませんでした"),_T(""),MB_OK);
}

プロジェクトファイルをダウンロード

2006年12月02日

Internet Explorerの履歴をクリアする

IEの履歴を削除するにはIUrlHistoryStg2インターフェースを利用する。このCOMは実装されたのが比較的最近のためInternet Explorer 5.5以降、かつ、Windows Me、Windows 2000以降で利用できる。Windows 95/98/NT 4.0では利用できないので注意。

また、バグによりInternet Explorer 6 SP1でWindows Updateをしていない場合は実行した日の履歴が削除されないことがあるらしい。

#include "mshtml.h"
#include "shlguid.h"
#include "urlhist.h"


//
//	Internet Exploreの履歴を削除する
//
// IE5.5以降対応
// Windows Me/2000以降対応
//
bool	ClearInternetExplorerHistry(void)
{
	HRESULT		hr;
	IUrlHistoryStg2*	pIUrlHistoryStg2; 

	hr = ::CoCreateInstance(CLSID_CUrlHistory,NULL,CLSCTX_INPROC_SERVER,IID_IUrlHistoryStg2,reinterpret_cast<void**>(&pIUrlHistoryStg2));
	if(FAILED(hr) || pIUrlHistoryStg2 == NULL)
		return	false;

	hr = pIUrlHistoryStg2->ClearHistory();

	pIUrlHistoryStg2->Release();

	return	SUCCEEDED(hr) ? true : false;
}


void	Test(void)
{
	bool	ret;

	//COM初期化
	::CoInitialize(NULL); 

	ret = ClearInternetExplorerHistry();

	if(ret)
		::MessageBox(NULL,_T("IEの履歴を空にしました"),_T(""),MB_OK);
	else
		::MessageBox(NULL,_T("IEの履歴を空にできませんでした"),_T(""),MB_OK);

	//COM開放
	::CoUninitialize(); 
}

プロジェクトファイルをダウンロード

Visual Studio 2005ではWindows 95用アプリを作れない?!

MSDNライブラリを見ていてふと目に留まった項目があった。

test30_00.gif
サポートされるプラットフォームの中にWindows 95がない!ということだ。Windows NT 4.0も消えている。あるのはWindows 98以降とWindows 2000以降のみ。ちょっとこれにはビックリ。

test30_01.gif
本当にWindows 95をサポートしていないのかな?と思い簡単に確かめることにしてみた。
Visual Studio 2005を起動して「ファイル」メニューの「新規作成」から「プロジェクト」を選択する。そして「MFCアプリケーション」のプロジェクトを作成する。

test30_02.gif
MFCアプリケーションウィザードが開いたらすぐに「完了」ボタンを押さずに次へ進む。

test30_03.gif
デフォルトでは「ユニコードライブラリを使用する」にチェックが入っている。このチェックを外して「完了」ボタンを押す。Windows 9x系では基本的にユニコードは使えないために外している。


test30_04.gif
プロジェクトが作成できたら、左側の「ソリューションエクスプローラ」にある「stdafx.h」をダブルクリックして開く。そしてプラットフォーム用の定義を全て一番最低ラインの「0x0400」に変更する。


test30_05.gif
VC++で作成したEXEファイルは実行にいくつかのDLLファイルが必要なことが多い。依存環境を調べてDLLをコピーしてもいいが、手軽に実行用の環境を作るためにインストーラーを作る。
「ファイル」メニューにある「追加」から「新しいプロジェクト」を選択する。


test30_06.gif
プロジェクトの種類として「セットアップと配置」を選び、テンプレートは「セットアップウィザード」を指定して「OK」ボタンを押す。


test30_07.gif
これでインストーラーを作るためのセットアップウィザードが開いた。


test30_08.gif
プロジェクトの種類はデフォルトの「Windowsアプリケーションのセットアップを作成する」を利用する。


test30_09.gif
ここで「○○のプライマリ出力」にチェックを入れる。これで先ほど作成したMFCアプリケーションのEXEファイルがインストールされる。


test30_10.gif
必要な設定は済んだので「完了」ボタンを押してもいいが、一応内容を見るために順次「次へ」ボタンを押す。


test30_11.gif
これで「完了」ボタンを押してセットアッププロジェクトを作成する。


test30_12.gif
作成できたらVisual Studio 2005の画面上部にあるビルドモードの選択欄で「Release」を選択する。


test30_13.gif
そして「ビルド」メニューにある「ソリューションのビルド」を選択してビルドする。


test30_14.gif
この状態だとまだインストーラーまでは作成されないので、画面左側の「ソリューションエクスプローラー」内のセットアッププロジェクトを右クリックして現れるメニューから「ビルド」を選択してインストーラーを作成する。

test30_15.gif
ここまでできたらテスト環境を起動する。まずはWindows 98(Second Edition)でテストすることにした。テスト用のPCを用意してもいいが、今はVirtual PCが無料化されたのでこのような仮想マシン環境を使った方が楽だ。


test30_16.gif
Windows 98を起動したらVisual Studio 2005によって作成した「Setup.exe」と「Setup.msi」をコピーして起動する。これでインストーラーが起動する。


test30_17.gif
「次へ」ボタンをどんどん押してインストールを進める。インストール先のフォルダはデフォルトのままなので「c:\Program Files\既定の会社名\Setup1\」という変な場所になっている。


test30_18.gif


test30_19.gif
これでインストールが完了した。


test30_20.gif
エクスプローラでインストール先のフォルダを開く。するとちゃんと作成したMFCアプリケーションのEXEファイルがあることが分かる。これをダブルクリックして実行する。


test30_21.gif
するとエラーも出ずにちゃんと起動した。


test30_22.gif
Windows 98ではきちんと動作したので、次に問題のWindows 95でテストすることにする。ここではWindows 95 OSR2を用意した。


test30_23.gif
Windows 98のときと同じようにインストールする。インストーラーは正常に動くようだ。


test30_24.gif
そしてインストール先のEXEをダブルクリックして実行する。


test30_25.gif
...すると「プログラム開始エラー」のダイアログと「システムに装着してあるデバイスは動作していません。」というメッセージが表示された。本当にWindows 95では動かないようだ。


Dependency Walkerで調べるといくつかのDLLが使えていないようだった。がんばればWindows 95でも動作するEXEを作ることはできるだろう。しかしVisual Studio 2005でサポート外という事実は変わらない。そのためそこまでがんばるメリットもないだろう。
ということでWindows 95やWindows NT 4.0など古いプラットフォームでも動くソフトを開発したい場合はVisual Studio 2005以外を選択するしかないようだ。

プロジェクトファイルをダウンロード

DDKの無料ダウンロードが開始されていた!

ddk01.gif
Windows用のデバイスドライバー開発キット(DDK)と言えば、昔は無料でダウンロードできたがWindows 2000辺りからだったかダウンロード提供が中止されていた。手に入れるには有料のMSDNサブスクリプションに入るか、ホームページのオーダーフォームで3000円ぐらい払って購入するしかなかった。

ddk02.gif
以前(2004年年末)、私はMicrosoftのホームページ上で注文したことがある。クレジットカードで3000円ちょっと支払い、実際に引き落としまではあったが...いつになってもDDKは届かなかったということがある。そんな苦い思い出があるためDDKとは無縁の生活を送っていたのだが...

ddk03.gif
知らないうちに無料でダウンロードが開始されていた。ダウンロードページはhttp://www.microsoft.com/japan/whdc/DevTools/ddk/default.mspxにある。ISOイメージで236MB。ダウンロードできるのはWindows XP/2000/2003Server用のDDK「Windows Server 2003 SP1 DDK」のみとなっている。ファイル名は「1830_usa_ddk.iso」。
残念ながらWindows Vista用のDDK「Windows Vista WDK」はダウンロードできないようだ。

ddk04.gif
これで何度かちょっとかじっては諦め...というのを繰り返してきたDDKに再び挑戦できる環境は整った。後はやる気と根性だけかな?

ddk05.gif
インストール画面では、デフォルトではサンプルがインストールされない。

ddk06.gif
サンプルにはかなり色々なデバイス用のソースコードが含まれているのでインストールは必須だろう。全部インストールするのに必要な容量は746MBになる。

ddk07.gif
ちなみにDDKをダウンロードではなく注文する場合も2006年12月現在、無料のようだ。

ddk08.gif
開発環境にはx64やAMD64など64ビット環境向けのものも含まれる。またインストール後のフォルダざっとを見てみたらATL2.1、ATL3.0、MFC4.2など古いライブラリコードも入っているようだった。かなり新旧織り交ぜられている印象だ。

ddk09.gif
これでインストール終了。インストールの最終画面には要した時間が表示されている。私の場合は24分57秒かかったようだ。このDDKのインストーラーは起動時にスプラッシュ画面が表示されるし、インストールにかかった時間が計測されているし...かなり開発者の"趣味"が入っているようだ。


2006年12月03日

OSに関係なく特殊フォルダのパスを取得する

デスクトップやお気に入り、マイドキュメントなどの特殊フォルダにアクセスするとき、例えば「c:\windows\」や「c:\Program Files\」のようにソースコードに書いてはいけない。必ず専用のAPIを利用してパスを取得する必要がある。取得にはSHGetFolderPath、SHGetSpecialFolderPath、SHGetFolderLocationなどを利用する。

現在のプラットフォームでは何も考えずにSHGetFolderPathを利用すればいい。しかし古いプラットフォームもサポートしたプログラムにしようと考えるとやっかいなことになる可能性がある。SHGetFolderPathをshfolder.libに実装したりshell32.dllに変えたり...と過去に何回かの変更があるからだ。
ここで紹介する関数は現在のプラットフォームではSHGetFolderPathを、古いものではSHGetSpecialFolderPathを利用して特殊フォルダのパスを取得している。

取得できるフォルダには...
CSIDL_ADMINTOOLS
CSIDL_COMMON_ADMINTOOLS
CSIDL_APPDATA
CSIDL_COMMON_APPDATA
CSIDL_COMMON_DOCUMENTS
CSIDL_COOKIES
CSIDL_HISTORY
CSIDL_INTERNET_CACHE
CSIDL_LOCAL_APPDATA
CSIDL_MYPICTURES
CSIDL_PERSONAL
CSIDL_PROGRAM_FILES
CSIDL_PROGRAM_FILES_COMMON
CSIDL_SYSTEM
CSIDL_WINDOWS
などなどがある。これらはshlobj.hに定義されているのでそちらを参考にするといいだろう。

#include "shlobj.h"


//
//	SHGetSpecialFolderPathの全プラットフォーム対応バージョン
//
BOOL	SafeSHGetSpecialFolderPath(HWND hwndOwner,LPTSTR lpszPath,int nFolder,BOOL fCreate)
{
	BOOL	(CALLBACK* g_pfnSHGetSpecialFolderPath)(HWND,LPTSTR,int,BOOL);
	HRESULT	(CALLBACK* g_pfnSHGetFolderPath)(HWND,int,HANDLE,DWORD,LPTSTR);

	int			i;
	BOOL		ret;
	HRESULT		hr;
	HMODULE		hDLL;
	TCHAR		pszDllFile[][15] = {_T("shfolder.dll"),_T("shell32.dll")};

	if(lpszPath == NULL)
		return	FALSE;
	*lpszPath = NULL;

	ret = FALSE;
	for(i = 0; i < 2; i++)
	{
		hDLL = ::LoadLibrary(pszDllFile[i]);
		if(hDLL == NULL)
			continue;

		#ifdef UNICODE
			(*(FARPROC*)&g_pfnSHGetSpecialFolderPath = ::GetProcAddress(hDLL,"SHGetSpecialFolderPathW"));
			(*(FARPROC*)&g_pfnSHGetFolderPath = ::GetProcAddress(hDLL,"SHGetFolderPathW"));
		#else
			(*(FARPROC*)&g_pfnSHGetSpecialFolderPath = ::GetProcAddress(hDLL,"SHGetSpecialFolderPathA"));
			(*(FARPROC*)&g_pfnSHGetFolderPath = ::GetProcAddress(hDLL,"SHGetFolderPathA"));
		#endif

		if(g_pfnSHGetSpecialFolderPath)
			ret = g_pfnSHGetSpecialFolderPath(hwndOwner,lpszPath,nFolder,fCreate);
		if(ret == FALSE && g_pfnSHGetFolderPath)
		{
			hr = g_pfnSHGetFolderPath(hwndOwner,nFolder | (fCreate ? CSIDL_FLAG_CREATE : 0),NULL,SHGFP_TYPE_DEFAULT,lpszPath);
			ret = (SUCCEEDED(hr)) ? TRUE : FALSE;
		}

		::FreeLibrary(hDLL);

		if(ret)
			return	TRUE;
	}

	return	FALSE;
}



void	Test(void)
{
	BOOL	ret;
	TCHAR	pszPath[MAX_PATH*2];

	ret = SafeSHGetSpecialFolderPath(NULL,pszPath,CSIDL_DESKTOPDIRECTORY,FALSE);

	if(ret)
		::MessageBox(NULL,pszPath,_T(""),MB_OK);
	else
		::MessageBox(NULL,_T("取得に失敗しました"),_T(""),MB_OK);
}

プロジェクトファイルをダウンロード

物理メモリ容量を取得する

パソコンに積まれたメモリ容量を取得します。いくつか方法があるがここではIShellDispatch2を利用した。

このCOMインターフェースを利用するとCPUクロックも取得できるはずだが、Core Duo環境では失敗したのでクロック取得には使わないほうがいいだろう。それでもクロックを取得したい場合はL"PhysicalMemoryInstalled"の部分をL"ProcessorSpeed"にすればいい。

依存環境:ATL
#include "atlstr.h"
#include "shldisp.h"


//
//	PCに載っている物理メモリ量を取得する
//
//単位はbytes
//対応はWindows Me/2000以降
//
//失敗時はゼロを返す
//
ULONGLONG	GetPhysicalMemoryInstalled(void)
{
	IShellDispatch2*	pIShellDispatch2;
    HRESULT		hr;
	VARIANT		val;
	BSTR		bstr;

	pIShellDispatch2 = NULL;
	hr = ::CoCreateInstance(CLSID_Shell,NULL,CLSCTX_INPROC_SERVER,IID_IShellDispatch2,(void**)&pIShellDispatch2);
	if(FAILED(hr) || pIShellDispatch2 == NULL)
		return	0;

	::VariantClear(&val);
	bstr = ::SysAllocString(L"PhysicalMemoryInstalled");
	hr = pIShellDispatch2->GetSystemInformation(bstr,&val);
	::SysFreeString(bstr);
	pIShellDispatch2->Release();

	if(FAILED(hr) || val.vt != VT_R8)
		return	0;

	return	V_R8(&val);
}



void	Test(void)
{
	ULONGLONG	nMemory;
	CAtlString	strMessage;

	::CoInitialize(NULL);


	nMemory = GetPhysicalMemoryInstalled();

	strMessage.Format(_T("%ld MB"),nMemory / (1024*1024));
	::MessageBox(NULL,strMessage,_T(""),MB_OK);

	::CoUninitialize();
}

プロジェクトファイルをダウンロード

CPU数などのCPU関連情報を取得する

Windows XPから使えるようになったAPIにGetNativeSystemInfoがある。この関数を利用するとCPU数、CPUアーキテクチャ種(64ビット環境か32ビットかなど)の情報を取得できる。

依存環境:ATL
#include "atlstr.h"


//
//	CPU情報の取得
//
//取得できるのはCPU数など一部の情報のみ。
//実際のCPU型番(「Core Duo T2600」など)はこの関数では取得できない。
//とは言うもののCPU型番を推測するための情報として利用できる。
//
//pwProcessorArchitectureなどの詳しい情報はMSDNのSYSTEM_INFOを参照すること
//
//対応:Windows XP以降
//
bool	GetCPUInformation(DWORD* pdwNumberOfProcessors,WORD* pwProcessorArchitecture,DWORD* pdwProcessorType,WORD* pwProcessorLevel,WORD* pwProcessorRevision)
{
	SYSTEM_INFO	sInfo;
	HMODULE		hDLL;

	void	(CALLBACK* pfnGetNativeSystemInfo)(LPSYSTEM_INFO);

	if(pdwNumberOfProcessors)
		*pdwNumberOfProcessors = 0;
	if(pwProcessorArchitecture)
		*pwProcessorArchitecture = 0;
	if(pdwProcessorType)
		*pdwProcessorType = 0;
	if(pwProcessorLevel)
		*pwProcessorLevel = 0;
	if(pwProcessorRevision)
		*pwProcessorRevision = 0;

	hDLL = ::LoadLibrary(_T("Kernel32.dll"));
	if(hDLL == NULL)
		return	false;

	(*(FARPROC*)&pfnGetNativeSystemInfo) = ::GetProcAddress(hDLL,"GetNativeSystemInfo");
	if(pfnGetNativeSystemInfo == NULL)
	{
		::FreeLibrary(hDLL);
		return	false;
	}

	::ZeroMemory(&sInfo,sizeof(SYSTEM_INFO));
	pfnGetNativeSystemInfo(&sInfo);
	::FreeLibrary(hDLL);

	if(pdwNumberOfProcessors)
		*pdwNumberOfProcessors = sInfo.dwNumberOfProcessors;
	if(pwProcessorArchitecture)
		*pwProcessorArchitecture = sInfo.wProcessorArchitecture;
	if(pdwProcessorType)
		*pdwProcessorType = sInfo.dwProcessorType;
	if(pwProcessorLevel)
		*pwProcessorLevel = sInfo.wProcessorLevel;
	if(pwProcessorRevision)
		*pwProcessorRevision = sInfo.wProcessorRevision;

	return	true;
}




void	Test(void)
{
	bool		ret;
	DWORD		dwNumberOfProcessors;
	CAtlString	strMessage;

	ret = GetCPUInformation(&dwNumberOfProcessors,NULL,NULL,NULL,NULL);

	if(ret)
	{
		strMessage.Format(_T("CPU(コア)は %d 個"),dwNumberOfProcessors);
		::MessageBox(NULL,strMessage,_T(""),MB_OK);
	}
	else
		::MessageBox(NULL,_T("取得に失敗しました"),_T(""),MB_OK);
}

プロジェクトファイルをダウンロード

2006年12月04日

使うAPIの対応OSに注意しよう

test34_01.gif
MSDNライブラリを見ていると新しいOSやサービスパック、Internet ExplorerがリリースされるたびにAPIなどが増えていることに気づく。

例えばWindows XPから追加されたものの1つのGetNativeSystemInfoというのがある。これはCPUの数などのシステム情報を取得するためのAPIだ。MSDNには左の図のように掲載されている。

ここで赤く囲んだところが注意すべきところだ。このAPIの場合はWindows XP、Vista、Windows 2003 Serverなどに対応していることが分かる。


実際にこのAPIを利用して簡単なテストプログラムを作ってみる。搭載されているCPU数を取得して表示するだけの簡単なものだ。

#include "atlstr.h"

void	Test(void)
{
	CAtlString	strMessage;
	SYSTEM_INFO	sInfo;

	::GetNativeSystemInfo(&sInfo);

	strMessage.Format(_T("CPU(コア)は %d 個"),sInfo.dwNumberOfProcessors);
	::MessageBox(NULL,strMessage,_T(""),MB_OK);
}
プロジェクトファイルをダウンロード

test34_04.gif

私のパソコンはCPUにCore Duoが使われているのでコア数が2個。そのためWindows XP環境で実行するとこのように表示された。


次に作成した実行ファイルをWindows 2000へコピーして実行してみた。すると...

test34_02.gif

このようなエラーメッセージが表示されて実行できなかった。

作成したプログラムの中に1つでも対応OSがWindows XP以降のAPIが含まれていた場合、そのプログラムはWindows 2000では起動もしなくなってしまう。これが対応OSに注意を払わなければいけない理由だ。


ではどうすればWindows 2000でも起動するプログラムにできるかと言うと、LoadLibraryで使いたいAPIが実装されているDLLファイルを読み込み、GetProcAddressでそのAPIのアドレスを取得してAPIを実行する。そうすればAPIが対応していないOSの場合は、GetProcAddressに失敗するだけで起動しなくなるという問題は起きない。
GetNativeSystemInfoを例に具体的に書くと以下のようになる。

#include "atlstr.h"


//
//	GetNativeSystemInfoラッパー関数
//
//GetNativeSystemInfoはWindows XP以降でサポートされている。
//そのためWindows 2000以前ではfalseが返る
//
bool	SafeGetNativeSystemInfo(LPSYSTEM_INFO lpSystemInfo)
{
	HMODULE	hDLL;

	void	(CALLBACK* pfnGetNativeSystemInfo)(LPSYSTEM_INFO);

	if(lpSystemInfo == NULL)
		return	false;
	::ZeroMemory(lpSystemInfo,sizeof(SYSTEM_INFO));

	hDLL = ::LoadLibrary(_T("Kernel32.dll"));
	if(hDLL == NULL)
		return	false;

	(*(FARPROC*)&pfnGetNativeSystemInfo) = ::GetProcAddress(hDLL,"GetNativeSystemInfo");
	if(pfnGetNativeSystemInfo == NULL)
	{
		::FreeLibrary(hDLL);
		return	false;
	}

	pfnGetNativeSystemInfo(lpSystemInfo);
	::FreeLibrary(hDLL);

	return	true;
}




void	Test(void)
{
	bool		ret;
	CAtlString	strMessage;
	SYSTEM_INFO	sInfo;

	ret = SafeGetNativeSystemInfo(&sInfo);

	if(ret)
	{
		strMessage.Format(_T("CPU(コア)は %d 個"),sInfo.dwNumberOfProcessors);
		::MessageBox(NULL,strMessage,_T(""),MB_OK);
	}
	else
		::MessageBox(NULL,_T("取得に失敗しました。"),_T(""),MB_OK);
}
プロジェクトファイルをダウンロード

これでAPIが対応していないWindows 2000などで実行すると以下のようにプログラム中で実装したエラーメッセージが表示される。

test34_03.gif


1番上のMSDNライブラリ画像中の赤い枠で示した部分には対応OS以外にもLoadLibraryで利用するDLL名なども書かれている。普段はあまり目がいかないかもしれないが、かなり重要なデータだ。初めて実装に使うAPIの場合は必ず確認しよう。

フォルダ選択ダイアログを表示する

test35.gif
フォルダ選択ダイアログを表示するにはSHBrowseForFolderを利用する。これはダイアログを開いたときの初期選択フォルダ設定をコールバックで指定する必要があるなど少し使い方に癖のあるAPIだ。

#include "atlstr.h"
#include "shlobj.h"



//	初期フォルダ設定用のコールバック関数
int		CALLBACK	_SHBrowseForFolderCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
	if(uMsg == BFFM_INITIALIZED)
		::SendMessage(hwnd,BFFM_SETSELECTION,TRUE,lpData);

	return	0;
}


//
//	フォルダー選択ダイアログの表示
//
//		結果は"c:\windows\"のように末尾に必ず"\"が付加する
//
bool	SelectFolderDlg(HWND hWndParent,CAtlString* lpstrFolder,LPCTSTR pszIniFolder,bool bAvailNewFolder)
{
	bool			ret;
	TCHAR			lpszPath[MAX_PATH];
	LPMALLOC		lpMalloc;
	BROWSEINFO		sInfo;
	LPITEMIDLIST	lpidlRoot;
	LPITEMIDLIST	lpidlBrowse;
	CAtlString		strIniFolder;

	if(lpstrFolder == NULL)
		return	false;
	strIniFolder = pszIniFolder;

	if(::SHGetMalloc(&lpMalloc) != NOERROR)
		return	false;

	ret = false;
	*lpstrFolder = _T("");
	if(strIniFolder != _T(""))
	{
		if(strIniFolder.Right(1) == _T("\\"))
			strIniFolder = strIniFolder.Left(strIniFolder.GetLength() - 1);			//末尾の\\を除去
	}

	lpidlRoot = NULL;
	::SHGetSpecialFolderLocation(NULL,CSIDL_DRIVES,&lpidlRoot);	//選択可能フォルダ名取得

	::ZeroMemory(&sInfo, sizeof(BROWSEINFO));
	sInfo.pidlRoot		= lpidlRoot;
	sInfo.pszDisplayName = lpszPath;
	sInfo.lpszTitle		= _T("フォルダの選択");
	sInfo.ulFlags		= BIF_RETURNONLYFSDIRS;
	if(bAvailNewFolder == true)
		sInfo.ulFlags	|= BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_USENEWUI;
	sInfo.lpfn			= _SHBrowseForFolderCallbackProc;
	sInfo.lParam		= (LPARAM)strIniFolder.GetBuffer(0);
	sInfo.hwndOwner		= hWndParent;

	lpidlBrowse = ::SHBrowseForFolder(&sInfo);			//フォルダ選択ダイアログ表示
	if(lpidlBrowse != NULL)
	{
		::SHGetPathFromIDList(lpidlBrowse,lpszPath);	//フォルダ名の取得
		*lpstrFolder = lpszPath;

		if(*lpstrFolder != _T(""))
		{
			if(lpstrFolder->Right(1) != _T("\\"))
				*lpstrFolder += _T("\\");			//末尾に\\が付加することを保証
		}

		ret = true;
	}

	if(lpidlBrowse != NULL)
		::CoTaskMemFree(lpidlBrowse);
	if(lpidlRoot != NULL)
		::CoTaskMemFree(lpidlRoot);

	lpMalloc->Release();

	return	ret;
}



void	Test(void)
{
	bool		ret;
	CAtlString	strMessage;

	ret = SelectFolderDlg(NULL,&strMessage,_T("c:\\"),false);

	if(ret)
		::MessageBox(NULL,strMessage,_T(""),MB_OK);
	else
		::MessageBox(NULL,_T("取得に失敗しました"),_T(""),MB_OK);
}

プロジェクトファイルをダウンロード

日本語版WTL 8.0のアプリケーションウィザード

test36_01.gif
■はじめに
WTLはライセンスがCPLのオープンソースプロジェクトとして公開されている。そのためおそらく勝手に日本語化したものを配布しても問題ないと思い用意しました。ライセンス的に問題があるようでしたらお知らせください。削除します。

test36_02.gif
■メリット
従来の英語版WTLではプロジェクト作成後にリソースエディタでメニューなどの言語を日本語に切り替えて、表示文字列を編集する必要がありました。しかしこの日本語版WTL 7.5ではプロジェクト生成直後から日本語リソースが利用可能です。

test36_03.gif
■日本語化内容:
 ・Windows用アプリケーションウィザード
 ・生成されるソースコード
 ・生成されるソースコードのコメント
など

※サンプルやWindows CE用のアプリケーションウィザードはダウンロード可能なファイルに含まれません。
※incldueファイルやreadme.html、cpl.txtは日本語化されていません。
※短時間で翻訳処理したため変な日本語になっている部分があります。

test36_04.gif
■ダウンロード

日本語版WTL 8.0をダウンロード

test36_05.gif
右の画像はデフォルトで生成したアプリケーション例です。
プロジェクトファイルをダウンロード


2006年12月05日

MD5ハッシュを計算する

CryptCreateHashを利用するとハッシュを計算できる。このAPIは利用できるOSに制限があるものもあるがMD5以外にもSHA-1やMD4、SHA-256、SHA-512など様々なハッシュ計算に対応している。

ここで作成した関数は簡単にするためメモリ上のデータを一括してハッシュ計算処理に回している。小さなデータであればこの関数で十分だ。しかしファイルハッシュを計算したいときなどにはこの関数は利用しない方がいい。ファイルハッシュ計算時はファイルを少しずつ読み出しCryptHashDataを何度も呼び出して計算するのがいい。

依存環境:ATL
#include "atlstr.h"

#include "wincrypt.h"

//
//	MD5ハッシュ計算(128ビット)
//
//対応:Windows 95 OSR2以降
//
bool	GetMD5Hash(const void* pData,DWORD dwLen,BYTE pcbHashData[16])
{
	bool		ret;
	HCRYPTPROV	hCryptProv;
	HCRYPTHASH	hHash;
	BYTE		pbHash[0x7f];
	DWORD		dwHashLen;

	::ZeroMemory(pcbHashData,16);
	if(pData == NULL || dwLen == 0)
		return	false;

	dwHashLen = 16;
	hHash = NULL;
	hCryptProv = NULL;
	ret = false;
	if(::CryptAcquireContext(&hCryptProv,NULL,NULL,PROV_RSA_FULL,CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET))
	{
		if(::CryptCreateHash(hCryptProv,CALG_MD5,0,0,&hHash))
		{
			if(::CryptHashData(hHash,(BYTE*)pData,dwLen,0))
			{
				if(::CryptGetHashParam(hHash,HP_HASHVAL,pbHash,&dwHashLen,0)) 
				{
					::memcpy_s(pcbHashData,dwHashLen,pbHash,dwHashLen);
					ret = true;
				}
			}
		}
	}

	if(hHash)
		::CryptDestroyHash(hHash);
	if(hCryptProv)
		::CryptReleaseContext(hCryptProv,0);

	if(ret == false)
		::ZeroMemory(pcbHashData,dwHashLen);

	return	ret;
}





void	Test(void)
{
	bool		ret;
	int			i;
	BYTE		pcbData[16];
	CAtlString	strTmp;
	CAtlString	strMessage;


	//"abc"という文字のMD5ハッシュを計算
	ret = GetMD5Hash("abc",3,pcbData);

	if(ret == false)
	{
		::MessageBox(NULL,_T("取得に失敗しました"),_T(""),MB_OK);
		return;
	}

	for(i = 0; i < 16; i++)
	{
		strTmp.Format(_T("%x"),pcbData[i]);
		strMessage += strTmp;
	}

	::MessageBox(NULL,strMessage,_T(""),MB_OK);
}

プロジェクトファイルをダウンロード

ファイルやIStreamのハッシュを計算する

Windows 95 OSR2以降に対応するCryptHashDataを利用すると簡単にハッシュを求めれる。ここではこのAPIを利用して、メモリ上のデータ、ファイル、IStreamのデータのハッシュを求めるクラスを作成した。簡単に利用できるのはMD2、MD4、MD5、SHA-1ハッシュだが、SHA-256などほかのハッシュもdwFlagsに指定することで計算できる。

「DnpHashEx.h」として保存
#pragma once

#include <wincrypt.h>
#include "objidl.h"


//
//Crypt関連APIを利用してハッシュを計算する
//
//Windows95OSR2以降
//
class	CDnpHashEx
{
public:


	//
	//	MD2ハッシュ計算(128ビット)
	//
	bool	GetMD2Hash(const void* pData,DWORD dwLen,BYTE pcbHashData[16])
	{
		return	GetHash(pData,dwLen,pcbHashData,16,CALG_MD2);
	}


	//
	//	MD4ハッシュ計算(128ビット)
	//
	bool	GetMD4Hash(const void* pData,DWORD dwLen,BYTE pcbHashData[16])
	{
		return	GetHash(pData,dwLen,pcbHashData,16,CALG_MD4);
	}


	//
	//	MD5ハッシュ計算(128ビット)
	//
	bool	GetMD5Hash(const void* pData,DWORD dwLen,BYTE pcbHashData[16])
	{
		return	GetHash(pData,dwLen,pcbHashData,16,CALG_MD5);
	}


	//
	//	SHA-1ハッシュ計算(160ビット)
	//
	bool	GetSHA1Hash(const void* pData,DWORD dwLen,BYTE pcbHashData[20])
	{
		return	GetHash(pData,dwLen,pcbHashData,20,CALG_SHA1);
	}




	//
	//	各種ハッシュ計算
	//
	bool	GetHash(const void* pData,DWORD dwLen,BYTE* pcbHashData,DWORD dwHashLen,DWORD dwFlags)
	{
		bool		ret;
		HCRYPTPROV	hCryptProv;
		HCRYPTHASH	hHash;
		BYTE		pbHash[0x7f];

		::ZeroMemory(pcbHashData,dwHashLen);
		if(pData == NULL || dwLen == 0 || dwHashLen == 0 || dwHashLen > 0x7f)
		{
			ATLASSERT(0);
			return	false;
		}

		hHash = NULL;
		hCryptProv = NULL;
		ret = false;
		if(::CryptAcquireContext(&hCryptProv,NULL,NULL,PROV_RSA_FULL,CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET))
		{
			if(::CryptCreateHash(hCryptProv,dwFlags,0,0,&hHash))
			{
				if(::CryptHashData(hHash,(BYTE*)pData,dwLen,0))
				{
					if(::CryptGetHashParam(hHash,HP_HASHVAL,pbHash,&dwHashLen,0)) 
					{
						::memcpy_s(pcbHashData,dwHashLen,pbHash,dwHashLen);
						ret = true;
					}
				}
			}
		}

		if(hHash)
			::CryptDestroyHash(hHash);
		if(hCryptProv)
			::CryptReleaseContext(hCryptProv,0);

		if(ret == false)
			::ZeroMemory(pcbHashData,dwHashLen);

		return	ret;
	}



	//
	//	MD2ハッシュ計算(128ビット)
	//
	bool	GetMD2FileHash(LPCTSTR pszFile,BYTE pcbHashData[16])
	{
		return	GetFileHash(pszFile,pcbHashData,16,CALG_MD2);
	}


	//
	//	MD4ハッシュ計算(128ビット)
	//
	bool	GetMD4FileHash(LPCTSTR pszFile,BYTE pcbHashData[16])
	{
		return	GetFileHash(pszFile,pcbHashData,16,CALG_MD4);
	}


	//
	//	MD5ハッシュ計算(128ビット)
	//
	bool	GetMD5FileHash(LPCTSTR pszFile,BYTE pcbHashData[16])
	{
		return	GetFileHash(pszFile,pcbHashData,16,CALG_MD5);
	}


	//
	//	SHA-1ハッシュ計算(160ビット)
	//
	bool	GetSHA1FileHash(LPCTSTR pszFile,BYTE pcbHashData[20])
	{
		return	GetFileHash(pszFile,pcbHashData,20,CALG_SHA1);
	}




	//
	//	ファイルハッシュ計算
	//
	bool	GetFileHash(LPCTSTR pszFile,BYTE* pcbHashData,DWORD dwHashLen,DWORD dwFlags)
	{
		bool		ret;
		HCRYPTPROV	hCryptProv;
		HCRYPTHASH	hHash;
		BYTE		pbHash[0x7f];
		HANDLE		hFile;
		ULARGE_INTEGER	lnSize;

		::ZeroMemory(pcbHashData,dwHashLen);
		if(pszFile == 0 || dwHashLen == 0 || dwHashLen > 0x7f)
			return	false;


		hFile = ::CreateFile(pszFile,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
		if(hFile == INVALID_HANDLE_VALUE)
			return	false;


		//ファイルサイズ取得
		lnSize.LowPart = ::GetFileSize(hFile,&lnSize.HighPart);

		hHash = NULL;
		hCryptProv = NULL;
		ret = false;
		if(::CryptAcquireContext(&hCryptProv,NULL,NULL,PROV_RSA_FULL,CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET))
		{
			if(::CryptCreateHash(hCryptProv,dwFlags,0,0,&hHash))
			{
				BOOL			bRet;
				ULARGE_INTEGER	lnPos;
				DWORD	dwRead;
				BYTE	pcbTmp[64000];

				bRet = FALSE;
				lnPos.QuadPart = 0;
				while(1)
				{
					//64KBずつ処理
					bRet = ::ReadFile(hFile,pcbTmp,64000,&dwRead,NULL);
					if(bRet == FALSE)
						break;

					lnPos.QuadPart += dwRead;

					//ハッシュ計算
					bRet = ::CryptHashData(hHash,pcbTmp,dwRead,0);
					if(bRet == FALSE)
						break;

					//処理バイト数とファイルサイズが一致したらbreak
					if(lnPos.QuadPart == lnSize.QuadPart)
						break;
				}

				//ハッシュ取得
				if(bRet && ::CryptGetHashParam(hHash,HP_HASHVAL,pbHash,&dwHashLen,0)) 
				{
					::memcpy_s(pcbHashData,dwHashLen,pbHash,dwHashLen);
					ret = true;
				}
			}
		}

		if(hHash)
			::CryptDestroyHash(hHash);
		if(hCryptProv)
			::CryptReleaseContext(hCryptProv,0);

		::CloseHandle(hFile);

		if(ret == false)
			::ZeroMemory(pcbHashData,dwHashLen);

		return	ret;
	}






	//
	//	MD2ハッシュ計算(128ビット)
	//
	bool	GetMD2IStreamHash(IStream* pIStream,BYTE pcbHashData[16])
	{
		return	GetIStreamHash(pIStream,pcbHashData,16,CALG_MD2);
	}


	//
	//	MD4ハッシュ計算(128ビット)
	//
	bool	GetMD4IStreamHash(IStream* pIStream,BYTE pcbHashData[16])
	{
		return	GetIStreamHash(pIStream,pcbHashData,16,CALG_MD4);
	}


	//
	//	MD5ハッシュ計算(128ビット)
	//
	bool	GetMD5IStreamHash(IStream* pIStream,BYTE pcbHashData[16])
	{
		return	GetIStreamHash(pIStream,pcbHashData,16,CALG_MD5);
	}


	//
	//	SHA-1ハッシュ計算(160ビット)
	//
	bool	GetSHA1IStreamHash(IStream* pIStream,BYTE pcbHashData[20])
	{
		return	GetIStreamHash(pIStream,pcbHashData,20,CALG_SHA1);
	}


	//
	//	IStreamからのハッシュ計算
	//
	bool	GetIStreamHash(IStream* pIStream,BYTE* pcbHashData,DWORD dwHashLen,DWORD dwFlags)
	{
		bool		ret;
		HCRYPTPROV	hCryptProv;
		HCRYPTHASH	hHash;
		BYTE		pbHash[0x7f];
		HRESULT			hr;
		LARGE_INTEGER	lnPos;
		STATSTG			sStatstg;

		::ZeroMemory(pcbHashData,dwHashLen);
		if(pIStream == 0 || dwHashLen == 0 || dwHashLen > 0x7f)
			return	false;

		//ファイル先頭へシーク
		lnPos.QuadPart = 0;
		hr = pIStream->Seek(lnPos,STREAM_SEEK_SET,NULL);
		if(FAILED(hr))
			return	false;

		//ファイルサイズ取得
		::ZeroMemory(&sStatstg,sizeof(STATSTG));
		hr = pIStream->Stat(&sStatstg,STATFLAG_DEFAULT);
		if(FAILED(hr))
			return	false;

		hHash = NULL;
		hCryptProv = NULL;
		ret = false;
		if(::CryptAcquireContext(&hCryptProv,NULL,NULL,PROV_RSA_FULL,CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET))
		{
			if(::CryptCreateHash(hCryptProv,dwFlags,0,0,&hHash))
			{
				BOOL			bRet;
				ULARGE_INTEGER	lnPos;
				ULONG	nRead;
				BYTE	pcbTmp[64000];

				bRet = FALSE;
				lnPos.QuadPart = 0;
				while(1)
				{
					//64KBずつ処理
					hr = pIStream->Read(pcbTmp,64000,&nRead);
					if(FAILED(hr))
						break;

					lnPos.QuadPart += nRead;

					//ハッシュ計算
					bRet = ::CryptHashData(hHash,pcbTmp,(DWORD)nRead,0);
					if(bRet == FALSE)
						break;

					//処理バイト数とファイルサイズが一致したらbreak
					if(lnPos.QuadPart == sStatstg.cbSize.QuadPart)
						break;
				}

				//ハッシュ取得
				if(bRet && ::CryptGetHashParam(hHash,HP_HASHVAL,pbHash,&dwHashLen,0)) 
				{
					::memcpy_s(pcbHashData,dwHashLen,pbHash,dwHashLen);
					ret = true;
				}
			}
		}

		if(hHash)
			::CryptDestroyHash(hHash);
		if(hCryptProv)
			::CryptReleaseContext(hCryptProv,0);

		if(ret == false)
			::ZeroMemory(pcbHashData,dwHashLen);

		return	ret;
	}
};

依存環境:ATL
#include "atlstr.h"

#include "DnpHashEx.h"

//
//	テスト用関数
//
void	Test(void)
{
	bool		ret;
	int			i;
	CAtlString	strTmp;
	CAtlString	strMessage;
	BYTE		pcbData[20];
	CDnpHashEx	cHash;


	//ファイルのSHA-1ハッシュを計算
	ret = cHash.GetSHA1FileHash(_T("c:\\boot.ini"),pcbData);

	if(ret == false)
	{
		::MessageBox(NULL,_T("取得に失敗しました"),_T(""),MB_OK);
		return;
	}

	for(i = 0; i < 20; i++)
	{
		strTmp.Format(_T("%x"),pcbData[i]);
		strMessage += strTmp;
	}

	::MessageBox(NULL,strMessage,_T(""),MB_OK);
}

プロジェクトファイルをダウンロード

Windowsプログラミングでのcharの扱い方

char配列の変数を宣言するときに文字列を入れた状態で初期化する。
////////////////////////////////
//初期文字列代入
//
char	pszSrc[] = {"abcdあいう"};
char*型の変数内の文字数を取得する。
////////////////////////////////
//文字数取得
//
size_t	nLen1 = ::strlen(pszSrc);		//変数内の文字列の文字数取得
size_t	nLen2 = ::strlen("あいうえ");	//指定した文字列の文字数取得
変数へのコピーにはstrcpyではなく、より安全なstrcpy_sを利用する。
////////////////////////////////
//文字列代入
//
char	pszText[256];
::strcpy_s(pszText,256,"あいうえABCD");	//直接代入
::strcpy_s(pszText,256,pszSrc);			//変数から代入
実際のプログラミングではchar*型ではなくCStringもしくはCStringAにしておくほうが楽。
////////////////////////////////
//char*からCStringへの変換
//
CString	strText(pszText);
CStringAというのはプロジェクトの設定に関わらず常にchar型の文字列操作クラスだ。
////////////////////////////////
//char*からCStringA(char型のCString)への変換
//
CStringA	strTextA1(pszText);			//コンストラクタを利用
CStringA	strTextA2 = pszText;		//代入
CStringWは常にWCHAR*型になる。 この操作は文字列のユニコード変換でもある。
////////////////////////////////
//char*からCStringW(WCHAR型のCString)への変換
//
CStringW	strTextW(pszText);
char*型からWCHAR*型への変換。CStringWを利用しないユニコード変換はかなり面倒。
////////////////////////////////
//char*からWCHAR*へ変換
//
char	pszChar[] = {"abcdあいう"};
WCHAR*	pszWchar;
{
	int		nLen;

	//Unicodeに必要な文字数の取得
	nLen = ::MultiByteToWideChar(CP_THREAD_ACP,0,pszChar,-1,NULL,0);
	pszWchar = new WCHAR[nLen];
	if(pszWchar)
	{
		//変換
		nLen = ::MultiByteToWideChar(CP_THREAD_ACP,0,pszChar,(int)::strlen(pszChar)+1,pszWchar,nLen);
		if(nLen == 0)
		{
			delete	pszWchar;
			pszWchar = NULL;
		}
	}
}
//
//ここでWCHAR型のpszWcharを任意の処理で利用
//
delete	pszWchar;		//処理が終わったらdelete

WindowsプログラミングでのWCHARの扱い方

WCHARでは代入などの際に " " ではなく、L" " を利用していることに注意。

変数の宣言時に文字列を指定することもできる。
////////////////////////////////
//初期文字列代入
//
WCHAR	pszSrc[] = {L"abcdあいう"};
文字数取得にstrlen(char型専用)は利用できない。wcslenを利用する。
////////////////////////////////
//文字数取得
//
size_t	nLen1 = ::wcslen(pszSrc);		//変数内の文字列の文字数取得
size_t	nLen2 = ::wcslen(L"あいうえ");	//指定した文字列の文字数取得
文字列の代入にstrlenは利用できない。wcscpyをより安全にしたwcscpy_sを利用する。
////////////////////////////////
//文字列代入
//
WCHAR	pszText[256];
::wcscpy_s(pszText,256,L"あいうえABCD");	//直接代入
::wcscpy_s(pszText,256,pszSrc);				//変数から代入
この変換時、プロジェクトの設定が非ユニコード(MBCSなど)になっているときは正常に変換できない可能性があるので注意。例えばWCHAR*型にハングル文字と日本語などが混在している場合が挙げられる。
////////////////////////////////
//WCHAR*からCStringへの変換
//
CString	strText(pszText);
この場合も変換ができないことがあるので注意。
////////////////////////////////
//WCHAR*からCStringA(char型のCString)への変換
//
CStringA	strTextA(pszText);			//コンストラクタを利用
常にWCHAR*型のCStringWへの変換時はユニコード同士の変換なので文字列が正常にコピーできないことはない。
////////////////////////////////
//WCHAR*からCStringW(WCHAR型のCString)への変換
//
CStringW	strTextW1(pszText);			//コンストラクタを利用
CStringW	strTextW2 = pszText;		//代入
CStringAへの変換と内容は同じだが実装量が増える。この場合も変換できない可能性があるので注意が必要だ。
////////////////////////////////
//WCHAR*からchar*へ変換
//
WCHAR	pszWchar[] = {L"abcdあいう"};
char*	pszChar;
{
	int		nLen;

	//charに必要な文字数の取得
	nLen = ::WideCharToMultiByte(CP_THREAD_ACP,0,pszWchar,-1,NULL,0,NULL,NULL);
	pszChar = new char[nLen];
	if(pszChar)
	{
		//変換
		nLen = ::WideCharToMultiByte(CP_THREAD_ACP,0,pszWchar,(int)::wcslen(pszWchar)+1,pszChar,nLen,NULL,NULL);
		if(nLen == 0)
		{
			delete	pszChar;
			pszChar = NULL;
		}
	}
}
//
//ここでchar*型のpszCharを任意の処理で利用
//
delete	pszChar;		//処理が終わったらdelete

WindowsプログラミングでのTCHARの扱い方

TCHARでは代入などの際に " " ではなく、_T(" ") を利用していることに注意。

変数の宣言時に文字列を指定することもできる。
////////////////////////////////
//初期文字列代入
//
TCHAR	pszSrc[] = {_T("abcdあいう")};
文字数の取得にstrlenは利用できない。_tcslenを利用する。
////////////////////////////////
//文字数取得
//
size_t	nLen1 = ::_tcslen(pszSrc);			//変数内の文字列の文字数取得
size_t	nLen2 = ::_tcslen(_T("あいうえ"));	//指定した文字列の文字数取得
文字列のコピーにstrcpyは利用できない。_tcscpyをより安全にした_tcscpy_sを利用する。
////////////////////////////////
//文字列代入
//
TCHAR	pszText[256];
::_tcscpy_s(pszText,256,_T("あいうえABCD"));	//直接代入
::_tcscpy_s(pszText,256,pszSrc);				//変数から代入
CStringはTCHARを扱うための文字列操作クラスだ。
////////////////////////////////
//TCHAR*からCStringへの変換
//
CString	strText1(pszText);
CString	strText2 = pszText;
ユニコードビルド時はTCHAR=WCHARとなる。そのときは文字列の変換に失敗する可能性がある。
////////////////////////////////
//TCHAR*からCStringA(char型のCString)への変換
//
CStringA	strTextA(pszText);			//コンストラクタを利用
この場合は文字列の変換に失敗することはない。
////////////////////////////////
//TCHAR*からCStringW(WCHAR型のCString)への変換
//
CStringW	strTextW(pszText);			//コンストラクタを利用
この場合もWCHARからcharへの変換が起きる可能性があるので注意する。
////////////////////////////////
//TCHAR*からchar*へ変換
//
//TCHARはプロジェクト設定によってはWCHAR、charのどちらかになるので
//ifdefで分岐処理する
{
	TCHAR	pszTchar[] = {_T("abcdあいう")};
	char*	pszChar;
#ifdef UNICODE
	{
		int		nLen;

		//charに必要な文字数の取得
		nLen = ::WideCharToMultiByte(CP_THREAD_ACP,0,pszTchar,-1,NULL,0,NULL,NULL);
		pszChar = new char[nLen];
		if(pszChar)
		{
			//変換
			nLen = ::WideCharToMultiByte(CP_THREAD_ACP,0,pszTchar,(int)::wcslen(pszTchar)+1,pszChar,nLen,NULL,NULL);
			if(nLen == 0)
			{
				delete	pszChar;
				pszChar = NULL;
			}
		}
	}
#else
	{
		size_t	nLen;

		nLen = ::_tcslen(pszTchar) + 1;
		pszChar = new char[nLen];
		if(pszChar)
			::strcpy_s(pszChar,nLen,pszTchar);
	}
#endif
	//
	//ここでchar*型のpszCharを任意の処理で利用
	//
	delete	pszChar;		//処理が終わったらdelete
}
この場合は変換に失敗することはない。
////////////////////////////////
//TCHAR*からWCHAR*へ変換
//
//TCHARはプロジェクト設定によってはWCHAR、charのどちらかになるので
//ifdefで分岐処理する
{
	TCHAR	pszTchar[] = {_T("abcdあいう")};
	WCHAR*	pszWchar;
#ifdef UNICODE
	{
		size_t	nLen;

		nLen = ::_tcslen(pszTchar) + 1;
		pszWchar = new WCHAR[nLen];
		if(pszWchar)
			::wcscpy_s(pszWchar,nLen,pszTchar);
	}
#else
	{
		int		nLen;

		//Unicodeに必要な文字数の取得
		nLen = ::MultiByteToWideChar(CP_THREAD_ACP,0,pszTchar,-1,NULL,0);
		pszWchar = new WCHAR[nLen];
		if(pszWchar)
		{
			//変換
			nLen = ::MultiByteToWideChar(CP_THREAD_ACP,0,pszTchar,(int)::strlen(pszTchar)+1,pszWchar,nLen);
			if(nLen == 0)
			{
				delete	pszWchar;
				pszWchar = NULL;
			}
		}
	}
#endif
	//
	//ここでWCHAR*型のpszWcharを任意の処理で利用
	//
	delete	pszWchar;		//処理が終わったらdelete
}

2006年12月06日

IStreamを簡単に作る

APIのCreateStreamOnHGlobalを利用すると簡単にIStreamを取得できる。COMインターフェースの中にはIStreamでデータを受け取るものがあるのでそういう場合に重宝する。

#include "objidl.h"

//
//	グローバルメモリ上に新しくIStreamを作る
//
bool	CreateOnHGlobal(IStream** ppIStream)
{
	HRESULT	hr;

	if(ppIStream == NULL)
		return	false;
	hr = ::CreateStreamOnHGlobal(NULL,TRUE,ppIStream);
	if(SUCCEEDED(hr) && *ppIStream)
		return	true;

	*ppIStream = NULL;
	return	false;
}



//
//	テスト用関数
//
void	Test(void)
{
	::CoInitialize(NULL);


	bool		ret;
	HRESULT		hr;
	IStream*	pIStream;

	//メモリ上にIStreamを確保
	pIStream = NULL;
	ret = CreateOnHGlobal(&pIStream);

	//IStreamへ10byteデータ書き込み
	hr = E_FAIL;
	if(ret)
		hr = pIStream->Write("1234567890",10,NULL);
	if(FAILED(hr))
		ret = false;

	if(ret)
		::MessageBox(NULL,_T("成功しました"),_T(""),MB_OK);
	else
		::MessageBox(NULL,_T("失敗しました"),_T(""),MB_OK);

	if(pIStream)
		pIStream->Release();

	::CoUninitialize();
}

プロジェクトファイルをダウンロード

WTLをインストールする

MFCは多機能で使い勝手のいいフレームワークだ。しかしちょっと手の込んだことをしようと思ったり、ほかに配布するためのEXEを生成しようというときには途端に使い勝手が悪くなる。その反面、WTLは機能はシンプルだがMFCのようにDLLに依存することもなく、小回りのきくフレームワークになっている。

私は昔は「MFCがないとWindowsプログラミングはできない!」と思っていた。しかし最近ではMFCを使うことはまずない。ほぼ全てをWTLで作成している。使えば使うほどMFCにはない良さが分かる。

wtl01.gif
WTLはVisual Studio 2005に標準では備わっていないのでダウンロードして取得する。ちなみにここでは
日本語版WTL8.0を利用することにする。

WTLをダウンロードしたらVisual Studio 2005がインストールされているフォルダをエクスプローラで開く。


wtl02.gif
そして適当な名前でフォルダを作ってそこにWTLを解凍する。ここでは「WTL」というフォルダ名にした。

wtl03.gif
解凍したWTLのAppWizフォルダにある「setup80.js」というスクリプトを実行する。これは通常のVisual Studio 2005用のもので、
Visual Studio 2005 Express Editionの場合はsetup80x.js、
Visual Studio .NET 2003はsetup71.js、
Visual Studio .NET 2002はsetup70.jsを利用する。

wtl04.gif

wtl05.gif
この状態ではまだWTLのインストールは終わっていない。次にVisual Studio 2005を起動して、「ツール」メニューから「オプション」を開く。

wtl06.gif
そしてインクルードフォルダとしてWTLを解凍したフォルダの下にあるincludeフォルダを指定する。
以上の操作でVisual Studio 2005上からWTLが利用可能になる。

一連の作業から分かるようにWTLに含まれるsetup80.jsは必要なファイルをコピーしてインストールするわけではない。そのため解凍したファイル群は削除してはならない。


WTLで文字を出力するだけのアプリケーションを作る

wtl_a_01.gif
WTLアプリケーションウィザードを利用すると簡単にWindowsアプリケーションを作成できる。

Visual Studio 2005を開き、「ファイル」メニューの「新規作成」から「プロジェクト」を選択する。そして開いたウインドウの左側から「WTL」を選び、その中の「ATL/WTL アプリケーション ウィザード」を利用する。

wtl_a_02.gif
アプリケーションウイザードでは様々なオプションを設定できるが、ここではデフォルトの設定のまま「完了」ボタンを押す。これで基本的なソースコードが自動的に生成される。

wtl_a_03.gif
プロジェクトを作成したら「ビルド」メニューから「ソリューションのビルド」を選択して実行ファイルを作る。

wtl_a_04.gif
ビルドした実行ファイルを起動するために「デバッグ」メニューから「デバッグ開始」を選択する。

wtl_a_05.gif
これが実行画面。デフォルトの状態でメニューやツールバーを備えたウインドウとなっている。


wtl_a_06.gif
せっかくなので次に画面に文字を出力してみる。画面に文字や線などを描きたい場合は左側の「ソリューションエクスプローラー」にある「○○View.h」(○○はプロジェクト名)をダブルクリックしてビューに関するソースコードを開く。
ここで「//TODO: 描画コードの追加」という部分に文字の出力命令を追加すればいい。

wtl_a_07.gif
文字を表示する命令を追加する。ここではTextOut命令を利用した。

dc.TestOut(10,10,_T("文字を出力する"));


wtl_a_08.gif
ソースコードを変更したら「ビルド」メニューから「ソリューションのビルド」を選択して再びビルドする。

wtl_a_09.gif
そして「デバッグ」メニューの「デバッグ開始」を選択して実行ファイルを起動する。

wtl_a_10.gif
これで作成したアプリケーションのウインドウに文字が出力された。

プロジェクトファイルをダウンロード

バイト配列をひっくり返す(intel配列とモトローラ配列)

ごくたまにだが、Mac系とIntel系でのメモリ上の格納方式の違いを意識しなければいけないことがある。例えばデジカメ画像に含まれるExifデータやVirtual PCのVirtual HDDファイルを読むときなどが挙げられる。
そのようなときに使うと便利なのがこのようなマクロ。
0x12345678という配列であれば0x78563412のようにひっくり返してくれる。もちろんDWORDだけでなく、int、shortやLONGLONGなどほかの型でも使える。

依存環境:ATL
#include "atlstr.h"


#define	SWAP_BYTE_ALIGNMENT(data)	\
{									\
	int		i;						\
	int		nSize;					\
	int		nLoop;					\
	BYTE*	pData;					\
	BYTE	cbTmp;					\
									\
	pData = (BYTE*)&data;			\
	nSize = sizeof(data);			\
	nLoop = nSize / 2;				\
	for(i = 0; i < nLoop; i++)		\
	{								\
		cbTmp = *(pData + i);		\
		*(pData + i) = *(pData + nSize - i - 1);	\
		*(pData + nSize - i - 1) = cbTmp;			\
	}								\
}



void	Test(void)
{
	CAtlString	strMessage;
	DWORD		dwData;

	dwData = 0x12345678;
	SWAP_BYTE_ALIGNMENT(dwData)

	strMessage.Format(_T("%08x"),dwData);

	::MessageBox(NULL,strMessage,_T(""),MB_OK);
}

プロジェクトファイルをダウンロード