戻る

・ wsprintf() は多用しない (98/12/15)

wsprintf() は非常に便利な関数ですが、1024 バイトの文字までしか扱えません。 じっさいオンラインマニュアルにも

Pointer to a buffer to receive the formatted output. The maximum size of the buffer is 1024 bytes. (MSDNライブラリ の wsprintf の項より引用)

と書いてあります。1024 バイトを越えると 1025 バイト以降の文字が 消えてしまいます。これに気がつかず 3 時間近くはまってました(^^;;


・コンソールアプリケーションからダイアログを呼ぼう(98/12/19/Sat)

普通 main() から始まるコンソールアプリケーションは WinMain() と 違ってアプリケーションのインスタンスを渡してくれないのでダイアログボックス を表示できませんが、オンラインマニュアルをざーっと よんでみたら GetModuleHandle() の所に

If this parameter is NULL, GetModuleHandle returns a handle to the file used to create the calling process. (MSDNライブラリ の GetModuleHandle の項より引用)

と書いてありましたので

#include < windows.h>
#include "resource.h"

BOOL CALLBACK DlgProc(HWND hWnd ,UINT msg ,WPARAM wp,LPARAM lp){
	if(msg == WM_CLOSE) EndDialog(hWnd,0);
	return FALSE;
}

void main(){

	HINSTANCE hInst = GetModuleHandle(NULL);

	DialogBox(hInst,MAKEINTRESOURCE(IDD_DIALOG1),NULL,(DLGPROC)DlgProc);
}
みたいなソースを書いてみたらちゃんとダイアログが表示されました。 しかし素直に WinMain() から呼んだ方が良いような (^^;

ちなみに http://www.microsoft.com/msj/code.htm にある MSJOCT96.EXE を解凍して出てくる \Under the Hood\crt0twin.c に書いてある WinMainCRTStartup() (WinMainの前に呼ばれる初期化関数) のソースを見ても GetModuleHandle(NULL) でアプリケーションのインスタンスを取得している 事が分かります。


・ソースの自動整形の方法 (98/12/19/Sat,99/7/15/Thu)

VC++ のエディタでソースを前の行に合わせて整形するとき、今までは いちいち手作業でやっていて非常に面倒くさかったんですが、 T 鼻君が「編集」の「IndentSelectionToPlay」機能を使うと良いですよ と教えてくれました。そこでさっそくツール→カスタマイズ→コマンド で「IndentSelectionToPlay」をツールバーに登録して、Ctrl+A で ファイル全体を選択して、登録した「「IndentSelectionToPlay」ボタンを 押したら見事にきれいに整形してくれました。うーん、一応編集機能は 全部チェックしたはずなんですが、こんな便利な機能を見落としていた とは(^^;;

(追記)整形するだけなら「IndentSelectionToPlay」よりも「SelectionFormat」機能 の方が早いです。


・ 95/98 より NT の方がビルドが早い (98/12/26/Sat)

ちょっと前はマイコン(TP535E) に NT が入っていたんですが、NT は起動とシャットダウンに 1 分位 かかって少し面倒なので、最近は 95 に戻していたんですが、 IE4 を入れてからまともに動作しなくなったので HDD をフォーマットして 95 と NT を入れて NT の方に VC++ を入れ直しました。一気に HDD の残りが 500 M 切りました(笑) (MIDI カードか NT 対応だったら 95 なんか入れないんですけどねえ。NT のドライバつくって 下さい > TDK)

まあそれはおいといて、前々から薄々気がついていたのですが、マシンのスペックが同じでも 95 と NT では コンパイルとビルドの速度が違うことが分かりました。NT の方が 30 倍速いです(笑、いやマジで)。やっぱり メモリまわりの問題ですかねえ。ま、ゲーム用の OS で開発なんかするなって事なんでしょうね。

という訳で 95 で VC++ を使っていて、コンパイル速度遅ーっとか、デバッグ中に OS ごと落ちるんじゃねえ、と嘆いている方はさっさと 95 を捨てて NT 入れましょう。


・ パイプでプロセス間通信の基本 (98/12/29/Tue)

プロセスの間で通信させるのに一番てっとり早い方法はパイプを使う方法です。 パイプというのは一言で言えばパイプみたいなもん(なんだそりゃ)なのですが、 中を流れているのは水や石油やカレーではなくて情報(メッセージ)であります。

このパイプには主に「名前なしパイプ」と「名前付パイプ」の2種類があり、 「名前付パイプ」を使えばネットワーク上の他のマシンで動いているプロセスと 通信する事が出来ますが、Win95/98 では「名前付パイプ」は作成出来ません。 CreateNamedPipe() のヘルプをみても対応していませんと書いてあります。 さすがゲーム用の OS(しつこい)。

って訳で今回は「名前なしパイプ」を使って子プロセスとの通信をする 方法を書きます。

流れとしては

(1) GetStdHandle() で親の入出力ハンドルを保存
(2) CreatePipe() でパイプ作成
(3) SetStdHandle() でパイプの片側を入出力ハンドルに割り当てる
(4) DuplicateHandle() で (3) で割り当てた側と逆の側を継承しない様に指定
    ついでにハンドルの複製を作る
(5) CloseHandle() で継承しないハンドルをクローズ
(5) CreateProcess() で子プロセス起動
(6) (1) で保存しておいた親の入出力ハンドルを戻す。
(6) 後は WriteFile() 、 ReadFile() で子プロセスと会話出来る
となります。ちなみに DuplicateHandle()で「継承しない」ようにするのを 忘れることが多いので気をつけましょう。
# そういや ア○キーから出てる 公式 API 辞典とかいう本には
# DuplicateHandle() の説明が無かったなあ。だいたいあの本の
# 内容はほとんど MSDN ライブラリの内容の丸写しだし。あれで
# 5000円は高いよなあ、って買ってもいないのにケチつけて
# はいかんな < 私 (^^;;
なおパイプの流れは一方通行ですので、入力と出力(場合によっては エラー出力)それぞれにパイプを作って割り当てる必要があります。 ま、くどくどと説明するより下のサンプルを見ればパイプの 作り方が分かると思います。
// パイプのテスト 12/29/98 (c)fides.dti.ne.jp

#include < windows.h>

#define R 0
#define W 1

#define CHR_BUF 4048
	
int WINAPI WinMain(  HINSTANCE hInst,  HINSTANCE hPrevInst,  LPSTR lpCmdLine,  int nCmdShow)         
{
	
	HANDLE hPipeP2C[2];  // 親 → 子 のパイプ(stdin)
	HANDLE hPipeC2P[2];  // 子 → 親 のパイプ(stdout)
	HANDLE hPipeC2PE[2]; // 子 → 親 のパイプ(stderr)
	HANDLE hDupPipeP2CW; // 親 → 子 のパイプ(stdin)の複製
	HANDLE hDupPipeC2PR; // 子 → 親 のパイプ(stdout)の複製
	HANDLE hDupPipeC2PE; // 子 → 親 のパイプ(stderr)の複製

	SECURITY_ATTRIBUTES secAtt;
	STARTUPINFO startInfo;
	PROCESS_INFORMATION proInfo;
	
	HANDLE hParent = GetCurrentProcess();
	
	char str[CHR_BUF],processName[CHR_BUF];
	DWORD dwByte;

	// コンソール割り当て
	FreeConsole();
	AllocConsole();

	//------------------------------------------------------
	// パイプ作成(STDOUT,STDERR,STDIN の3本)
	
	// 親の STDOUT , STDIN ,STDERR のハンドルを保存
	HANDLE hOldIn = GetStdHandle(STD_INPUT_HANDLE);
	HANDLE hOldOut = GetStdHandle(STD_OUTPUT_HANDLE);
	HANDLE hOldErr = GetStdHandle(STD_ERROR_HANDLE);
	
	// SECURITY_ATTRIBUTES の設定(パイプを作るのに必要
	secAtt.nLength = sizeof(SECURITY_ATTRIBUTES);
	secAtt.lpSecurityDescriptor = NULL;
	secAtt.bInheritHandle = TRUE;  // ハンドル継承
	
	//------------------------------------------------------
	// STDOUT
	
	// パイプ作成
	CreatePipe(&hPipeC2P[R],&hPipeC2P[W],&secAtt,0);
	
	// 「子」プロセスの STDOUT をセット
	SetStdHandle(STD_OUTPUT_HANDLE,hPipeC2P[W]);
	
	// 「子」からくるパイプの読み側(つまり親がリードする側)は継承しない
	DuplicateHandle(hParent,hPipeC2P[R],hParent,&hDupPipeC2PR,0,FALSE,DUPLICATE_SAME_ACCESS);
	
	// 読み側のハンドルをクローズ
	CloseHandle(hPipeC2P[R]);
	
	//------------------------------------------------------
	// STDERR
	
	// パイプ作成
	CreatePipe(&hPipeC2PE[R],&hPipeC2PE[W],&secAtt,0);
	
	// 「子」プロセスの STDERR をセット
	SetStdHandle(STD_ERROR_HANDLE,hPipeC2PE[W]);
	
	// 「子」からくるパイプの読み側(つまり親がリードする側)は継承しない
	DuplicateHandle(hParent,hPipeC2PE[R],hParent,&hDupPipeC2PE,0,FALSE,DUPLICATE_SAME_ACCESS);
	
	// 読み側のハンドルをクローズ
	CloseHandle(hPipeC2PE[R]);
	
	//------------------------------------------------------
	// STDIN
	
	//パイプ作成
	
	CreatePipe(&hPipeP2C[R],&hPipeP2C[W],&secAtt,0); 
	
	// 「子」プロセスの STDIN をセット
	SetStdHandle(STD_INPUT_HANDLE,hPipeP2C[R]);
	
	// 「子」からくるパイプの書き込み側(つまり親がライトする側)は継承しない
	DuplicateHandle(hParent,hPipeP2C[W],hParent,&hDupPipeP2CW,0,FALSE,DUPLICATE_SAME_ACCESS);
	
	// 書き込み側のハンドルをクローズ
	CloseHandle(hPipeP2C[W]);
	
	// パイプ作成終了
	//------------------------------------------------------


	// STARTUPINFO の設定
	memset(&startInfo,0,sizeof(STARTUPINFO));
	startInfo.cb = sizeof(STARTUPINFO);
	
	// 子プロセスでコマンドインタープリタを起動
	// STDIN,STDOUT,STDIN のハンドルが継承される(つまり親とパイプでつながる)

	GetEnvironmentVariable("ComSpec",processName,CHR_BUF);

	if(CreateProcess(processName,"",NULL,NULL,TRUE,
		0,NULL,NULL,&startInfo,&proInfo)==TRUE){
		
		// 子プロセスが起動したら親の STDIN と STDOUT を戻す
		SetStdHandle(STD_OUTPUT_HANDLE,hOldOut);
		SetStdHandle(STD_INPUT_HANDLE,hOldIn);
		SetStdHandle(STD_ERROR_HANDLE,hOldErr);
		
		// "dir" コマンドを子プロセスに送る
		wsprintf(str,"dir\r\n");  // (注) CR-LF を入れないとコマンドを受け取ってくれない
		WriteFile(hDupPipeP2CW,str,strlen(str),&dwByte,NULL);
		
		// "exit" 
		wsprintf(str,"exit\r\n");  
		WriteFile(hDupPipeP2CW,str,strlen(str),&dwByte,NULL);
		
		// バッファのフラッシュ
		FlushFileBuffers(hDupPipeP2CW);
		FlushFileBuffers(hDupPipeC2PR);
		
		// 子プロセスが終るまで停止
		WaitForSingleObject(proInfo.hProcess,INFINITE);
		
		// 子からきたメッセージを読む
		ReadFile(hDupPipeC2PR,str,CHR_BUF,&dwByte,NULL);
		str[dwByte] = '\0';
		
		MessageBox(NULL,str,"",NULL);
		
	}
	
	return 0;
}


・ショートファイル名をロングファイル名へ変換する方法(99/1/8/Fri)

コマンドラインで起動するとファイル名はショートファイル名でしかえられないので ロングファイル名へ変換する必要があります。ところがそういう API は何故か無いので FindFirstFile()を使わなければなりません。具体的には次の関数の様にして変換します。

// 1999 (c) tokai@fides.dti.ne.jp
// 
// ショートファイル名をロングファイル名へ変換する関数
// lpFileName に変換したいファイル名を入れるとロングファイル名に変換される
BOOL ShortToLongName(char* lpFileName){
	
	char fPath[MAX_PATH],fDrive[MAX_PATH],fName[MAX_PATH],fExt[MAX_PATH]; 
	WIN32_FIND_DATA findData;
	HANDLE hFind;
	
	hFind = FindFirstFile(lpFileName,&findData);
	
	if(hFind==INVALID_HANDLE_VALUE) return FALSE;
	
	FindClose(hFind); 
	
	_splitpath(lpFileName,fDrive,fPath,fName, fExt);	
	wsprintf(lpFileName,"%s%s%s",fDrive,fPath,findData.cFileName);  

	return TRUE;
}


・スクリーンセーバーにはショートファイル名をつけよう(99/1/19/Tue)

画面のプロパティーのスクリーンセーバーの名前のところには、普通は 作成したスクリーンセーバーのリソーススクリプトのストリングテーブルの IDS_DESCRIPTION で定義した文字列が入るのですが、スクリーンセーバーの 名前がロングファイル名の時は、そのファイル名が表示されてしまいます。 例えば、ファイル名が「ssaver.scr」で IDS_DESCRIPTIONを「スクリーンセーバー」 とした時は、画面のプロパティーの所には「スクリーンセーバー」と表示されます。 しかしファイル名が「screensaver.scr」、 IDS_DESCRIPTIONが「スクリーンセーバー」 とした時は、画面のプロパティーの所に「screensaver」と表示されてします。

したがってスクリーンセーバーの名前には出来るだけショートファイル名を 付けたほうが良いです。

あ、ちなみにこれは NT の話で、95/98 は IDS_DESCRIPTION を見てない様で ファイル名がそのままプロパティーに表示されます。ま、95/98 の事は考えない 様にしましょう。


・ハードの問題が絡むと面倒くさい(99/1/28/Thu)

MCI を使わないで音声の録音をする場合は自分でメモリを確保して、音源のデバイス を開いて録音して、デバイスクローズ、メモリ開放という流れで録音します。 当然ですが音質やメモリの管理の仕方はデバイスによって結構変わります。 デバイスの特性を知っていないと次の様にハマリます。

私は ThinkPader ですので音源はあの悪名 高い(-_-; M-WAVE を使っているのですが、M-Wave を使って録音しようと思って 普通に GlobalAlloc() でメモリを確保してデバイスを開いてサンプリングして GlobalFree() で開放しました。するとタスクマネージャーで見る分にはちゃんと メモリを開放しているにも関わらず確保した部分のメモリのロックが外れなくて 実メモリからその部分が出て行かないという症状が起こりました。

で、2,3日色々試してみて結局これは OS(WinNT4) が悪いんじゃなくて M-Wave が悪さをしている事が分かりました。M-Wave で録音を開始すると メモリのまだ使っていないブロックも全部ロックしてしまい、録音が終了して アプリケーションはメモリを開放しているにも関わらず、M-Wave がロックを 外さないので実メモリから出て行かなくなるみたいです。

で結局何を言いたいのかというと、ハードの問題が絡むと事態が面倒くさくなる ということです(そのまんま (^^;; )。録音とか録画など、 デバイスに直接アクセスする様なアプリケーションを作っていて何か問題が起きたら、 ソースの他にもデバイスを疑ってみるべきでしょう。


・VC++6.0 のプロパティシートは昔の Win95 では動かない(99/1/28/Thu)

VC++6.0 で PropetySheet() を使ってプロパティーシートを作ってみたら、 そのプロパティシートは最初のバージョンの Win95 (IE が入って無い奴) では 表示されませんでした。ただそのバージョンの Win95 でも画面のプロパティとかの プロパティシートは表示されているので何か原因があるんだろうと思ってちょっと 調べてみたら、昔と IE4.0 以後の comctl32.dll は互換性が無く、しかも VC++6.0 で作成したアプリは「IE4.0 以後の」comctl32.dllを対象にしたアプリを ビルドするということが分かりました。つまり

「VC++6.0 でビルドしたアプリのコモンコントロールは昔の Win95 では 動かないか変な動作をする」

という事です。

この辺の詳しいことは MSDNライブラリ の Shell and Common Controls: Platform SDK の Common Controls の項を見ていただく事にして、 とにかく昔のバージョンの Win95 でもプロパティシートを表示させたい場合には ソースのはじめに「#define _WIN32_IE 0x0200」と書くか プロジェクトの設定のプロジェクトオプションの所に 「/D _WIN32_IE=0x0200」スイッチを追加してみて下さい。 するとコンパイラは旧バージョンの comctl32.dll を対象 としたコードをはいてくれます。もちろんこの設定をすると最近の comctl32.dll にある機能は使えなくなります。また UDM_SETRANGE32 などの一部の コモンコントロールのメッセージもサポートされなくなります。

まあ、とにかく互換性を気にする場合は上の様な設定でビルドしておいた方が 賢明でしょう。


・DIB の幅には気を付けよう(99/2/19/Fri)

配列を使って画像を扱える DIB (device-independent bitmap) は非常に 便利なのですが、CreateDIBSection() を使う時に一つだけ重大な落とし穴が あります。 それはDIB を作る時はビットマップの幅に気を付けなければ ならないことです。

MSDN の BITMAPINFO 構造体の所に「The bits in the array are packed together, but each scan line must be padded with zeroes to end on a LONG data-type boundary. 」とあります。これは「ビットマップのスキャンライン は 32 bit 境界に合わせろ」と言う意味です。もっと分かりやすくいえば、

8 bit カラーの時は横のピクセル数を 4 の倍数

16 bit カラーの時は横のピクセル数を 2 の倍数

24 bit カラーの時は横のピクセル数を 4 の倍数

にしなければなりません。また(描画に関係の無い、追加した)余分な部分に は 0 を入れておく必要があります。

これを忘れてると画像が変なふうに表示されて大ハマリ状態に陥ります(^^;; 気を付けましょう。