Posts Tagged Win32 API

続・dprintf

前回「Visual CのGUIアプリで「出力」ウィンドウへデバッグメッセージを出す」でのdprintfは文字サイズ固定だったからちょっとどうかなぁ〜と思った次第。
多少は動的にして文字数に余裕を持たせたいところ。

あと、MSのAPIにはprintf_sとかsprintf_sとか、さらにはvsnprintf_sとか「_s」付きのセキュリティ強化版がある。
で、これ使ってエラー出すと完全に止まる。。。
例えばバッファより文字が多かった場合、即止まって怒られて落ちる。
本当はその後にバッファをより多くreallocする予定だったのにも関わらず・・・。
そしてそのエラーの止め方がわからない。。
なので、デバッグと言う名目なので、若干セキュアじゃないvsnprintfを使うことに。
(超後ろ向き。。。)
UNIX系でサポートされてない関数をバカスカ使うのも正直気が引けてたから、これで良いのだ〜♪
・・・なんてね。

とりあえず今回書いたコード。

// debug.h
#define _DPRINTF_MALLOC_ERR_ -100
#define _DPRINTF_ARG_ERR_ -101
#define _DPRINTF_REALLOC_ERR_ -102

int dprintf( const char *format, ...);
// debug.cpp
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include "debug.h"

#define _DEBUG_BUFF_BASE_SIZE_ 256

int dprintf( const CHAR *format, ...)
{
	va_list argPtr;
	char *debugMsgBuffer;
	char *tmpMsgBuffer;
	int msgLen = 0;
	int maxBuffSize = _DEBUG_BUFF_BASE_SIZE_;

	va_start( argPtr, format );
	debugMsgBuffer = (char *) malloc( maxBuffSize );
	if ( debugMsgBuffer == NULL ) {
		OutputDebugStringA( "dprintf:malloc error.\n" );
		return _DPRINTF_MALLOC_ERR_;
	}

	if ( format == NULL ) {
		OutputDebugStringA( "dprintf:format argument is null.\n" );
		return _DPRINTF_ARG_ERR_;
	}

	msgLen = vsnprintf( debugMsgBuffer, maxBuffSize - 1, format, argPtr );
	// vsnprintfにてバッファ終端に'\0'が書き込まれない
	// 時があったので、strlenでも長さチェックをかける。
	// あるいは、この時点でスタックを破壊している可能性もある。
	if ( (int) strlen( debugMsgBuffer ) > msgLen ) msgLen = -2;

	// メモリが足りないときの処理
	while ( msgLen < 0 ) {
		maxBuffSize += _DEBUG_BUFF_BASE_SIZE_;
		tmpMsgBuffer = (char *) realloc( debugMsgBuffer, maxBuffSize );
		if ( tmpMsgBuffer == NULL ) {
			free( debugMsgBuffer );
			OutputDebugStringA( "dprintf:realloc error.\n" );
			return _DPRINTF_REALLOC_ERR_;
		}
		debugMsgBuffer = tmpMsgBuffer;

		msgLen = vsnprintf( debugMsgBuffer, maxBuffSize - 1, format, argPtr );
		// vsnprintfの'\0'書き忘れ問題をここでも対処。
		if ( (int) strlen( debugMsgBuffer ) > msgLen ) msgLen = -2;
	}

	OutputDebugString( debugMsgBuffer );
	free( debugMsgBuffer );

	return msgLen;
}

まぁ、コメントの通りなんだけど、

if ( (int) strlen( debugMsgBuffer ) > msgLen ) msgLen = -2;

なんてい言う小賢しい処理を入れてる。
何でかというと、下記のコードを実行してもらいたい。

vsnprintfでおかしくなる。

// debug.cpp
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include "debug.h"

#define _DEBUG_BUFF_BASE_SIZE_ 4

int dprintf( const CHAR *format, ...)
{
	va_list argPtr;
	char *debugMsgBuffer;
	char *tmpMsgBuffer;
	int msgLen = 0;
	int maxBuffSize = _DEBUG_BUFF_BASE_SIZE_;

	char test[1024];
	int testLen = 0;

	setlocale( LC_ALL, "C" );

	va_start( argPtr, format );
	debugMsgBuffer = (char *) malloc( maxBuffSize );
	if ( debugMsgBuffer == NULL ) {
		OutputDebugStringA( "dprintf:malloc error.\n" );
		return _DPRINTF_MALLOC_ERR_;
	}

	if ( format == NULL ) {
		OutputDebugStringA( "dprintf:format argument is null.\n" );
		return _DPRINTF_ARG_ERR_;
	}

	msgLen = vsnprintf( debugMsgBuffer, maxBuffSize - 1, format, argPtr );
	// vsnprintfにてバッファ終端に'\0'が書き込まれない
	// 時があったので、strlenでも長さチェックをかける。
	// あるいは、この時点でスタックを破壊している可能性もある。
	// if ( strlen( debugMsgBuffer ) > msgLen ) msgLen = -2; // ここをコメントアウト
	// メモリが足りないときの処理
	while ( msgLen < 0 ) {
		{ // debug
			testLen = sprintf( test, "maxBuffSize appended from \"%d\" to", maxBuffSize );
			maxBuffSize += _DEBUG_BUFF_BASE_SIZE_;
			sprintf( &test[testLen], "\"%d\".\n", maxBuffSize );
			OutputDebugStringA( test );
		} // debug end
		tmpMsgBuffer = (char *) realloc( debugMsgBuffer, maxBuffSize );
		if ( tmpMsgBuffer == NULL ) {
			free( debugMsgBuffer );
			OutputDebugStringA( "dprintf:realloc error.\n" );
			return _DPRINTF_REALLOC_ERR_;
		}
		debugMsgBuffer = tmpMsgBuffer;

		msgLen = vsnprintf( debugMsgBuffer, maxBuffSize - 1, format, argPtr );
		{ // debug
			sprintf( test, "maxBufferSize = \"%d\"\n msgLen = \"%d\"\n  strlen( debugMsgBuffer ) = \"%d\"\n", maxBuffSize, msgLen, strlen( debugMsgBuffer ) );
			OutputDebugStringA( test );
		} // debug end
		// vsnprintfの'\0'書き忘れ問題をここでも対処。
		// if ( strlen( debugMsgBuffer ) > msgLen ) msgLen = -2; // ここをコメントアウト
	}

	OutputDebugString( debugMsgBuffer );
	free( debugMsgBuffer );

	return msgLen;
}

さっきの小賢しいコード2カ所を撤去してメモリを何バイト取得し、文字列の長さはいくらで、実際の文字列の長さはいくらかを表示しているコードも入ってる。
これを額面通り

dprintf( "hoge = %5d\n", hoge );

とか文字数を変えて実行してみると、変な文字が付いてくる可能性がある。
自分の環境では

glnWidth =  1432	glnHeight =   815

を期待したところ、

glnWidth =  1432	glnHeight =   815
ォォォォォォォォォ

と言うようなゴミが付いてきた。
どうやら境界線ギリギリで書き込みを行う場合に’\0′が書き込まれていないような気もする。
とは言え、vsnprintfの第2引数を-1から-2に変えたところで同様のエラーが起きる。
個人的には手詰まり。
なので、本当に文字処理をプログラム中で扱うときはこの関数は使えない。。。
対処できないし、こんなの。
strlenによるネガティブな対処で良ければいくらでもやるけど、これ、正解じゃないよね?
そんなわけで、dprintfは前のバージョンの法が良かったのかもしれないと思えてきたり・・・。
(で、でも、文字数制限は無いぞ!と、自分を擁護しつつ、こちらを改良して行くことに。。。)

お粗末。

Post to Twitter

, , , , , ,

No Comments

Visual CのGUIアプリで「出力」ウィンドウへデバッグメッセージを出す

俺、PHPerの頃からの癖で、デバッグメッセージをどうしても出したくなる。
コマンドライン上では普通にprintfではき出せば良いんだけど、GUIとなるとそこに出すのもちょいと面倒だし、メッセージボックスなんてやった日には、メッセージボックスの嵐となる可能性さえ秘めているのは自明。
で、Visual Studioでデバッグすると出力ウィンドウがあります、と。
これを使いたい。
答えを書いちゃうとwindows.hにある、OutputDebugStringA( *str )と言う関数で実現可能です、と。
と言うわけで、dprintfを自作。

// debug.h
bool dprintf( const char *str, ...);
// debug.cpp
#include <windows.h>
#include <stdio.h>
#include <stdarg.h>

#define _DEBUG_OUT_BUFF_SIZE_ 128

bool dprintf( const char *str, ...)
{
	char debugOutBuff[ _DEBUG_OUT_BUFF_SIZE_ ];

	va_list ap;
	va_start( ap, str );

	if ( !vsprintf_s( debugOutBuff, _DEBUG_OUT_BUFF_SIZE_, str, ap ) ) {
		OutputDebugStringA( "dprintf error." );
		return false;
	}
	OutputDebugStringA( debugOutBuff );

	return true;
}

dprintfで「出力」ウィンドウへ出力したところ

まぁ、クソみたいな関数だけど、一応メモ。
使い方はまぁ、printfと同じだと思ってもらえれば。
あと、OutputDebugStringではなく、何故OutputDebugStringAを使っているかというと、「出力」ウィンドウがShift_JISだったからと言う。。。
少なくとも俺の持ってるVisual Studio 2008 Professionalはそうなってた。
OutputDebugStringを指定しておくとプロジェクトがUnicodeを使うように指定されていると、コンパイル時にOutputDebugStringWと言うUnicode版に書き換わってしまって都合が悪いかなぁ、と。
VC2010の「出力」ウィンドウがUnicodeになってるんだったらifdef使うなり拡張はするかも。

そんだけ。

んが、「続・dprintf」に続く。

Post to Twitter

, , , , , ,

No Comments

WindowsでBitBltなるものを勉強

最近ちょっと落ち着いてきたので、「プログラミングWindows」を読み進めていたんだけど、どーにもつまらない。
俺がやりたいのは、ドット単位での画面描画なのに、ずーっとベクター系。
で、終わったと思えば「キーボード」の章。
ん~・・・。

やりたいことは結構単純で、リアルタイム3DCGの本があるからその公式やらを元にウィンドウ内に書き込みたいなぁ、と。
だから、勉強方法を変えてみることにした。
ウィンドウ描いてその中をラスタライズして2Dにしたリアルタイム3Dを表示したいだけ。
思い切ってページをすっ飛ばして、画面のビットマップ情報をいじることに。
その手始めとして「BitBlt(Bit-block transfer:ビットブリット)」というのをやってみた。

#include <windows.h>

LRESULT CALLBACK MyWindow(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance,
				   HINSTANCE hPrevInstance, LPSTR lpCmdLine,int iCmdShow)
{
	static	TCHAR	szAppName[] =
		TEXT("おいらのアプリ");	//アプリ名?
	HWND			hwnd;	//ウィンドウハンドル
	MSG				msg;	//メッセージ
	WNDCLASS		wndCls;	//ウィンドウクラス構造体

	//ウィンドウスタイルを指定
	wndCls.style			= CS_HREDRAW | CS_VREDRAW;
	//ウィンドウプロシージャを指定
	wndCls.lpfnWndProc		= MyWindow;
	wndCls.cbClsExtra		= 0;
	wndCls.cbWndExtra		= 0;
	//インスタンスハンドル変数を指定
	wndCls.hInstance		= hInstance;
	//ウィンドウのアイコンを指定
	wndCls.hIcon			= LoadIcon(NULL, IDI_APPLICATION);
	//カーソルの形を指定
	wndCls.hCursor			= LoadCursor(NULL, IDC_CROSS);
	//バックグラウンド色を指定
	wndCls.hbrBackground	= (HBRUSH) GetStockObject(WHITE_BRUSH);
	wndCls.lpszMenuName		= NULL;
	wndCls.lpszClassName	= szAppName;

	if (!RegisterClass(&wndCls)) {
		MessageBox(NULL,
			TEXT("このプログラムはWindowsNT以降対応です。"),
			TEXT("そーりー"), MB_ICONERROR);
		return 0;
	}

	hwnd = CreateWindow(szAppName, TEXT("どうよ? - Bitmap Viewer"),
						WS_OVERLAPPEDWINDOW,
						CW_USEDEFAULT, CW_USEDEFAULT,
						CW_USEDEFAULT, CW_USEDEFAULT,
						NULL, NULL, hInstance, NULL);

	//ウィンドウ表示状態を指定
	ShowWindow(hwnd, iCmdShow);
	//ウィンドウ更新実行
	UpdateWindow(hwnd);

	while (GetMessage(&msg, NULL, 0, 0)) { //メッセージループ
		TranslateMessage(&msg);	//命令変換?
		DispatchMessage(&msg);	//命令転送
	}

	return msg.wParam;
}

LRESULT CALLBACK MyWindow(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static int	cxClient, cyClient, cxSource, cySource;
	HDC			hdcClient, hdcWindow;
	PAINTSTRUCT ps;
	int			x, y;
	TCHAR		buff[512];

	switch (message) {
		case WM_CREATE:
			cxSource = GetSystemMetrics (SM_CXSIZEFRAME) +
					GetSystemMetrics (SM_CXSIZE);

			cySource = GetSystemMetrics (SM_CYSIZEFRAME) +
					GetSystemMetrics (SM_CYCAPTION);

			wsprintf(buff,
				TEXT("GetSystemMetrics (SM_CXSIZEFRAME):%d\n")
				TEXT("GetSystemMetrics (SM_CXSIZE):%d\n")
				TEXT("GetSystemMetrics (SM_CYSIZEFRAME):%d\n")
				TEXT("GetSystemMetrics (SM_CYCAPTION):%d"),
				GetSystemMetrics (SM_CXSIZEFRAME),
				GetSystemMetrics (SM_CXSIZE),
				GetSystemMetrics (SM_CYSIZEFRAME),
				GetSystemMetrics (SM_CYCAPTION));
			MessageBox(NULL, buff, TEXT("情報"), MB_OK);
			return 0;

		case WM_SIZE:
			cxClient = LOWORD (lParam);
			cyClient = HIWORD (lParam);
			return 0;

		case WM_PAINT:
			hdcClient = BeginPaint (hwnd, &ps);
			hdcWindow = GetWindowDC (hwnd);

			for (y = 0; y < cyClient; y += cySource)
				for (x = 0; x < cxClient; x += cxSource) {
					BitBlt (hdcClient, x, y, cxSource, cySource,
							hdcWindow, 0, 0, SRCCOPY);
				}

			ReleaseDC (hwnd, hdcWindow);
			EndPaint (hwnd, &ps);
			return 0;

		case WM_DESTROY:
			PostQuitMessage (0);
			return 0;
	}

	return DefWindowProc (hwnd, message, wParam, lParam);
}

ほぼほぼ「プログラミングWindows」上のソースの写経。
何がやりたいかというと、タイトルバーの左端にあるアイコンをウィンドウ内に敷き詰めるというもの。
改造点はプログラムが立ち上がる際に、アイコンの左端の取得アイコン座標の情報を表示する点。

一生やってろと言いたくなるくらいのペースに苦笑い。

そんだけ。

Post to Twitter

, ,

No Comments

Visual Studio 2008 Express Editionを使い始める、の巻

ちょっとCとC++の勉強もあったまってきたので、「猫でもできるプログラミング」を参考にしながらWindows SDKプログラミングもちょっくらいじりだそうかと思っている昨今。
さっそくイントロダクションでつまったので、備忘録を載せておくことに。

写経すること数分、正直言ってビルド失敗。
で、チキン野郎なので、そのままコピペ・・・したら(エンコーディングの問題からか、)1行になったので、それを分解・・・。
するとどうでしょう。
うごかねぇ~~。。

そのエラーを探ること数分、見つけましたよ、原因を。
そもそも、このページのオーナーさんである粂井さんが一連の記事を書き始めたときのVCのバージョンが4.0だったことが事の発端なんだとか。
というわけで、どこを直せばいいかを書いておこうかなぁ、と。

まず、コンパイラのエラーを見る。

error C2440: '=' : 'HGDIOBJ' から 'HBRUSH' に変換できません。
        'void*' から非 'void' 型への変換には明示的なキャストが必要です。
error C2440: '=' : 'char [25]' から 'LPCWSTR' に変換できません。
        指示された型は関連がありません。変換には reinterpret_cast、C スタイル キャストまたは関数スタイルのキャストが必要です。
error C2664: 'CreateWindowExW' : 2 番目の引数を 'char [25]' から 'LPCWSTR' に変換できません。(新しい機能 ; ヘルプを参照)
        指示された型は関連がありません。変換には reinterpret_cast、C スタイル キャストまたは関数スタイルのキャストが必要です。

最初に、本来HBRUSH型を要求するhbrBackgroundにGetStockObject(WHITE_BRUSH)を入れていることからエラーとなったご様子。
HGDIOBにキャストしてみる。

2番目はszClassNameがchar型なのに対して、LPCWSTR型を要求する場所を引数として入れていることが問題のご様子。
LPCWSTRにキャストしてみる。
(3箇所)

で、該当箇所はこの辺り。

	WNDCLASS myProg;
	if (!hPreInst) {
		myProg.style			= CS_HREDRAW | CS_VREDRAW;
		myProg.lpfnWndProc		= WndProc;
		myProg.cbClsExtra		= 0;
		myProg.cbWndExtra		= 0;
		myProg.hInstance		= hInstance;
		myProg.hIcon			= NULL;
		myProg.hCursor			= LoadCursor(NULL, IDC_ARROW);
		// VC 6.0以降は型のチェックが徐々に厳しくなっている。
		// 本来HBRUSH型を要求するhbrBackgroundにGetStockObject(WHITE_BRUSH)を入れたところ、
		// エラーとなるのでHGDIOBにキャストしている。
		//myProg.hbrBackground	= GetStockObject(WHITE_BRUSH);
		myProg.hbrBackground	= (HBRUSH) GetStockObject(WHITE_BRUSH);
		myProg.lpszMenuName		= NULL;
		// myProg.lpszClassName	= szClassNme;
		myProg.lpszClassName	= (LPCWSTR) szClassNme;
		if (!RegisterClass(&amp;myProg)) return FALSE;
	}
	hWnd = CreateWindow(
		//szClassNme,
		//"俺にもできる?Windowsプログラミング",
		(LPCWSTR) szClassNme,
		(LPCWSTR) "俺にもできる?Windowsプログラミング",
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		NULL,
		NULL,
		hInstance,
		NULL);

で、これを実行すると・・・。
blank_window_false
タイトル、化けちゃってるね・・・。

どうやら、そもそもの修正方法が違うらしい。
粂井さんのサンプルはどうやらSJIS前提らしく、関数もSJIS前提の関数とかっぽい??
現状のプロジェクトファイルはUnicodeで設定されていて、それを直したりすれば行けるっぽい。

とりあえず、文字エンコーディングを直す。
(Alt-F7でプロジェクトのプロパティ)
プロジェクトのプロパティで「構成」が「Unicode文字セットを使用する」とか書いてあると思うんだけど、
vc2008express_property_unicode
「マルチバイト文字セットを使用する」とする。
vc2008express_property_sjis
恐らくこれでSJISになる。

で、今度は別のエラーが。

error C2440: '=' : 'LPCWSTR' から 'LPCSTR' に変換できません。
        指示された型は関連がありません。変換には reinterpret_cast、C スタイル キャストまたは関数スタイルのキャストが必要です。
error C2664: 'CreateWindowExA' : 2 番目の引数を 'LPCWSTR' から 'LPCSTR' に変換できません。(新しい機能 ; ヘルプを参照)
        指示された型は関連がありません。変換には reinterpret_cast、C スタイル キャストまたは関数スタイルのキャストが必要です。

なんだとか。
・・・LPCWSTR型にしなくてよかった、と。。。

さっきの箇所は、これでいいらしい・・・。

	WNDCLASS myProg;
	if (!hPreInst) {
		myProg.style			= CS_HREDRAW | CS_VREDRAW;
		myProg.lpfnWndProc		= WndProc;
		myProg.cbClsExtra		= 0;
		myProg.cbWndExtra		= 0;
		myProg.hInstance		= hInstance;
		myProg.hIcon			= NULL;
		myProg.hCursor			= LoadCursor(NULL, IDC_ARROW);
		// VC 6.0以降は型のチェックが徐々に厳しくなっている。
		// 本来HBRUSH型を要求するhbrBackgroundにGetStockObject(WHITE_BRUSH)を入れたところ、
		// エラーとなるのでHGDIOBにキャストしている。
		//myProg.hbrBackground	= GetStockObject(WHITE_BRUSH);
		myProg.hbrBackground	= (HBRUSH) GetStockObject(WHITE_BRUSH);
		myProg.lpszMenuName		= NULL;
		myProg.lpszClassName	= szClassNme;
		if (!RegisterClass(&amp;myProg)) return FALSE;
	}
	hWnd = CreateWindow(
		szClassNme,
		"俺にもできる?Windowsプログラミング",
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		NULL,
		NULL,
		hInstance,
		NULL);

結果はこれ。
blank_window_success

お粗末さまでした。

Post to Twitter

, , , , ,

No Comments

Bad Behavior has blocked 24 access attempts in the last 7 days.