メイン | 2006年12月 »

前の10件 1  2  3  4

2006年11月 記事一覧

2つのパス間の相対パスを取得する(非API)

例えば画像ファイルを一覧できるHTMLファイルを書き出したい場合など、HTMLファイルの位置から見た画像ファイルへの相対パスを知りたいことがある。このようなときはPathRelativePathToを利用する。

...が、この関数は引数として相対パスを格納するバッファサイズが指定できないなど今後変更されそうな雰囲気を匂わせているAPIでもある。そのためここでは手作業で変換する関数を作成した。と、もっともらしく書きたいが実際はコードを書いたときにPathRelativePathToの存在を知らなかっただけだったりもする。開発で利用するならMicrosoftが用意しているので誤動作の心配がないPathRelativePathToを利用した方がいいだろう。

ちなみにこの関数もPathRelativePathToも、相対パスで表現できないパスが与えられた場合は処理に失敗する。

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


//
//	相対パスの取得
//
//Aから見たBへの相対パスを返す。
//ドライブが異なるなど相対パス化できないときは関数は失敗する。
//パスはフルパスで渡すこと!
//一般的にはAPIとして用意されているPathRelativePathToを利用する
//
bool	GetRelatedPath(CAtlString* pstrRelatedPath,LPCTSTR pszPathA,bool bPathAisFolder,LPCTSTR pszPathB,bool bPathBisFolder)
{
	int			nFind;
	CAtlString	strPathA;
	CAtlString	strPathB;

	if(pstrRelatedPath == NULL)
		return	false;
	*pstrRelatedPath = _T("");
	if(pszPathB == NULL || pszPathB == NULL)
		return	false;

	strPathA = pszPathA;
	strPathB = pszPathB;
	if(strPathA.GetLength() == 0 || strPathA.GetLength() == 0)
		return	false;

	if(bPathAisFolder == false)
	{
		//パスAからファイル名部分を取り除き、フォルダ名にする(末尾は「¥」ではない)
		nFind = strPathA.ReverseFind(_T('\\'));
		if(nFind < 0)
			return	false;			//どんなファイルパスでも最低1つは「¥」があるはず
		strPathA = strPathA.Left(nFind);
	}

	if(bPathBisFolder == false)
	{
		//パスBからファイル名部分を取り除き、フォルダ名にする(末尾は「¥」ではない)
		nFind = strPathB.ReverseFind(_T('\\'));
		if(nFind < 0)
			return	false;			//どんなファイルパスでも最低1つは「¥」があるはず
		strPathB = strPathB.Left(nFind);
	}

	if(strPathA.GetLength() == 0 || strPathA.GetLength() == 0)
		return	false;

	nFind = strPathB.Find(strPathA);
	if(nFind == 0)
	{
		//以下のような関係だった場合
		//PATHA=c:\\aaa\\bbb\\aa.txt
		//PATHB=c:\\aaa\\bbb\\ccc\\aa.txt
		//結果=ccc/aa.txt

		*pstrRelatedPath = pszPathB;
		*pstrRelatedPath = pstrRelatedPath->Right(pstrRelatedPath->GetLength() - strPathA.GetLength() - 1);
		return	true;
	}

	while(1)
	{
		//以下のような関係だった場合
		//PATHA=c:\\aaa\\bbb\\ccc\\aa.txt
		//PATHB=c:\\aaa\\bbb\\aa.txt
		//結果=../aa.txt
		// ↑この場合はwhileの1度目のループで結果が取得できる
		//
		//PATHA=c:\\aaa\\bbb\\ccc\\aa.txt
		//PATHB=c:\\aaa\\bbb\\ddd\\aa.txt
		//結果=../ddd/aa.txt
		// ↑この場合はwhileの2度目のループで結果が取得できる

		nFind = strPathA.Find(strPathB);
		if(nFind == 0)
		{
			*pstrRelatedPath = pszPathB;
			*pstrRelatedPath = pstrRelatedPath->Right(pstrRelatedPath->GetLength() - strPathB.GetLength() - 1);

			strPathA = pszPathA;
			strPathA = strPathA.Right(strPathA.GetLength() - strPathB.GetLength() - 1);
			nFind = strPathA.Replace(_T("\\"),_T("\\"));

			int		i;
			for(i = 0; i < nFind; i++)
				*pstrRelatedPath = _T("..\\") + *pstrRelatedPath;

			return	true;
		}

		nFind = strPathB.ReverseFind(_T('\\'));
		if(nFind < 0)
			break;
		strPathB = strPathB.Left(nFind);
	}

	return	false;
}





void	Test(void)
{
	bool		ret;
	CAtlString	strPath;
	ret = GetRelatedPath(&strPath,_T("c:\\aaa\\bbb\\ccc\\aa.txt"),false,_T("c:\\aaa\\bbb\\ddd\\aa.txt"),false);

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

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

2つのパス間の相対パスを取得する

例えば画像ファイルを一覧できるHTMLファイルを書き出したい場合など、HTMLファイルの位置から見た画像ファイルへの相対パスを知りたいことがある。このようなときはPathRelativePathToを利用する。

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


//
//	相対パスの取得
//
//Aから見たBへの相対パスを返す。
//ドライブが異なるなど相対パス化できないときは関数は失敗する。
//
bool	GetRelatedPath(CAtlString* pstrRelatedPath,LPCTSTR pszPathA,bool bPathAisFolder,LPCTSTR pszPathB,bool bPathBisFolder)
{
	size_t	nLen;
	BOOL	ret;
	TCHAR*	pszPath;

	if(pstrRelatedPath == NULL)
		return	false;
	*pstrRelatedPath = _T("");
	if(pszPathA == NULL || pszPathB == NULL)
		return	false;

	nLen = max(::_tcslen(pszPathA),::_tcslen(pszPathB));
	nLen *= 2;		//変換結果の方が長くなる可能性があるので×2している

	pszPath = new TCHAR[nLen];
	if(pszPath == NULL)
		return	false;

	//本当はFILE_ATTRIBUTE_DIRECTORYかそうでないかで引数を渡さなければいけない
	ret = ::PathRelativePathTo(pszPath,pszPathA,(bPathAisFolder ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL),pszPathB,(bPathBisFolder ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL));

	if(ret)
		*pstrRelatedPath = pszPath;

	delete	pszPath;

	return	ret ? true : false;
}





void	Test(void)
{
	bool		ret;
	CAtlString	strPath;
	ret = GetRelatedPath(&strPath,_T("c:\\aaa\\bbb\\ccc\\aa.txt"),false,_T("c:\\aaa\\bbb\\ddd\\aa.txt"),false);

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

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

2006年11月28日

開発対象OSはWindows Vista/XP/2000で十分

仕事でソフト開発をしていると必ず出てくるのが動作環境。特にWindowsのバージョンをどれくらい古いものまでサポートするかだ。結論から言うとWindows XP(SP2)とWindows Vistaで動けば問題ない。余裕があればWindows 2000も動作保証するかな?という程度で十分なはずだ。

企業向けソフトの開発の現場では「企業、特に市場規模が大きい中小企業じゃWindows 98を使っているところもまだまだ多いからWindows 9x系はサポートしないとダメだよ。」と耳にすることがある。確かに中小企業ではWindows 98は多く使われている。また潜在的な市場も大きい。

しかしどうだろう?いまだにWindows 98を使っているような企業が新しく開発したソフトに興味を持つだろうか?ソフトに投資するだろうか?
よっぽどそのソフトに魅力があるか、開発後の売込みがうまくない限り無理だろう。開発したソフトに興味を持つのは新しい技術などの"変化"を求める企業であり、そういう企業では今の時代最低でもWindows XPは導入していると考えられるからだ。
またWindows 9x系を動作保証に入れる場合は、該当するOSで動作をテストする必要がある。今はVirtual PCも無料化されWindows XP上でWindows 95や98を走らせることも簡単にでき、テストもはかどる環境は整っている。しかしサービスパックの有無、OSR1、2、各種あるInternet Explorerのバージョン、アクティブデスクトップ使用の有無...確認しなければいけないケースが非常に多く、「Windows 9x系対応」という一言をパッケージに印刷したいがためにかかるコストはかなりなものになる。


ちなみに私のホームページへのアクセス履歴(2006年10月)を見てみると...

■WIndows系プラットフォーム
os_access01.gif

■OS全体
os_access02.gif

※履歴はGoogle Analyticsで取得したものです。この分析はかなり解析もれが生じていることがあります

となっていた。

Windowsの中で見るとWindows 98を含めたWindows 9x系は1300アクセスにも届かない程度。パーセンテージにして3%しかない。この数は全体のアクセスでみたときのMacintoshとほぼ同じ割合になっている。やはりダントツに多いのは80%を占めるWindows XP、そして13%のWindows 2000となっている。

このようなOSの割合から見て「Windows XPが多いからWindows 98の使用率はもうすでに非常に低い!」とは言えない。このデータはInternet上で集計したものだからであり、Windows 98はどちらかというとInternetやLANなどのネットワークに接続せず、企業内部で利用されていることが多いからだ。また、「私のホームページへのアクセス」という偏った母体集団に基づいていることもある。
とは言うもののWindows XP系(NT系)への移行は確実に起きている。今後、新たにソフトを開発する場合は、特別な理由がない限りWindows 9x系は切り捨て、Windows 2000やXP、そして新しいVistaへの動作保証をするだけでいいだろう。

Windows 9x系を利用しない場合は比較的容易にソフトを多言語化できる。Windows 98系サポートへ開発時間を注ぐよりも、むしろ多言語化に力を注ぎ「世界中で使えるソフト」をめざした方が潜在的な市場規模も大きいのではないだろうか?

TCHARとかLPCTSTR、LPTSTRって何???

Windowsプログラミングからは切っても切り離せないのが、TCHARと、LPCTSTR、LPTSTRなどのTCHAR系列の型。結論から書くとこれはソースコードをユニコードと非ユニコード両対応にするための型だ(何のことだかサッパリ分からないって?こういうプログラミングの授業じゃ習わない意味不明な拡張が多いからWindowsプログラミングはややこしいんですよね)。

何も考えずに

・TCHAR = char
・LPCTSTR = const char*
・LPTSTR = char*

と頭の中で置き換えて使っている人も多いのではないだろうか?実際、この置き換え方はそう大きく間違えたものではない。

CHARは文字型を意味しているし、STRは文字列型を、LPは*(ポインタ)、LPCのCはconstを示している。問題は残った「T」という文字だ。これは...私にも分かりません(かなり無責任ですが本当に分かりません)。

分かっていることは、例えばTCHARは以下のように定義されているということです。

#ifdef UNICODE
	typedef WCHAR    TCHAR;
#else
	typedef char     TCHAR;
#endif

このことから「UNICODE」が定義されているとTCHARはWCHAR、定義されていないと「char」と同一になります。つまり、前に挙げた

・TCHAR = char
・LPCTSTR = const char*
・LPTSTR = char*

というのは「UNICODE」が定義されていないときの置き換えということになり、「UNICODE」が定義されているときには

・TCHAR = WCHAR
・LPCTSTR = const WCHAR*
・LPTSTR = WCHAR*

という関係になります。

くどいようですが「UNICODE」を定義しなければ、TCHARはcharと同義なわけです。よく分からないTCHARやWCHARという型の意味は放っておいて、charであれば馴染みやすい型であり、扱いやすいと思う人も多いでしょう。

では、「UNICODE」を定義しないようにするにはどうすればいいかと言うと...

例えば「MFCアプリケーションウィザード」では、「アプリケーションの種類」の設定項目の中に
「ユニコードライブラリを使用する」というチェックボックスがあります。このチェックを外せばOKです。
tchar01.gif


既にプロジェクトができている場合は、「プロジェクト」メニューの「プロパティ」からプロジェクトの設定画面を開き、左側のツリーで「構成プロパティ」の「全般」を選択します。そして「文字セット」を「マルチバイト文字セットを使用する」に変更すればOKです。
tchar02.gif

これらの設定を行えば、

・TCHAR = char
・LPCTSTR = const char*
・LPTSTR = char*

というように置き換えて利用して問題ありません。

本当はTCHARとchar、WCHARの違いをきちんと把握してプログラミングするべきです。しかし上のように置き換えてTCHAR=char!と考えても上記のプロジェクトの設定さえきちんとしていれば困ることはまずありません。「多言語プログラミング」や「ユニコード」などに興味を覚えたら「TCHARは「UNICODE」を定義するとWCHARになる」ということを思い出して再びTCHARについて勉強してみてください(今後きちんとTCHARの使い方などについての説明をしたいと思います)。
TCHARなどのことをきちんと知っている方がこの文章を読んでいたら...ごめんなさい。あまりお怒りにならず「変なことを書いてるなぁ」と笑い飛ばしていただければと思います。

2006年11月29日

TCHARについて

「TCHAR」型とのお付き合いはWindowsプログラミングにはかかせない。TCHAR型は前に説明したように「UNICODE」定義がない場合はchar型と同一だ。

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


void	Test(void)
{
	CAtlString	strMessage;

	strMessage.Format(_T("TCHARのサイズは %d Bytes\n"),sizeof(TCHAR));

	::MessageBox(NULL,strMessage,_T(""),MB_OK);
}
プロジェクトファイルをダウンロード

このプログラムはsizeof(TCHAR)の結果をメッセージボックスに表示するだけの簡単なものだ。これの実行結果を見てみると...

■「UNICODE」が定義されていないとき
test22_01.gif


■「UNICODE」が定義されているとき
test22_02.gif

となる。つまり「UNICODE」定義の有無でTCHAR型はサイズが変わる。TCHAR型は文字を代入するための型なので、次は文字がメモリ上でどのような値として格納されているかを見てみよう。

#include "atlstr.h"


void	Test(void)
{
	CAtlString	strMessage;

	TCHAR	ch;

	ch = _T('A');
	strMessage.Format(_T("「%c」はメモリ上で 0x%X と格納されています"),ch,ch);

	::MessageBox(NULL,strMessage,_T(""),MB_OK);
}
プロジェクトファイルをダウンロード

■「UNICODE」が定義されていないとき
test22_03.gif


■「UNICODE」が定義されているとき
test22_03.gif


...この例ではまったく同じだ。実はこのように英数字の1文字を見た場合、「UNICODE」が定義されていても、そうでなくても文字コードに違いはない。異なるのは型の大きさsizeof(TCHAR)だけだ。ではどのようなときに違いが出るかというと、全角文字や文字列を利用する場合だ。

次はTCHAR型に文字列を代入して、その文字列のメモリ上での格納状態を見てみよう。

#include "atlstr.h"


void	Test(void)
{
	CAtlString	strTmp;
	CAtlString	strMessage;
	BYTE*	pcbTmp;

	//文字列用にTCHARを確保
	TCHAR	pch[256];

	//文字列を代入
	_tcscpy(pch,_T("ABCあいう"));


	//文字列をBYTE配列として処理する
	pcbTmp = (BYTE*)pch;

	for(int i = 0; i < _tcslen(pch)*sizeof(TCHAR); i++)
	{
		//1BYTEずつ16進数表示
		strTmp.Format(_T("%02X "),pcbTmp[i]);
		strMessage += strTmp;
	}

	strTmp.Format(_T("「%s」はメモリ上で %s と格納されています"),pch,strMessage);

	::MessageBox(NULL,strTmp,_T(""),MB_OK);
}
プロジェクトファイルをダウンロード

■「UNICODE」が定義されていないとき
test22_04.gif


■「UNICODE」が定義されているとき
test22_05.gif

先に試した「A」という1文字での結果では「UNICODE」定義有無での違いは現れなかった。しかしどうだろう。今回の文字列での結果は「UNICODE」定義の有無で結果が大きくことなっている。

まず従来の「UNICODE」定義がない場合は、「ABC」という文字列の文字コード0x41、0x42、0x43が連続して並んでいる。しかし「UNICODE」定義がある場合では各文字コードの間に0x00が入っている。これはsizeof(TCHAR)の違い(UNICODE時は2BYTE、それ以外は1BYTEということ)からも容易に想像できるだろう。
その次の「あいう」という文字列では「UNICODE」定義がない場合には従来どおり0xA0 0x82...とSHIFT-JISによる文字コードが並んでいる。しかし「UNICODE」定義がある場合には0x42 0x30 0x44 0x30...とまったく異なった配列になっている。
このような「UNICODE」定義があるときの文字コードのことを「ユニコード」という。つまりTCHARという文字型は「UNICODE」定義がないときはSHIFT-JISなどの従来の文字コード、「UNICODE」が定義されているときはユニコードの文字コードを扱う型になる。

紛らわしいぞ!LPCTSTR、LPTSTR、LPSTR、LPCSTRは全部意味が違う!

TCHAR系の型というのは初めての人にとっては非常に理解しにくい。理解できれば使えるようになるのだが、そうなっても実装に間違えることが多々ある。その理由がこれ。LPCTSTR、LPTSTR、LPSTR、LPCSTR、LPWSTR、LPCWSTRの6つが全て意味が違うということ。ぱっと見ではほとんど同じだがよく見ると「C」が付いていたり、「T」が1つ多かったりと微妙にスペルが異なっている。

これがどう違うのかと言うと...

LPSTR	= char*
LPCSTR	= const char*
LPTSTR	= TCHAR*
LPCTSTR	= const TCHAR*
LPWSTR	= WCHAR*
LPCWSTR	= const WCHAR*
となっている。つまり
LP	= *(ポインタ)
C	= const
TSTR	= TCHAR
STR	= char
WSTR	= WCHAR
というような規則で型の名前が作られている。

非ユニコードビルド環境でプログラミングをしているときは、WSTR系は使うことがほとんどないので、何も考えずにchar*でプログラミングして、エラーがでたらconstを付加するという方法でとりあえず動く。

しかしユニコードビルド環境の場合はTSTRとSTRの間の区別をきちんとしておかないと大変なことになるし、1つのソースコードで非ユニコードとユニコードビルドの両方に対応させようと思ったらいつも違いを意識してプログラミングをしなければならない。
インターネット上にたくさんあるサンプルコードのほとんどはこの区別がなされていない。たいていはTCHAR=charという前提になっているので注意しよう。

2006年11月30日

文字列をユニコードに変換する

SHIFT-JISの文章などいわゆる普通の文字列をユニコード文字列に変換するにはMultiByteToWideCharを利用する。気をつけなければいけないことは、SHIFT-JISとユニコードとでは文字列の格納に必要なバイト数が異なることだ。そのため、MultiByteToWideCharの第五引数にNULLを渡して、ユニコード文字列にしたときに必要なバイト数を取得し、それを元に動的にメモリを確保する。

例えばSHIFT-JISの文字列をユニコードに変換してメッセージボックスに表示するソースコードは以下のようになる。

void	Test(void)
{
	int		nLen;
	char	pszChar[50];
	WCHAR*	pszWchar;

	//変換したい非ユニコード文字列
	strncpy(pszChar,"ABCあいう",50);

	//Unicodeに必要な文字数の取得
	nLen = ::MultiByteToWideChar(CP_THREAD_ACP,0,pszChar,-1,NULL,0);
	pszWchar = new WCHAR[nLen];

	//変換
	::MultiByteToWideChar(CP_THREAD_ACP,0,pszChar,strlen(pszChar)+1,pszWchar,nLen);


	//MessageBoxの第二引数としてWCHARを与えているのでユニコードビルド時以外はエラーになるのでifdefで処理
	#ifdef UNICODE
		//変換したユニコード文字列を表示
		::MessageBox(NULL,pszWchar,_T(""),MB_OK);
	#endif

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


同じ処理を関数的に書くとこんな感じだろうか?

//
//	SHIFT-JISなどのANSIやMBCS文字列をユニコード文字列に変換
//
//取得した結果は必ずdeleteしてメモリを開放すること!
//
WCHAR*	ConvertToUnicode(const char* pszChar)
{
	int		nLen;
	WCHAR*	pszWchar;

	//Unicodeに必要な文字数の取得
	nLen = ::MultiByteToWideChar(CP_THREAD_ACP,0,pszChar,-1,NULL,0);
	pszWchar = new WCHAR[nLen];
	if(pszWchar == NULL)
		return	NULL;

	//変換
	nLen = ::MultiByteToWideChar(CP_THREAD_ACP,0,pszChar,strlen(pszChar)+1,pszWchar,nLen);
	if(nLen)
		return	pszWchar;

	delete	pszWchar;

	return	NULL;
}




void	Test(void)
{
	int		nLen;
	char	pszChar[50];
	WCHAR*	pszWchar;

	//変換したい非ユニコード文字列
	strncpy(pszChar,"ABCあいう",50);

	pszWchar = ConvertToUnicode(pszChar);


	//MessageBoxの第二引数としてWCHARを与えているのでユニコードビルド時以外はエラーになるのでifdefで処理
	#ifdef UNICODE
		//変換したユニコード文字列を表示
		::MessageBox(NULL,pszWchar,_T(""),MB_OK);
	#endif

	//必ずdeleteすること!
	delete	pszWchar;
}
プロジェクトファイルをダウンロード

VC++プロジェクトでユニコードビルドと非ユニコードビルドを切り替える

Visual Studio 2005(Visual C++ 2005)ではプロジェクト作成時に自動的にユニコードビルドプロジェクトとして作成される(ものが多い)。

プロジェクトがユニコード設定になっていると、ほとんどのAPI関数でchar型の引数が受け付けてもらえい。そのためエラーがたくさん出てビルドできない。「インターネット上のサンプルコードをそのままコピー&ペーストしているのにビルドできない!」と泣きたくなった人もいるのではないだろうか?

プロジェクトをユニコードビルドするか、しないかはプロジェクトのプロパティから設定できる。

プロジェクトを開いた状態で「プロジェクト」メニューの「プロパティ」を選択する。
prop01.gif

そして現れたプロパティページの画面左側で「構成プロパティ」の「全般」を選択し、「文字セット」のところで「マルチバイト文字セットを使用する」を選択すると非ユニコードビルドになる。
prop03.gif

当然のことながら「Unicode文字セットを使用する」を選択するとユニコードビルドになる。
prop02.gif


ここで注意すべきは今行った設定はたいてい「Debug」か「Release」のどちらかになっているという点だ。ここでは詳しくは説明しないがVisual C++では開発中に利用するデバッグビルドと配布する製品バイナリ(EXEファイルなど)を出力するためのリリースビルドという2つのモードを備えている。
単に自分のパソコンの中だけでプログラムをして遊んでいるだけであれば意識する必要はない。しかしフリーソフトや市販ソフトなど公に配布するソフトを開発しようという人はこの2つをきちんと区別した方がよい。
prop04.gif

ユニコード文字列から非ユニコード(SHIFT-JIS)文字列に変換する

ユニコード文字列をSHIFT-JISなどの非ユニコード文字列に変換したいときはWideCharToMultiByteをりようする。
この関数を使うときに気をつけなければならないのは、変換先のchar型の文字列に必要なバイト数だ。これはユニコード文字列中の全角文字の数によって可変する。固定長で指定してもいいのだが安全のためにかならず一度WideCharToMultiByteを使って必要なバイト数を取得し、その値を使って動的にメモリを確保してから変換すること。

//
//	ユニコード文字列をSHIFT-JISなどのANSIやMBCS文字列をに変換
//
//取得した結果は必ずdeleteしてメモリを開放すること!
//
char*	ConvertFromUnicode(const WCHAR* pszWchar)
{
	int		nLen;
	char*	pszChar;

	//charに必要な文字数の取得
	nLen = ::WideCharToMultiByte(CP_THREAD_ACP,0,pszWchar,-1,NULL,0,NULL,NULL);
	pszChar = new char[nLen];
	if(pszChar == NULL)
		return	NULL;

	//変換
	nLen = ::WideCharToMultiByte(CP_THREAD_ACP,0,pszWchar,wcslen(pszWchar)+1,pszChar,nLen,NULL,NULL);
	if(nLen)
		return	pszChar;

	delete	pszChar;

	return	NULL;
}




void	Test(void)
{
	int		nLen;
	char*	pszChar;
	WCHAR	pszWchar[50];

	//変換したいユニコード文字列
	wcsncpy(pszWchar,L"ABCあいう",50);

	pszChar = ConvertFromUnicode(pszWchar);


	//MessageBoxの第二引数としてcharを与えているのでユニコードビルド時はエラーになるのでifndefで処理
	#ifndef UNICODE
		//変換した非ユニコード文字列を表示
		::MessageBox(NULL,pszChar,_T(""),MB_OK);
	#endif

	//必ずdeleteすること!
	delete	pszChar;
}
プロジェクトファイルをダウンロード

文字コード変換にWideCharToMultiByteやMultiByteToWideCharなんて使わない!

Windowsには文字コード変換用のAPI関数としてWideCharToMultiByteやMultiByteToWideCharがある。それぞれユニコード文字列をSHIFT-JISにしたり、SHIFT-JIS文字列をユニコードにしたりするときに用いるためのAPIだ。

前回までにこれらの関数を用いたユニコード<->SHIFT-JISの相互変換関数のソースコードを紹介した。しかし私自身、今までソフト開発をするときにWideCharToMultiByteやMultiByteToWideCharを呼び出して利用した経験はほとんどない。文字列の文字コード変換を要するソフト開発をしたことがないからというわけではない。利用する必要性を感じなかったからだ。

大量の文字列データを扱うソフトの場合や処理スピードを速くしなければならないソフトは別として、ほとんどのソフト開発ではそれほど頻繁かつ大量に文字コードを変換することはない。仮に変換をしても数バイトから数キロバイト程度の小さい文字列をたまに変換する程度のことが多い。

そのようなソフトの場合はchar*やWCHAR*やstrcpy、wcscpyなどを使ってゴリゴリ文字列操作をするよりも、むしろMFCやATL、WTLに備わる文字列操作用クラスCStringを利用している。CStringは非常に文字列の扱いが楽に作られている。例えば...

	//変数定義
	CStringA	strText;

	//文字列代入
	strText = "あいうえお";

	//文字列追加
	strText += "かきくけこ";

	//文字列長取得
	int nLen = strText.GetLength();

というように簡単に利用できる。
同じことをchar*を使って表現すると...

	//変数定義
	char	pszText[25];

	//文字列代入
	strcpy(pszText,"あいうえお");

	//文字列追加
	strcat(pszText,"かきくけこ");

	//文字列長取得
	int nLen = strlen(pszText);

というようになる。

「あれ?CStringを使ってもchar*でゴリゴリ書いても差はないじゃん!」と思うかもしれない。そこに重大な落とし穴がある。例えば...

	//変数定義
	CStringA	strText;

	//文字列代入
	strText = "あいうえおああああああああああああああああ";

	//文字列追加
	strText += "かきくけここここここここここここここここ";

	//文字列長取得
	int nLen = strText.GetLength();

上のように代入や追加する文字列の長さを大きくとってもCStringではそのまま動く。しかし

	//変数定義
	char	pszText[25];

	//文字列代入
	strcpy(pszText,"あいうえおああああああああああああああああ");

	//文字列追加
	strcat(pszText,"かきくけここここここここここここここここ");

	//文字列長取得
	int nLen = strlen(pszText);

上の場合はpszTextに割り当てられているメモリサイズを超えて代入してしまうので、実行時にエラーになる。CStringは内部で代入や追加される文字列の長さに応じて動的にメモリを確保する仕組みがあるが、ここで書いたchar*を使ったソースコードはメモリを固定長で宣言しているため、それを超える文字列を扱えないためだ。もちろんchar*の場合でも動的に確保するようにプログラミングすればエラーを起こさないようにできる。しかしかなり面倒になる。実装量が増えるのでバグをはらむ可能性も高くなるだろう。そんなわけで文字列操作にはCString系を使うと安全かつ便利だ。

少し話しがずれたが、文字コードの変換にWideCharToMultiByteやMultiByteToWideCharを使わないという理由は、CStringがこれらの変換処理も自動で行うように設計されているからだ。

CStringという型はTCHARと同じように振舞う。「UNICODE」が宣言されているとユニコード文字列を扱うクラスになり、宣言されていないと(日本語の場合は)SHIFT-JIS文字列を扱うクラスになる。
TCHARに対してWCHARやcharがあるように、CStringに対してはCStringWとCStringAが用意されている。

CStringAは非ユニコード文字列を扱うクラス
CStringWはユニコード文字列を扱うクラス
CStringはユニコードビルド時はユニコード文字列、そうでないときは非ユニコード文字列を扱うクラス

というようになる。そして...

	//charと同一の型(非ユニコード型)で宣言
	CStringA	strChar;

	//SHIFT-JIS文字列を代入
	strChar = "あいうえお";

	//ユニコード文字列を代入
	strChar = L"あいうえお";

上のように、非ユニコード型であるCStringAに対してユニコード文字列をそのまま代入することもできる。このときに自動的にCStringAの内部でWideCharToMultiByteが呼び出されてユニコード文字列から非ユニコード文字列に変換されている。

ATL用のCStringの場合はクラス定義の中(cstringt.hとatlstr.hの中)で以下のように実装されている。

	CStringT& operator=( __in_z_opt PCYSTR pszSrc )
	{
		// nDestLength is in XCHARs
		int nDestLength = (pszSrc != NULL) ? StringTraits::GetBaseTypeLength( pszSrc ) : 0;
		if( nDestLength > 0 )
		{
			PXSTR pszBuffer = GetBuffer( nDestLength );
			StringTraits::ConvertToBaseType( pszBuffer, nDestLength, pszSrc);
			ReleaseBufferSetLength( nDestLength );
		}
		else
		{
			Empty();
		}

		return( *this );
	}

	static int GetBaseTypeLength(__in_z const wchar_t* pszSrc) throw()
	{
		// Returns required buffer length in XCHARs
		return ::WideCharToMultiByte(_AtlGetConversionACP(), 0, pszSrc, -1, NULL, 0, NULL, NULL)-1;
	}

	static void ConvertToBaseType(__out_ecount(nDestLength) _CharType* pszDest, __in int nDestLength,
		__in_ecount(nSrcLength) const wchar_t* pszSrc, __in int nSrcLength = -1) throw()
	{		
		// nLen is in XCHARs
		::WideCharToMultiByte(_AtlGetConversionACP(), 0, pszSrc, nSrcLength, pszDest, nDestLength, NULL, NULL);
	}
確かにWideCharToMultiByteが呼ばれている。


どうだろう?これでWideCharToMultiByteやMultiByteToWideCharを使わない理由が分かっただろうか?

MFCやATL、WTLといったMicrosoftが用意するライブラリは必ずしも完璧ではない。少なからずバグがあり思いもよらぬ誤動作が起きて原因追求に手間取ることもある。しかしながら、自分で書いたソースコードよりも品質が高く信頼性が高いことだろう。そのためこれらのライブラリに使いたい処理が実装されているのであれば使った方が実装やテストの開発コストを抑えることができてお得だ。あるものはどんどん利用しよう。

前の10件 1  2  3  4





usefullcode@gmail.com

About 2006年11月

2006年11月にブログ「UsefullCode.net」に投稿されたすべてのエントリーです。

次の記事は2006年12月です。

他にも多くのエントリーがあります。メインページ記事一覧も見てください。