Posts Tagged 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」に続く。
メモリのアラインメントを確認する
そもそもの話。
なぜ「ゲームプログラマになる前に覚えておきたい技術(以下「セガ本」)」を読んでる途中で「C言語ポインタ完全制覇(以下「ポインタ本」)」を読むことになったか?
それは、セガ本にデータ型のサイズで割りきれるアドレスでデータを読み込まないと、x86系(いわゆるIntel、AMD系)以外のCPUではクラッシュし、x86でもデータを2回読むことになってパフォーマンスが落ちる、と書いてあったから。
で、パッと見て理解できないでいたら、時を同じくしてポインタ本が届き、目次にアラインメントの記述が。
と言うわけで、ポインタ本を読み進めてみた次第。
そこで思ったのが、この本、本当に為になる。
俺みたいなCの見習いの肩もみの家の便所掃除みたいな人間にはいろいろ発見が多すぎて驚く。
例えば
#include <stdio.h>
int main()
{
int i;
char s[] = "abcdefghij";
for ( i = 0; i < sizeof( s ) - 1; ++i ) {
printf( "%d:%c\n", i, i[s] );
}
}
なんてコード。
実行するとこう。
McLaren% gcc -o pointer_test pointer_test.c McLaren% ./pointer_test 0:a 1:b 2:c 3:d 4:e 5:f 6:g 7:h 8:i 9:j
9行目のi[s]はs[i]の間違いと思いがちだけど、”これでも”問題ない。
出来るか出来ないかの話であって、常識的にこの書き方をして良いかは別の話(そしてもちろんダメ)。
この表記はコンパイル時に
printf( "%d:%c\n", i, *( i + s ) );
と変換しているだけ。
確かにポインタ+数値でそのポインタの型の分だけ先へ進む機能がある。
とまぁ、なかなか興味深い。
さてさて本題。
ようやくアラインメントが書いてあるページまでたどり着いた。
どうやら現代のコンパイラはかなりその辺りも配慮しているらしく、次に置かれるデータがそのデータ型で割り切れない場合、”詰め物”をしてくれるんだとか。
実際コードにして試してみまふ。
// alignment_check.c
#include <stdio.h>
typedef struct {
char ch;
double dn;
short sn;
int nu;
} MyStruct;
int main()
{
MyStruct hoge;
unsigned long gap1, gap2, gap3;
printf( "sizeof char\t%lu\n", sizeof( char ) );
printf( "sizeof double\t%lu\n", sizeof( double ) );
printf( "sizeof short\t%lu\n", sizeof( short ) );
printf( "sizeof int\t%lu\n\n", sizeof( int ) );
printf( "sizeof MyStruct\t%lu\n\n", sizeof( MyStruct ) );
printf( "\t&hoge's hexadecimal memory address.\n" );
printf( "\tname\t\ttype\t\taddress\t\tsize\n" );
printf( "\t&hoge\t\tMyStruct\t%p\t%4lu\n", &hoge, sizeof( hoge ) );
printf( "\t&hoge.ch\tchar\t\t%p\t%4lu\n", &hoge.ch, sizeof( hoge.ch ) );
gap1 = (unsigned long)&hoge.dn -
( (unsigned long)&hoge.ch + sizeof( hoge.ch ) );
printf( "gap1:%lu\n", gap1 );
printf( "\t&hoge.dn\tdouble\t\t%p\t%4lu\n", &hoge.dn, sizeof( hoge.dn ) );
gap2 = (unsigned long)&hoge.sn -
( (unsigned long)&hoge.dn + sizeof( hoge.dn ) );
printf( "gap2:%lu\n", gap2 );
printf( "\t&hoge.sn\tshort\t\t%p\t%4lu\n", &hoge.sn, sizeof( hoge.sn ) );
gap3 = (unsigned long)&hoge.nu -
( (unsigned long)&hoge.sn + sizeof( hoge.sn ) );
printf( "gap3:%lu\n", gap3 );
printf( "\t&hoge.nu\tint\t\t%p\t%4lu\n\n", &hoge.nu, sizeof( hoge.nu ) );
printf( "char + gap1 + double + gap2 + short + gap3 + int = %lu\n\n",
sizeof( char ) + gap1 +
sizeof( double ) + gap2 +
sizeof( short ) + gap3 +
sizeof( int ) );
printf( "&hoge's decimal memory address (with alighnment gap size).\n" );
printf( "\tname\t\ttype\t\taddress\t\tsize\n" );
printf( "\t&hoge\t\tMyStruct\t%ll\t%4lu\n", &hoge, sizeof( hoge ) );
printf( "\t&hoge.ch\tchar\t\t%lu\t%4lu\n", &hoge.ch, sizeof( hoge.ch ) );
printf( "gap1:%lu\n", gap1 );
printf( "\t&hoge.dn\tdouble\t\t%lu\t%4lu\n", &hoge.dn, sizeof( hoge.dn ) );
printf( "gap2:%lu\n", gap2 );
printf( "\t&hoge.sn\tshort\t\t%lu\t%4lu\n", &hoge.sn, sizeof( hoge.sn ) );
printf( "gap3:%lu\n", gap3 );
printf( "\t&hoge.nu\tint\t\t%lu\t%4lu\n", &hoge.nu, sizeof( hoge.nu ) );
return 0;
}
まぁ、こんだけ親切に情報盛ったから、アドレス表記だけで良いとは思ったけど、一応アドレスを10進数にしたやつも記載。
(つっても、64ビット環境においてunsigned long程度でアドレス表示して間に合わない気がするけど、ギャップを目視することが目的だから、あえてこのまま。
あれ?ラップトップのMacでgcc動かしたときって64ビット扱い?
・・・ま、今回は気にしないことに。
どうせテストだし。。。
まともに64ビットアドレス書いてたら、京単位らしいから・・・ね。)
で、結果。
McLaren% gcc -o alignment_check alignment_check.c
alignment_check.c: In function ‘main’:
alignment_check.c:48: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘struct MyStruct *’
alignment_check.c:49: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘char *’
alignment_check.c:52: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘double *’
alignment_check.c:55: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘short int *’
alignment_check.c:58: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘int *’
McLaren% ./alignment_check
sizeof char 1
sizeof double 8
sizeof short 2
sizeof int 4
sizeof MyStruct 24
&hoge's hexadecimal memory address.
name type address size
&hoge MyStruct 0x7fff5fbfef10 24
&hoge.ch char 0x7fff5fbfef10 1
gap1:7
&hoge.dn double 0x7fff5fbfef18 8
gap2:0
&hoge.sn short 0x7fff5fbfef20 2
gap3:2
&hoge.nu int 0x7fff5fbfef24 4
char + gap1 + double + gap2 + short + gap3 + int = 24
&hoge's decimal memory address (with alighnment gap size).
name type address size
&hoge MyStruct 140734799802128 24
&hoge.ch char 140734799802128 1
gap1:7
&hoge.dn double 140734799802136 8
gap2:0
&hoge.sn short 140734799802144 2
gap3:2
&hoge.nu int 140734799802148 4
こんな塩梅。
ワーニングは全部アドレスを無理矢理10進数で出したため。
「ポインタ本」と同じように構造体で確認した。
こうすれば普通の処理系では間に別の変数を入れたりする余地はあるまい:-)
で、確かにアドレスを次のメンバ変数のサイズで割り切れない場合、ギャップが出ている。
なるほど、こうすればどのCPUでもスマートに動くというわけですかい。
勉強になった。
著者/訳者:前橋 和弥
出版社:技術評論社( 2001-01 )
定価:¥ 2,394
Amazon価格:¥ 2,394
単行本 ( 323 ページ )
ISBN-10 : 4774111422
ISBN-13 : 9784774111421
著者/訳者:平山 尚(株式会社セガ)
出版社:秀和システム( 2008-11-15 )
定価:¥ 4,725
Amazon価格:¥ 4,725
単行本 ( 872 ページ )
ISBN-10 : 4798021180
ISBN-13 : 9784798021188
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」上のソースの写経。
何がやりたいかというと、タイトルバーの左端にあるアイコンをウィンドウ内に敷き詰めるというもの。
改造点はプログラムが立ち上がる際に、アイコンの左端の取得アイコン座標の情報を表示する点。


一生やってろと言いたくなるくらいのペースに苦笑い。
そんだけ。
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);
お粗末さまでした。
インライン関数について考える
一個前の投稿ではCの仮引数付きマクロについて取り上げたけど、本題はC++のインライン関数だったり。
仮引数付きマクロでは()で正しく演算順序を指定しないと問題が起きがちですよ、と。
で、CからインクリメントしたC++ではそれを一歩上行くインライン関数と言う物があり、より関数に近い形で置換できると言う優れもの。
使い方は以下の通り。
#include <iostream>
using namespace std;
#define PI 3.141592674
inline double area_of_circle(double r)
{
return r * r * PI;
}
int main()
{
double radius;
cout << "円の半径を入力して下さい。\n";
cin >> radius;
cout << "円の面積は" << area_of_circle(radius - 1.0 + 1.0) << "です。\n";
return 0;
}
実行結果は以下の通り
McLaren% ./inline_test 円の半径を入力して下さい。 4 円の面積は50.2655です。
18行目のように、わざと計算を挟んでみた。
仮引数付きマクロならば
radius - 1.0 + (1.0 * radius) - 1.0 + (1.0 * PI)
となるところを、
(radius - 1.0 + 1.0) * (radius - 1.0 + 1.0) * PI
と言うように、パーレンで囲わなくてもコンパイラが適宜最適化してくれると言う仕組み。
型の指定をしていることからわかる通り、型もチェックしてくれます、と。
これは便利。
ただ、独習C++によると
inline指定子は、コンパイラにとってはコマンドではなく要求であることを覚えておいてください。
とのこと。
どうやらループとか書いてあるとダメなコンパイラもあるらしい。
ただ、普及しているコンパイラはOKっぽい?
そもそも自分で明示的にインライン関数にするのは好ましくないから、コンパイラが自動的にインライン化する機能を有しているので、それに任せた方が良いんだとか。
ん〜、さすが後発言語。
何から何まで便利。
で、インライン化できなかった場合は普通の関数になるらしい。
ここで疑問になるのがプロトタイプ宣言の是非なんだけど、Wikipediaによれば
インライン関数はモジュール単位に定義する必要がある(通常の関数は1つのモジュールで定義すればよい)。これにより、モジュール単位に独立したコンパイルができるようになっている。
となる。
と言うことは、モジュール単位で書くのが当然で、外部から呼び出すのは無理?もしくはナンセンス?
一応ちょっと試したんだけど、ヘッダに書いたら読める。
ただ、別ソースファイルに書いたら読めなかった。
なるほど、独立しているとはそう言うことなのか??
一応動くソースは下記の通り。
(文字列を渡すところで警告あり。でも動く。)
inline_test_main.cpp
#include <iostream>
#include "inline.h"
using namespace std;
int main()
{
double radius;
echo("円の半径を入力して下さい。");
cin >> radius;
cout << "円の面積は" << area_of_circle(radius) << "です。\n";
return 0;
}
inline.h
#include <iostream>
using namespace std;
#define PI 3.141592674
void echo(char []);
inline double area_of_circle(double r)
{
return r * r * PI;
}
inline.cpp
#include "inline.h"
void echo(char str[])
{
cout << str << endl;
}
コンパイルと実行結果は下記のとおり。
McLaren% g++ -o inline inline_test_main.cpp inline.cpp inline_test_main.cpp: In function ‘int main()’: inline_test_main.cpp:9: 警告: deprecated conversion from string constant to ‘char*’ McLaren% ./inline 円の半径を入力して下さい。 3 円の面積は28.2743です。
そんだけ。
Cの仮引数付きマクロ(マクロ関数)について考える
今ちょうどCをまじめに勉強中。
何度目かの正直・・・。
で、仮引数付きマクロ、いわゆるマクロ関数について整理しておこうかなぁ、と。
マクロ関数の基本としては、
#include <stdio.h>
#define PI 3.141592674
#define circle(r) (r) * (r) * PI
#define echo(str) printf("%s", str)
main()
{
double radius; // 半径
char str[] = "半径を入力して下さい\n";
echo(str);
scanf("%lf", &radius);
printf("面積は%.2fです。\n", circle(radius));
return 0;
}
とすると、
McLaren% ./marco 半径を入力して下さい 2.1 面積は13.85です。
となる。
何をやっているかと言えば、定数PIは3.141592674と定義。
問題はcircle(r) (r) * (r) * PI。
ソース内でcircle(radius)と呼び出すとその部分が(radius) * (radius) * PIと置換される。
文字通り置換されるらしい。
そのタイミングはコンパイル時。
で、それを連発すると関数呼び出しによるオーバーヘッドが無い分高速化はされるけど、ソースと実行ファイルは肥大化しますよ、と。
それはカーニハン&リッチーのプログラミング言語C(以下、K&R)に書いてある通り。
(p.109 第2版)
で、なぜ(radius) * (radius) * PI と回りくどい書き方をしているかと言うと、それもK&Rに書いてある。
仮引数r がもしhoge – 100 とかだと、r * r * PI だとhoge – 100 * hoge – 100 * PIとなる。
もうちょっと詳しく書くとhoge – (100 * hoge) – (100 * PI) となってしまう。
だからこう書かないと、期待した値にならないよ、と。
実はこのコードの初版ではそのことに気がつかずに書いていたから、急いで直したのはここだけの話w
そうそう、K&Rでは一言も「マクロ関数」とは書いていない。
あくまで「仮引数付きマクロ」とのこと。
まぁ、実際問題関数じゃないからそこは意見が分かれることなんだろうけど、所詮ニュアンスの違いじゃないの?と思ってしまう今日この頃。
そんだけ。


