ページ 11

MessageBoxとMessageBoxAを合わせた自作関数

Posted: 2015年5月04日(月) 07:49
by table_in_trace
Win32で空のプロジェクトから少しずつ勉強して書けるプログラムを増やしています。
今は、MessageBoxの柔軟性を高くするために、引数に合わせて正しくメッセージボックスを生成させようと思っています。

① MessageBox(NULL, TEXT("text1"), TEXT("title1"), MB_OK);
② MessageBox(NULL, &text[0], TEXT("title2"), MB_OK);
③ MessageBox(NULL, TEXT("text3"), &title[0], MB_OK);
④ MessageBox(NULL, &text[0], &title[0], MB_OK);

上記で正しく動作するのは①だけで、④はMessageBoxAを使えば動作させられるのですが
②、③は既存の関数では対応できず、引数によって使う関数を変えるのも面倒なので、
オーバーロードにてこれらを包括した関数を作成しようとしましたが、引数に全角文字を使うと文字化けしてしまいました。
(下記プログラムのように半角英数では期待通りの動作になります)
全角文字を使用しても文字化けしないようにしたいのですが、どのように変えれば良いでしょうか?
正直、TEXT("~~")の中身が良くわかっていないので、理解が追いつくか不安ですがよろしくお願いします。
環境は、「Microsoft Visual C++ 2010 Express」です。

コード:

int MessageBoxX(HWND hwnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType){
	return MessageBox(hwnd, lpText, lpCaption, uType);
}
int MessageBoxX(HWND hwnd, char *lpText, LPCTSTR lpCaption, UINT uType){
	int i1, length, Message;
	char *title;

	for(i1=0; i1<2560; i1++){
		if(lpCaption[i1] == '\0')	break;
	}
	if(i1 == 2560)	i1--;
	length = i1 + 1;
	title = (char *)malloc(sizeof(char) * length);

	for(i1=0; i1<length; i1++){
		title[i1] = lpCaption[i1];
	}
	title[2560] = '\0';
	Message = MessageBoxA(hwnd, &lpText[0], &title[0], uType);
	free(title);

	return Message;
}
int MessageBoxX(HWND hwnd, LPCTSTR lpText, char *lpCaption, UINT uType){
	int i1, length, Message;
	char *text;

	for(i1=0; i1<2560; i1++){
		if(lpText[i1] == '\0')	break;
	}
	if(i1 == 2560)	i1--;
	length = i1 + 1;
	text = (char *)malloc(sizeof(char) * length);

	for(i1=0; i1<length; i1++){
		text[i1] = lpText[i1];
	}
	text[2560] = '\0';
	Message = MessageBoxA(hwnd, &text[0], &lpCaption[0], uType);
	free(text);

	return Message;
}
int MessageBoxX(HWND hwnd, char *lpText, char *lpCaption, UINT uType){
	int i1, length, Message;
	char *title, *text;

	for(i1=0; i1<2560; i1++){
		if(lpCaption[i1] == '\0')	break;
	}
	if(i1 == 2560)	i1--;
	length = i1 + 1;
	title = (char *)malloc(sizeof(char) * length);

	for(i1=0; i1<length; i1++){
		title[i1] = lpCaption[i1];
	}
	title[2560] = '\0';


	for(i1=0; i1<2560; i1++){
		if(lpText[i1] == '\0')	break;
	}
	if(i1 == 2560)	i1--;
	length = i1 + 1;
	text = (char *)malloc(sizeof(char) * length);

	for(i1=0; i1<length; i1++){
		text[i1] = lpText[i1];
	}
	text[2560] = '\0';
	Message = MessageBoxA(hwnd, &text[0], &title[0], uType);
	free(title);
	free(text);

	return Message;
}

Re: MessageBoxとMessageBoxAを合わせた自作関数

Posted: 2015年5月04日(月) 08:02
by table_in_trace
> (下記プログラムのように半角英数では期待通りの動作になります)
すいません、呼出し部分のプログラムを記載し忘れていました。

コード:

	MessageBoxX(NULL, TEXT("てきすと1"), TEXT("たいとる1"), MB_OK);
	MessageBoxX(NULL, &text[0], TEXT("title2"), MB_OK);
	MessageBoxX(NULL, TEXT("text3"), &title[0], MB_OK);
	MessageBoxX(NULL, &text[0], &title[0], MB_OK);

Re: MessageBoxとMessageBoxAを合わせた自作関数

Posted: 2015年5月04日(月) 08:08
by へにっくす
型のLPCTSTR/LPTSTRは、プロジェクトの設定により変わります。
全般-文字セットが
UnicodeになっていればLPCWSTR/LPWSTR、
マルチバイトになっていればLPCSTR/LPSTRに切り替わります。
TEXTマクロも同様で、たとえばTEXT("AAA")とした場合、
Unicodeの場合は L"AAA" となり
マルチバイトの場合はそのまま "AAA" となります。
何も設定した覚えがない場合はUnicodeになっていますので、①しか正常に動かないのは当たり前です。
Win32API関数も同じですよ。
MessageBox関数は、
Unicodeの場合MessageBoxW関数、
マルチバイトの場合MessageBoxA関数を使用するように切り替わります。
なので④がMessageBoxAにすると動くのはそういった理由です(引数がconst char*/char*=LPCSTR/LPSTRなので問題ない)。

また、Unicodeとマルチバイトの扱う文字コードはもちろんまったく違いますので、
単純に文字列コピーしても動きません。
MultiByteToWideChar関数
WideCharToMultiByte関数
などの関数を使う必要があります。

参考)
http://yonex1.cis.ibaraki.ac.jp/~yoneku ... -Text.html
http://moeprog.web.fc2.com/moe/Win32API ... nicode.htm

Re: MessageBoxとMessageBoxAを合わせた自作関数

Posted: 2015年5月04日(月) 08:27
by みけCAT
オフトピック
個人的には、関数内で中身を書き換えない引数lpTextやlpCaptionをconst char*ではなくchar*型にしているのは気に入らないなあ。

あと、わざわざ&text[0]って書かずに、textって書いても(textがchar*型なので)全く同じ値になると思うのだが、これは好みかな?

さらに、なんで2560バイト/文字で切る仕様になっているのでしょうか?
どうせ動的確保なので、int型の許す範囲(少なくとも32767まで)いっぱいまで使えるように書いちゃダメだったのだろうか…?
コピーする所は仕方ないとしても、長さを測る部分はlstrlenA / lstrlenWじゃダメだったのだろうか?

さらにまずいことには、lengthの計算(11行目など)においてi1は高々2559の値しかとらないので、lengthは高々2560の値しか取りません。
sizeof(char)は1だったはずなので、title[2560]やtext[2560]へのアクセスは確実に確保された領域の範囲外へのアクセスになります。

もっとひどいことには、mallocは確保した領域を0で埋めないため、textやtitleに格納した文字列の後ろにゴミが入る可能性が高いです。
【訂正】文字列の長さ+1、すなわち終端文字までコピーしているようでした。

mallocの戻り値をチェックしていないことも気になりますが…範囲外アクセスや文字列の終端を格納していない問題に比べたら些細なことでしょう。

Re: MessageBoxとMessageBoxAを合わせた自作関数

Posted: 2015年5月04日(月) 08:30
by みけCAT
僕の玩具はC言語 さんが書きました:① MessageBox(NULL, TEXT("text1"), TEXT("title1"), MB_OK);
② MessageBox(NULL, &text[0], TEXT("title2"), MB_OK);
③ MessageBox(NULL, TEXT("text3"), &title[0], MB_OK);
④ MessageBox(NULL, &text[0], &title[0], MB_OK);

上記で正しく動作するのは①だけで、④はMessageBoxAを使えば動作させられるのですが
②、③は既存の関数では対応できず、引数によって使う関数を変えるのも面倒なので、
オーバーロードにてこれらを包括した関数を作成しようとしましたが、引数に全角文字を使うと文字化けしてしまいました。
(下記プログラムのように半角英数では期待通りの動作になります)
全角文字を使用しても文字化けしないようにしたいのですが、どのように変えれば良いでしょうか?
僕の玩具はC言語 さんが書きました:> (下記プログラムのように半角英数では期待通りの動作になります)
すいません、呼出し部分のプログラムを記載し忘れていました。

コード:

	MessageBoxX(NULL, TEXT("てきすと1"), TEXT("たいとる1"), MB_OK);
	MessageBoxX(NULL, &text[0], TEXT("title2"), MB_OK);
	MessageBoxX(NULL, TEXT("text3"), &title[0], MB_OK);
	MessageBoxX(NULL, &text[0], &title[0], MB_OK);
まず、textやtitleの型を指定してください。

Re: MessageBoxとMessageBoxAを合わせた自作関数

Posted: 2015年5月04日(月) 18:11
by ISLe()
この件に関しては、charじゃなくてTCHARを使えば解決するのではないでしょうかね。

TCHAR は TEXTマクロと同様にプロジェクトの設定で切り替わる文字型です。

Re: MessageBoxとMessageBoxAを合わせた自作関数

Posted: 2015年5月06日(水) 09:12
by table_in_trace
> へにっくす様

MultiByteToWideChar関数を使用してUnicodeとマルチバイトの文字コード変換を試みましたが、
理解不足のようでコンパイルが通らなかったので、今回は諦めました。

そこで、汎用性を放棄して、強制的にマルチバイトで扱うためにTEXT("AAAあああ")をすべて"AAAあああ"に変えたところ
MessageBoxではNGでしたが①~④すべてMessageBoxAで期待通りの動作になりました。
(MessageBoxがうまくMessageBoxAとMessageBoxWに切り替わっていない?)

ともかく、汎用性を放棄すれば、char型に格納した文字列も、任意の""に括られた文字列も
MessageBoxAで呼び出すことには成功したので、本件はこれで解決という事にさせてもらいます。

へにっくす様が冒頭に記載して下さっている内容が理解できていない事が根本的な問題かと思いますので
これについては今後参考書などで勉強していこうと思います。
(ネットでいくつかのサイトを読んでみましたが、いまいち理解できずでした)
ありがとうございました。


> みけCAT様

> 個人的には、関数内で中身を書き換えない引数lpTextやlpCaptionをconst char*ではなくchar*型にしているのは気に入らないなあ。
本件の内容とはずれている気がします。

> あと、わざわざ&text[0]って書かずに、textって書いても(textがchar*型なので)全く同じ値になると思うのだが、これは好みかな?
好みです。

> さらに、なんで2560バイト/文字で切る仕様になっているのでしょうか?
> どうせ動的確保なので、int型の許す範囲(少なくとも32767まで)いっぱいまで使えるように書いちゃダメだったのだろうか…?
> コピーする所は仕方ないとしても、長さを測る部分はlstrlenA / lstrlenWじゃダメだったのだろうか?
メモリ確保について勉強不足でして、どの程度で区切るのが効率的なのかがわからなかったため
1つの基準である256をベースとして、これでは必要な文字数を確保できない可能性が高かったためその10倍に設定しました。
この場合32767で区切った方がメリットが大きい(もしくは2560で区切る意味が無い)のでしょうか?
本件は「解決」とさせて頂きますが、引き続きもしよろしければ初心者にも分かるように教えていただけると幸いです。

> さらにまずいことには、lengthの計算(11行目など)においてi1は高々2559の値しかとらないので、lengthは高々2560の値しか取りません。
> sizeof(char)は1だったはずなので、title[2560]やtext[2560]へのアクセスは確実に確保された領域の範囲外へのアクセスになります。
ご指摘のとおり、これはchar title[2560]を静的確保したいた時の名残です(しかもそれでもtitle[2559]までしかダメ)。
受け取った文字列が想定以上の長さの場合、'\0'が代入されない事を懸念しての事であり、静的に確保した場合は
受け取った文字列に関係なく、保険として最終文字を'\0'としていたのですが、動的確保に変えた段階でそれらを考慮しておりませんでした。

> まず、textやtitleの型を指定してください。
すいません、宣言のところも含めておくべきでした。
char text[100], title[100];
text[0] = 't'; text[1] = 'e'; text[2] = 'x'; text[3] = 't'; text[4] = '\0';
title[0] = 't'; title[1] = 'i'; title[2] = 't'; title[3] = 'l'; title[4] = 'e'; title[5] = '\0';


> ISLe()様

TCHARの存在を知りませんで、一度試してみましたが「'TCHAR *' から 'LPCSTR' に変換できません。」として
コンパイルが通らなかったため、今回は汎用性を放棄してTEXT("AAAあああ")の部分を"AAAあああ"に変更しました。
また、MessageBoxAを使用することで関数を切り替える手間を省略する方法を採る事にしました。
簡潔なご指摘で、知識不足ながら動作確認を行いやすかったので凄く嬉かったです。
ありがとうございました。

Re: MessageBoxとMessageBoxAを合わせた自作関数

Posted: 2015年5月06日(水) 23:28
by ISLe()
マルチバイト版Win32 APIは互換性のために残されていると言っても良いもので、変換するならワイド版に持っていくべきと思います。

TCHARを使って解決できる状況ならば、そもそもオーバーライドする必要がありません。

コード:

#include <windows.h>
int main(void)
{
	// プロジェクトの設定によりワイドとマルチバイトが切り替わる
	TCHAR ttext[] = { TEXT("テキスト") };
	TCHAR ttitle[] = { TEXT("タイトル") };
	MessageBox(NULL, TEXT("てきすと1"), TEXT("たいとる1"), MB_OK);
	MessageBox(NULL, &ttext[0], TEXT("たいとる2"), MB_OK);
	MessageBox(NULL, TEXT("てきすと3"), &ttitle[0], MB_OK);
	MessageBox(NULL, &ttext[0], &ttitle[0], MB_OK);

	// あえてマルチバイトからワイドに変換したい場合
	char mbtext[] = { "マルチバイトてきすと" };
	wchar_t wtext[256];
	MultiByteToWideChar(
		CP_ACP,
		MB_PRECOMPOSED,
		mbtext,
		-1,
		wtext,
		256);
	MessageBoxW(NULL, &wtext[0], L"ワイドたいとる", MB_OK);

	return 0;
}