概要

APIで発生したエラーの種類はGetLastError APIの戻り値で確認できますが、これはエラーコードなので、エラー原因を調べたり、文字列化するのが面倒です。
APIの中に、エラーコードに対するメッセージを取得できる便利なFormatMessageがあります。
エラーコードを引数で指定してFormatMessage APIを呼び出すと、エラーの内容を示す日本語の文章が取得できます。
FormatMessage APIは、printf関数の様に書式化文字の指定によりDWORD型を10進文字列に変換して表示することも可能です。
ただし、エラーコードを日本語文章に変換することと書式化文字列の指定は同時に指定することはできません。
本プログラムは、動作確認のため存在しないファイルを開こうとするときに発生するエラーコードを、GetLastError APIの戻り値により取得し FormatMessage APIによりva_listを使用して文字列を作成し 表示するプログラムです。
va_listを作成するにあたりアライメントを考慮する必要があるます。本プログラムでは、アライメントを調整を自動化するため構造体を経由して引数を渡します。
以下に動作例を示します。

テスト環境

コンパイラ

Visual C++ 2008 Standard 32/64bit
Visual C++ 2013 Express 32/64bit

プロジェクトの作成

Win32プロジェクト Windowsアプリケーション

実行環境

Windows 8.1 Enterprise 64bit
Windows 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関数がエラーを返すので、error_dialog関数を呼び出しエラーコード等を表示します。

error_dialog関数

GetLastError APIを呼び出し最後に発生したエラーコードを取得します。
va_listを格納するためのバッファbufを定義します。アライメントを考慮してUINT64型の配列を使います。
va_listとはprintf関数の様に可変長の引数を扱うときに使われる型です。
通常はva_list型で受け取ってva_startとva_argを使用して1個ずつ引数を受け取る場合が多いですが、ここでは逆にva_listの中身を作成します。
関数の引数を左から順番にメモリーの低いほうから高い方に向かって格納していきます。最初の引数へのポインタが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*を割り付けた場合のアライメントの例を示します。 32bit 0x01000000 va_list 0x01000008 0x01000004 TCHAR* 0x01000000 DWORD32 64bit 0x0000000001000000 va_list 0x0000000001000010 0x0000000001000008 TCHAR* 0x0000000001000004 0x0000000001000000 DWORD32 SVGの代替画像
このプログラムではva_listの2個目の要素である、文字列へのポインタを代入する先を示すポインタをadj_pointerでアライメント調整を構造体を経由することによりコンパイラに任せています。
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 <tchar.h>

//      APIのエラーコードからエラーメッセージを作成しダイアログボックスに表示
void error_dialog(HWND hWnd);

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreInst, TCHAR* lpszCmdLine, int nCmdShow){
        FILE* fp;       //      存在しないファイルを開こうとしてエラーを発生させる。
        if (_tfopen_s(&fp, _TEXT("この世に存在しない幽霊ファイル"), _TEXT("r"))){
                error_dialog(0);
        }
        return 0;
}

//      va_listを使用
//      APIのエラーコードからエラーメッセージを作成しダイアログボックスに表示

struct ERR_MSG_STRUCT{
        DWORD errcode;
        TCHAR* msg;
};

void error_dialog(HWND hWnd){
        DWORD errorcode = GetLastError();       //      最後に発生したエラーコード
        LPVOID lpMsgBuf;

        ERR_MSG_STRUCT msg;     //      va_listを作成用の構造体
        msg.errcode = errorcode;
        msg.msg = _TEXT("エラーコード");
        va_list arg = (va_list)&msg;        //      va_listへ変換

        FormatMessage(
                FORMAT_MESSAGE_ALLOCATE_BUFFER |        //      テキストのメモリ割り当てを要求する
                FORMAT_MESSAGE_FROM_STRING,                     //      次の引数がメッセージ定義文字列であることを指定する
                _TEXT("Code %1!u! %2!s!"),                    //      メッセージ定義文字列
                0, 0,
                (LPTSTR)&lpMsgBuf,                                  //      メッセージテキストが保存されるバッファへのポインタ
                0,
                &arg);                                                              //      va_listへのポインタ

        MessageBox(hWnd, (LPCTSTR)lpMsgBuf, _TEXT("エラー"), MB_OK | MB_ICONINFORMATION);
        LocalFree(lpMsgBuf);    //      FormatMessage APIで確保されたメモリを解放する
}

ソースファイルと実行ファイルのダウンロード

ダウンロード errmsg4.zip(41.8kByte)
ZIPファイルに含まれるファイル
errmsg4.cpp
errmsg4.exe