Posts Tagged Visual C++
続・dprintf
Posted by yu++ in Programming on 2010 年 7 月 5 日
前回「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は前のバージョンの法が良かったのかもしれないと思えてきたり・・・。
(で、でも、文字数制限は無いぞ!と、自分を擁護しつつ、こちらを改良して行くことに。。。)
お粗末。
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」に続く。
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);
で、これを実行すると・・・。

タイトル、化けちゃってるね・・・。
どうやら、そもそもの修正方法が違うらしい。
粂井さんのサンプルはどうやらSJIS前提らしく、関数もSJIS前提の関数とかっぽい??
現状のプロジェクトファイルはUnicodeで設定されていて、それを直したりすれば行けるっぽい。
とりあえず、文字エンコーディングを直す。
(Alt-F7でプロジェクトのプロパティ)
プロジェクトのプロパティで「構成」が「Unicode文字セットを使用する」とか書いてあると思うんだけど、

「マルチバイト文字セットを使用する」とする。

恐らくこれで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);
お粗末さまでした。
