山本ワールド
Windowsプログラミング
アルゴリズム Vitual C++ 2008/2013によるWin32/Win64 APIレベルのプログラム 基礎 Vitual C++ 2008/2013によるAPIレベルのプログラム(32/64bit) Wix3でインストーラーを作る Visual C++ 2008 Standard Editonによるフォームアプリケーションのプログラム(32/64bit) Vitual C++ 2008 Standard EditonによるAPIレベルのプログラム(32/64bit) Windows 7対応 Visual C++ 2008 ExpressによるAPIレベルのプログラム Visual C++ 2005 ExpressによるAPIレベルのプログラム Visual C++ Versiosn 5 BORLAND C++ Windowsプログラム全般 Excel VBA その他エラーメッセージを作成する3(FormatMessage 可変長引数 va_listで指定)
概要
APIで発生したエラーの種類はGetLastError APIの戻り値で確認できますが、これはエラーコードなので、エラー原因を調べたり、文字列化するのが面倒です。
APIの中に、エラーコードに対するメッセージを取得できる便利なFormatMessageがあります。
エラーコードを引数で指定してFormatMessage APIを呼び出すと、エラーの内容を示す日本語の文章が取得できます。
FormatMessage APIは、printf関数の様に書式化文字の指定によりDWORD型を10進文字列に変換して表示することも可能です。
ただし、エラーコードを日本語文章に変換することと書式化文字列の指定は同時に指定することはできません。
本プログラムは、動作確認のため存在しないファイルを開こうとするときに発生するエラーコードを、GetLastError APIの戻り値により取得し FormatMessage APIによりva_listを使用して文字列を作成し 表示するプログラムです。
FormatMessage APIを使用するverror_dialog関数は、printf関数の様に可変引数をサポートします。
エラーメッセージを作成する1(FormatMessage va_listで指定)や エラーメッセージを作成する2(FormatMessage va_listで指定)と異なりva_listを作成するためにソースコードで明示的にバッファを確保せず、可変長の引数の関数として定義することによりその関数を呼び出すことによりスタックに引数が積まれるので、va_startマクロにより簡単にva_listを作成できます。 以下に動作例を示します。
APIの中に、エラーコードに対するメッセージを取得できる便利なFormatMessageがあります。
エラーコードを引数で指定してFormatMessage APIを呼び出すと、エラーの内容を示す日本語の文章が取得できます。
FormatMessage APIは、printf関数の様に書式化文字の指定によりDWORD型を10進文字列に変換して表示することも可能です。
ただし、エラーコードを日本語文章に変換することと書式化文字列の指定は同時に指定することはできません。
本プログラムは、動作確認のため存在しないファイルを開こうとするときに発生するエラーコードを、GetLastError APIの戻り値により取得し FormatMessage APIによりva_listを使用して文字列を作成し 表示するプログラムです。
FormatMessage APIを使用するverror_dialog関数は、printf関数の様に可変引数をサポートします。
エラーメッセージを作成する1(FormatMessage va_listで指定)や エラーメッセージを作成する2(FormatMessage va_listで指定)と異なりva_listを作成するためにソースコードで明示的にバッファを確保せず、可変長の引数の関数として定義することによりその関数を呼び出すことによりスタックに引数が積まれるので、va_startマクロにより簡単にva_listを作成できます。 以下に動作例を示します。
テスト環境
コンパイラ
Visual C++ 2008 Standard 32/64bitVisual C++ 2013 Express 32/64bit
プロジェクトの作成
Win32プロジェクト Windowsアプリケーション実行環境
Windows 8.1 Enterprise 64bitWindows 7 EnterPrise Service Pack 1 64bit
Windows Vista Ultimate Service Pack 2 32bit
Windows XP Professional Service Pack 3 32bit
プログラムソースの概要
_tWinMain関数
_tfopen_s関数によって、存在しないファイルを読み取り属性で開こうとします。_tfopen_s関数がエラーを返すので、verror_dialog関数を呼び出しエラーコード等を表示します。
verror_dialog関数
GetLastError APIを呼び出し最後に発生したエラーコードを取得します。va_listとはprintf関数の様に可変長の引数を扱うときに使われる型です。
関数の引数を左から順番にメモリーの低いほうから高い方に向かって格納していきます。最初の引数へのポインタがva_listです。
注意しなければならないのは、32bitと64bitではアライメントが異なることです。 たとえばDWORD32は4byteですが、TCHAR*は32bitでは4byte、64bitでは8byteとなります。4byteの変数は4byteの境界、64bitでは8byteの変数は8byteの境界に置いてあるとVisual C++やAPIは解釈します。
APIを呼び出す側と呼び出される側でアライメントが異なると、メモリの内容の解釈が異なることとなります。アライメントが異なるとポインタを扱っている場合、 APIでそのポインタを使うときに意図しないアドレスをアクセスしエラー等が発生したり暴走したりすることもあります。
以下にva_listが示すアドレスが32bitの場合0x01000000、64bitの場合0x0000000001000000の時のDWORD32とTCHAR*を割り付けた場合のアライメントの例を示します。
上記の2つのプログラムはこの関数内で変数をメモリ上にアライメントを考慮して順番に格納してva_listを作成していましたが、 FormatMessage APIを呼び出す、関数の引数を可変長にしてしまえば、変数の格納を、引数をスタックに積んで関数を呼び出すという行為を コンパイラが作成してくれます。
そして、可変長になる前の部分の引数のアドレスから可変長になる部分のアドレスは計算できるので、va_listが作成できるわけです。
va_list型はchar*型でありメモリへのポインタです。va_startマクロは、可変長になる部分のアドレスをその前の引数から計算しva_listにそのポインタを格納するマクロです。
格納されたva_listをFormatMessage APIに渡せば、複数の変数の書式化が容易にできます。
FormatMessage APIによりユーザー定義文字列に従って、va_listに格納されている各ポインタを10進数文字列および文字列として解釈しエラーメッセージを作成します。
FormatMessage APIより作成された文字列を、MessageBox APIで表示します。
メッセージボックスが閉じられたら、FormatMessage APIにより動的に確保された文字列をLocalFree APIにより解放します。
FormatMessage
概要で触れたとおり、エラーコードの日本語文字列取得と、書式文字列による文字列の作成ができます。ここでは、第1引数でメッセージ定義文字列(FORMAT_MESSAGE_FROM_STRINGフラグ)の使用、 文字列に必要なメモリはFormatMessage APIにより確保する(FORMAT_MESSAGE_ALLOCATE_BUFFERフラグ)ように指定して呼び出します。
このAPIの引数は以下の通りです。
DWORD FormatMessage(
DWORD dwFlags, // 入力元と処理方法のオプション
LPCVOID lpSource, // メッセージの入力元
DWORD dwMessageId, // メッセージ識別子
DWORD dwLanguageId, // 言語識別子
LPTSTR lpBuffer, // メッセージバッファ
DWORD nSize, // メッセージバッファの最大サイズ
va_list *Arguments // 複数のメッセージ挿入シーケンスからなる配列
);
第2引数には、printfの書式指定文字列の様なものを指定できます。文字列には変数の値を書式化する書式指定子を含めることができます。
ポインタの配列の指定する要素を文字列化
書式%数値!書式指定子!
%に続いて数値を指定すると複数の変数へのポインタの配列のうち何個目の要素を適用するか指定することができます。%1を指定すると配列の0個目のポインタが適用されます。
書式指定子には、符号なし10進数に変換するu、文字列であるsが指定できます。!書式指定子!を指定しない場合は文字列が適用されます。
以下に書式指定の例を示します。
"%1!u!" // va_listの0個目の要素をDWORD32型へのポインタと解釈し10進文字列に変換する
"%2!s!" // va_listの1個目の要素を文字列へのポインタと解釈しポインタで示される文字列をメッセージに含める。
"%2" // va_listの1個目の要素を文字列へのポインタと解釈しポインタで示される文字列をメッセージに含める。
%%
%が文字が適用されます。%n
改行を指定します。プログラムソース
// メッセージ定義文字列とva_listを使用して可変引数のエラーメッセージ作成関数を作成
// Visual C++ 2008/2013 Express
#include <windows.h>
#include <stdio.h>
#include <stdarg.h>
#include <tchar.h>
// メッセージ定義文字列に従って引数を書式化しエラーメッセージを表示
void verror_dialog(HWND hWnd, TCHAR* form, ...);
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreInst, TCHAR* lpszCmdLine, int nCmdShow){
TCHAR* fname = _TEXT("この世に存在しない幽霊ファイル");
FILE* fp; // 存在しないファイルを開こうとしてエラーを発生させる。
if (_tfopen_s(&fp, fname, _TEXT("r"))){
verror_dialog(0, _TEXT("ファイル名 %1!s!"), fname);
}
return 0;
}
// メッセージ定義文字列に従って引数を書式化しエラーメッセージを表示
void verror_dialog(HWND hWnd, TCHAR* form, ...){
TCHAR* lpMsgBuf;
va_list arg;
va_start(arg, form);
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | // テキストのメモリ割り当てを要求する
FORMAT_MESSAGE_FROM_STRING, // 次の引数がメッセージ定義文字列であることを指定する
form, // メッセージ定義文字列
0, 0,
(LPTSTR)&lpMsgBuf, // メッセージテキストが保存されるバッファへのポインタ
0,
&arg); // va_listへのポインタ
va_end(arg);
MessageBox(hWnd, lpMsgBuf, _TEXT("エラー"), MB_OK | MB_ICONINFORMATION);
LocalFree(lpMsgBuf); // FormatMessage APIで確保されたメモリを解放する
}
ソースファイルと実行ファイルのダウンロード
Copyright (C) 2012 山本ワールド All Rights Reserved.