Posts Tagged Visual C++

続・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」に続く。

Microsoft Visual Studio 2010 Professional アカデミック

Microsoft Visual Studio 2010 Professional アカデミック

定価:¥ 13,824

Amazon価格:¥ 84,800

カテゴリ:DVD-ROM

発売日:2010-06-18



プログラミングWindows第5版〈上〉Win32 APIを扱う開発者のための決定版! (Microsoft Programming Series)

著者/訳者:チャールズ ペゾルド

出版社:アスキー( 2000-10 )

定価:

単行本 ( 790 ページ )

ISBN-10 : 4756136001

ISBN-13 : 9784756136008



アマゾンのサーバでエラーが起こっているかもしれません。
一度ページを再読み込みしてみてください。

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(&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(&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