GetLastError()によって返されたエラーコードからエラーメッセージを取得する方法


137

Windows API呼び出しの後、最後のエラーメッセージをテキスト形式で取得するにはどうすればよいですか?

GetLastError() テキストメッセージではなく整数値を返します。


Visual Studioのツールセクションでexeエラールックアップを使用して、デバッグのためにエラーからのメッセージのみが必要な場合に、これをうまく実行します。
ColdCat 2014年

@ColdCat:デバッグの場合は、@err,hrウォッチを追加するだけで、デバッガーに最後のエラーコードを人間が読める形式に自動的に変換させる方がはるかに簡単です。,hrフォーマット指定子は整数値に評価は、例えば、Aという任意の式のために働く5,hrの時計が表示されます「ERROR_ACCESS_DENIED:アクセスが拒否されました」
IInspectable 2017

2
GetLastError()ドキュメントから:「システムエラーコードのエラー文字列を取得するには、FormatMessage()関数を使用します。」MSDNの「最後のエラーコードの取得」の例を参照してください。
レミールボー2017年

回答:


145
//Returns the last Win32 error, in string format. Returns an empty string if there is no error.
std::string GetLastErrorAsString()
{
    //Get the error message, if any.
    DWORD errorMessageID = ::GetLastError();
    if(errorMessageID == 0)
        return std::string(); //No error message has been recorded

    LPSTR messageBuffer = nullptr;
    size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                 NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);

    std::string message(messageBuffer, size);

    //Free the buffer.
    LocalFree(messageBuffer);

    return message;
}

2
(LPSTR)&messageBufferこの場合、実際に渡す必要があると思います。それ以外の場合、FormatMessageAが割り当てられたバッファを指すようにその値を変更する方法はありません。
カイロタン2014年

2
ああ、すごい、それはちょっと変だ。ポインタをどのように変更しますか?しかし、それにポインターのアドレス(ポインターからポインター)を渡しますが、通常のポインターにキャストします... Win32の奇妙さ。ヘッドアップをありがとう、私自身のコードベースで修正しました(そして私の答え)。非常に微妙なキャッチ。
Jamin Grey 2014

1
ありがとうございました。この例は、MSDNの例よりもはるかに明確です。さらに、MSDNのものはコンパイルできませんでした。これにはstrsafe.h安全ではないヘッダーが含まれているため、winuser.hおよびで一連のコンパイラエラーが発生しwinbase.hます。
Hi-Angel

2
一部のエラーIDはサポートされていません。たとえば、0x2EE7、ERROR_INTERNET_NAME_NOT_RESOLVEDは、FormatMessage:0x13Dを呼び出すときに新しいエラーを引き起こします。システムは、%2のメッセージファイルでメッセージ番号0x%1のメッセージテキストを見つけることができません。
ブレント

3
この実装の問題:1 GetLastError呼び出しが遅すぎる可能性があります。2Unicodeサポートなし。3例外の安全性を保証せずに例外を使用する。
IInspectable 2017

65

一部のコメントを考慮して更新(2017年11月)。

簡単な例:

wchar_t buf[256];
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
               NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
               buf, (sizeof(buf) / sizeof(wchar_t)), NULL);

3
@ Hi-Angel-この例では、UNICODEを定義してコンパイルしていることを前提としています。「FormatMessage」は実際には、アプリケーションのコンパイル方法に応じて、Ansi / MBCS文字バッファーの場合は「FormatMessageA」、UTF16 / UNICODEバッファーの場合は「FormatMessageW」のいずれかに展開されるマクロです。上記の例を自由に編集して、出力バッファーのタイプ(wchar_t)に一致するバージョンを明示的に呼び出しました。
2015年

2
FORMAT_MESSAGE_IGNORE_INSERTSを追加:「フォーマット文字列を制御できない場合は、FORMAT_MESSAGE_IGNORE_INSERTSを渡して、%1が問題を引き起こさないようにする必要があります。」blogs.msdn.microsoft.com/oldnewthing/20071128-00/?p=24353
Roi Danton

1
この実装の問題:1重要なFORMAT_MESSAGE_IGNORE_INSERTSフラグを指定できない。2 GetLastError遅すぎる可能性があります。3メッセージの256コード単位への任意の制限。4エラー処理はありません。
IInspectable 2017

1
FormatMessageはバッファのサイズをバイト単位ではなくTCHAR単位で想定しているため、sizeof(buf)はARRAYSIZE(buf)である必要があります。
カイ

1
256代わりに使用できません(sizeof(buf) / sizeof(wchar_t)か?それは許容可能で安全ですか?
2018



14

GetLastErrorは数値エラーコードを返します。説明的なエラーメッセージを取得するには(たとえば、ユーザーに表示するため)、FormatMessageを呼び出すことができます。

// This functions fills a caller-defined character buffer (pBuffer)
// of max length (cchBufferLength) with the human-readable error message
// for a Win32 error code (dwErrorCode).
// 
// Returns TRUE if successful, or FALSE otherwise.
// If successful, pBuffer is guaranteed to be NUL-terminated.
// On failure, the contents of pBuffer are undefined.
BOOL GetErrorMessage(DWORD dwErrorCode, LPTSTR pBuffer, DWORD cchBufferLength)
{
    if (cchBufferLength == 0)
    {
        return FALSE;
    }

    DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                 NULL,  /* (not used with FORMAT_MESSAGE_FROM_SYSTEM) */
                                 dwErrorCode,
                                 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                 pBuffer,
                                 cchBufferLength,
                                 NULL);
    return (cchMsg > 0);
}

C ++では、std :: stringクラスを使用してインターフェースを大幅に簡略化できます。

#include <Windows.h>
#include <system_error>
#include <memory>
#include <string>
typedef std::basic_string<TCHAR> String;

String GetErrorMessage(DWORD dwErrorCode)
{
    LPTSTR psz{ nullptr };
    const DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
                                         | FORMAT_MESSAGE_IGNORE_INSERTS
                                         | FORMAT_MESSAGE_ALLOCATE_BUFFER,
                                       NULL, // (not used with FORMAT_MESSAGE_FROM_SYSTEM)
                                       dwErrorCode,
                                       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                       reinterpret_cast<LPTSTR>(&psz),
                                       0,
                                       NULL);
    if (cchMsg > 0)
    {
        // Assign buffer to smart pointer with custom deleter so that memory gets released
        // in case String's c'tor throws an exception.
        auto deleter = [](void* p) { ::LocalFree(p); };
        std::unique_ptr<TCHAR, decltype(deleter)> ptrBuffer(psz, deleter);
        return String(ptrBuffer.get(), cchMsg);
    }
    else
    {
        auto error_code{ ::GetLastError() };
        throw std::system_error( error_code, std::system_category(),
                                 "Failed to retrieve error message string.");
    }
}

注:これらの関数はHRESULT値に対しても機能します。最初のパラメーターをDWORD dwErrorCodeからHRESULT hResultに変更するだけです。残りのコードは変更されないままにすることができます。


これらの実装により、既存の回答に対して次の改善が行われます。

  • 呼び出すAPIへの参照だけでなく、完全なサンプルコード。
  • CとC ++の両方の実装を提供します。
  • UnicodeとMBCSの両方のプロジェクト設定で機能します。
  • エラーコードを入力パラメーターとして受け取ります。スレッドの最後のエラーコードは明確に定義されたポイントでのみ有効であるため、これは重要です。入力パラメーターにより、呼び出し元は文書化された契約に従うことができます。
  • 適切な例外安全性を実装します。暗黙的に例外を使用する他のすべてのソリューションとは異なり、この実装は、戻り値の構築中に例外がスローされた場合にメモリをリークしません。
  • FORMAT_MESSAGE_IGNORE_INSERTSフラグの適切な使用。詳細については、FORMAT_MESSAGE_IGNORE_INSERTSフラグの重要性を参照してください。
  • 他の回答のいくつかとは異なり、エラーを静かに無視する適切なエラー処理/エラー報告。


この回答は、Stack Overflowのドキュメントに組み込まれています。以下のユーザーがこの例に貢献しています:stackptrAjayCody Gray♦IInspectable


1
をスローする代わりに、失敗した呼び出しの後の戻り値がどこになるかstd::runtime_errorをスローstd::system_error(lastError, std::system_category(), "Failed to retrieve error message string.")するlastErrorことをお勧めGetLastError()FormatMessage()ます。
zett42

CバージョンをFormatMessage直接使用する利点は何ですか?
Micha Wiedenmann

@mic:それは尋ねるようなものです「Cの関数の利点は何ですか?」関数は抽象化を提供することで複雑さを軽減します。この場合、抽象化により、パラメーターの数が7から3に減り、互換性のあるフラグとパラメーターを渡すことにより、APIの文書化されたコントラクトが実装され、文書化されたエラー報告ロジックがエンコードされます。簡潔なインターフェースを提供し、セマンティクスを簡単に推測できるため、ドキュメントを読むために11分を費やす必要がありません。
IInspectable

私はこの答えが好きです。コードの正確さに細心の注意が払われていることは明らかです。2つの質問があります。1)ドキュメントが示唆しているのでは::HeapFree(::GetProcessHeap(), 0, p)なく、なぜ削除機能で使用するの::LocalFree(p)ですか?2)ドキュメントにこのようにすると書かれていることに気づきましたreinterpret_cast<LPTSTR>(&psz)が、厳密なエイリアス規則に違反していませんか?
Teh JoE

@teh:フィードバックありがとうございます。質問1):正直なところ、これが安全かどうかはわかりません。HeapFreeこれはまだSOドキュメントのトピックでしたが、誰に、またはなぜその呼び出しが導入されたのか思い出せません。私は調査する必要があります(ドキュメントは明確に見えますが、これは安全ではありません)。質問2):これは2つあります。MBCS構成の場合、はのLPTSTRエイリアスですchar*。そのキャストは常に安全です。ユニコード構成の場合、wchar_t*とにかくキャストするのは簡単ではありません。これがCで安全かどうかはわかりませんが、おそらくC ++ではそうではありません。私もこれを調査する必要があります。
IInspectable

13

一般に、FormatMessageWin32エラーコードをテキストに変換するには、を使用する必要があります。

MSDN ドキュメントから:

メッセージ文字列をフォーマットします。関数には、入力としてメッセージ定義が必要です。メッセージ定義は、関数に渡されたバッファから取得できます。ロード済みのモジュールのメッセージテーブルリソースから取得できます。または、呼び出し元は関数にシステムのメッセージテーブルリソースからメッセージ定義を検索するよう要求できます。関数は、メッセージ識別子と言語識別子に基づいて、メッセージテーブルリソース内のメッセージ定義を検索します。この関数は、フォーマットされたメッセージテキストを出力バッファにコピーし、必要に応じて埋め込み挿入シーケンスを処理します。

FormatMessageの宣言:

DWORD WINAPI FormatMessage(
  __in      DWORD dwFlags,
  __in_opt  LPCVOID lpSource,
  __in      DWORD dwMessageId, // your error code
  __in      DWORD dwLanguageId,
  __out     LPTSTR lpBuffer,
  __in      DWORD nSize,
  __in_opt  va_list *Arguments
);

7

C#を使用している場合は、次のコードを使用できます。

using System.Runtime.InteropServices;

public static class WinErrors
{
    #region definitions
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr LocalFree(IntPtr hMem);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern int FormatMessage(FormatMessageFlags dwFlags, IntPtr lpSource, uint dwMessageId, uint dwLanguageId, ref IntPtr lpBuffer, uint nSize, IntPtr Arguments);

    [Flags]
    private enum FormatMessageFlags : uint
    {
        FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100,
        FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200,
        FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000,
        FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000,
        FORMAT_MESSAGE_FROM_HMODULE = 0x00000800,
        FORMAT_MESSAGE_FROM_STRING = 0x00000400,
    }
    #endregion

    /// <summary>
    /// Gets a user friendly string message for a system error code
    /// </summary>
    /// <param name="errorCode">System error code</param>
    /// <returns>Error string</returns>
    public static string GetSystemMessage(int errorCode)
    {
        try
        {
            IntPtr lpMsgBuf = IntPtr.Zero;

            int dwChars = FormatMessage(
                FormatMessageFlags.FORMAT_MESSAGE_ALLOCATE_BUFFER | FormatMessageFlags.FORMAT_MESSAGE_FROM_SYSTEM | FormatMessageFlags.FORMAT_MESSAGE_IGNORE_INSERTS,
                IntPtr.Zero,
                (uint) errorCode,
                0, // Default language
                ref lpMsgBuf,
                0,
                IntPtr.Zero);
            if (dwChars == 0)
            {
                // Handle the error.
                int le = Marshal.GetLastWin32Error();
                return "Unable to get error code string from System - Error " + le.ToString();
            }

            string sRet = Marshal.PtrToStringAnsi(lpMsgBuf);

            // Free the buffer.
            lpMsgBuf = LocalFree(lpMsgBuf);
            return sRet;
        }
        catch (Exception e)
        {
            return "Unable to get error code string from System -> " + e.ToString();
        }
    }
}

このコードが機能することを確認しました。TSはこの回答を受け入れるべきではありませんか?
swdev 2014

2
さらにスローする必要がある場合は、Win32Exceptionを使用してC#で行う簡単な方法があります
SerG

2
@swdev:cまたはc ++のタグが付いた質問に対する回答をC#で受け入れる必要があるのはなぜですか?現状では、この答えは、尋ねられている質問にさえ対処していません。
IInspectable 2017

1
まあ、私はこの提案された答えに投票したことを覚えていません。@swdevのロジックの明らかな欠陥に対処しました。しかし、あなたは私を信じようとしないので、私はあなたにそれを証明するつもりです:ここで、別の反対票を持ってください。これは私からのものです。この答えは、別の質問では役立つかもしれませんが、尋ねられた質問では単に役に立たないからです。
IInspectable 2017

2
「私はあなたが明白なものを超えて提供するのに役立つ洞察を持っていると思います」 - 確かに、私は持っています。
IInspectable 2017

4

MBCSとUnicodeをサポートする必要がある場合、Cr.C64の答えは十分ではありません。バッファはTCHARとして宣言され、LPTSTRにキャストされる必要があります。このコードは、Microsoftがエラーメッセージに追加する迷惑な改行を処理しないことに注意してください。

CString FormatErrorMessage(DWORD ErrorCode)
{
    TCHAR   *pMsgBuf = NULL;
    DWORD   nMsgLen = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, ErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        reinterpret_cast<LPTSTR>(&pMsgBuf), 0, NULL);
    if (!nMsgLen)
        return _T("FormatMessage fail");
    CString sMsg(pMsgBuf, nMsgLen);
    LocalFree(pMsgBuf);
    return sMsg;
}

また、簡潔にするために、次の方法が役立つと思います。

CString GetLastErrorString()
{
    return FormatErrorMessage(GetLastError());
}

CStringc'torが例外をスローした場合、この実装はへの呼び出しによって割り当てられたメモリをリークしますFormatMessage
IInspectable 2017

確かに、しかし私は長年このコードを使用しており、それが問題になることは一度もありません。CStringのctorがスローする可能性が高い唯一のケースは、メモリの割り当ての失敗であり、ほとんどのMFCコード(Microsoftが提供するものを含む)は、メモリ不足の状態を希望どおりに適切に処理しません。幸いなことに、ほとんどのPCには非常に多くのメモリが搭載されているため、すべてを使用するにはかなりの努力が必要です。一時的なCStringインスタンスを生成する使用法(CStringを返すことを含む)は、このリスクを実行します。これはリスクと利便性のトレードオフです。また、メッセージハンドラで発生した場合、例外がキャッチされます。
victimofleisure

このコメントのほとんどは間違っています。「それは私には決して起こらなかった」というのは、特にコードがどのように失敗するかを知っている場合は、特に気にする必要がある弱点です。メモリの量は、プロセスに割り当てられた使用可能なアドレス空間にも影響を与えません。RAMは単なるパフォーマンス最適化です。コードがNRVOを許可するように記述されている場合、コピーエリクションは一時的な割り当てを防ぎます。例外がメッセージハンドラーでキャッチされるかどうかは、プロセスのビット数と外部設定によって異なります。リスク管理によって不便が生じないことを示す回答を提出しました。
IInspectable 2017

その上、一時ファイルを作成する際にメモリ不足になるリスクは疑わしいものです。その時点ですべてのリソースが解放され、悪いことは何も起こりません。
IInspectable 2017

1
いいえ、コードが役に立たないのは残念です。しかし、TBHは私の問題のリストではかなり低いです。
–victimofleisure

3
void WinErrorCodeToString(DWORD ErrorCode, string& Message)
{
char* locbuffer = NULL;
DWORD count = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, ErrorCode,
    0, (LPSTR)&locbuffer, 0, nullptr);
if (locbuffer)
{
    if (count)
    {
        int c;
        int back = 0;
        //
        // strip any trailing "\r\n"s and replace by a single "\n"
        //
        while (((c = *CharPrevA(locbuffer, locbuffer + count)) == '\r') ||
            (c == '\n')) {
            count--;
            back++;
        }

        if (back) {
            locbuffer[count++] = '\n';
            locbuffer[count] = '\0';
        }

        Message = "Error: ";
        Message += locbuffer;
    }
    LocalFree(locbuffer);
}
else
{
    Message = "Unknown error code: " + to_string(ErrorCode);
}
}

説明も追加していただけますか?
ロバート

1
この実装の問題:1Unicodeサポートなし。2エラーメッセージの不適切なフォーマット。呼び出し元が返された文字列を処理する必要がある場合、それを行うことができます。実装では、呼び出し元にオプションがありません。3例外の使用が適切な例外の安全性の欠如。std::stringオペレーターが例外をスローする場合、によって割り当てられたバッファーFormatMessageがリークされます。呼び出し側に参照によってオブジェクトを渡す代わりに、4単にを返さないのはなぜstd::stringですか?
IInspectable 2017

1

c ++ 11以降、次の代わりに標準ライブラリを使用できますFormatMessage

#include <system_error>

std::string GetLastErrorAsString(){
    DWORD errorMessageID = ::GetLastError();
    if (errorMessageID == 0) {
        return std::string(); //No error message has been recorded
    } else {
        return std::system_category().message(errorMessageID);
    }
}

呼び出しGetLastErrorが意味のある結果を生成する小さなウィンドウしかありません。これはC ++なので、ここでの唯一の安全なオプションは、呼び出し元に最後のエラーコードを提供させることです。表示されたコードがGetLastError 2回呼び出すことは、確かに役に立ちません。また、便利ですが、C ++に固有のワイド文字サポートがないため、error_categoryインターフェースが普遍的に使用できません。これは、C ++の機会を逃した長い歴史に追加されます。
IInspectable

への無用な呼び出しについての良い点GetLastError。しかし、私はGetLastErrorここに電話をかけることと、発信者に電話をかけることとの間に違いはありません。C ++とwcharについて:希望を放棄しないでください。MicrosoftはアプリがUTF-8のみであること許可し始めています
クロニアル

「違いはない」 -次の呼び出しサイトを検討してくださいlog_error("error", GetLastErrorAsString());。また、log_error最初の引数が型であることを考慮してくださいstd::string。(非表示の)変換のc'tor呼び出しは、呼び出しをGetLastError行った時点から意味のある値をキャプチャするという保証を取りやめました。
IInspectable

std :: stringコンストラクターがWin32関数を呼び出すと思うのはなぜですか?
クロニアル

1
mallocHeapAllocプロセスヒープを要求します。プロセスヒープは拡張可能です。大きくする必要がある場合は、最終的にVirtualAllocを呼び出します。これにより、呼び出しスレッドの最後のエラーコード設定されます。今、それはポイントを完全に欠いています:C ++は地雷原です。この実装はそれに追加するだけであり、暗黙の保証を備えたインターフェースを提供することにより、それは単純に生き残ることができません。問題がないと思われる場合は、提案されたソリューションの正確性を簡単に証明できます。幸運を。
IInspectable

0

これは後で使用する必要があるため、ここに残します。これは、アセンブリ、CおよびC ++で同等に機能する小さなバイナリ互換ツールのソースです。

GetErrorMessageLib.c(GetErrorMessageLib.dllにコンパイル)

#include <Windows.h>

/***
 * returns 0 if there was enough space, size of buffer in bytes needed
 * to fit the result, if there wasn't enough space. -1 on error.
 */
__declspec(dllexport)
int GetErrorMessageA(DWORD dwErrorCode, LPSTR lpResult, DWORD dwBytes)
{    
    LPSTR tmp;
    DWORD result_len;

    result_len = FormatMessageA (
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL,
        dwErrorCode,
        LANG_SYSTEM_DEFAULT,
        (LPSTR)&tmp,
        0,
        NULL
    );        

    if (result_len == 0) {
        return -1;
    }

    // FormatMessage's return is 1 character too short.
    ++result_len;

    strncpy(lpResult, tmp, dwBytes);

    lpResult[dwBytes - 1] = 0;
    LocalFree((HLOCAL)tmp);

    if (result_len <= dwBytes) {
        return 0;
    } else {
        return result_len;
    }
}

/***
 * returns 0 if there was enough space, size of buffer in bytes needed
 * to fit the result, if there wasn't enough space. -1 on error.
 */
__declspec(dllexport)
int GetErrorMessageW(DWORD dwErrorCode, LPWSTR lpResult, DWORD dwBytes)
{   
    LPWSTR tmp;
    DWORD nchars;
    DWORD result_bytes;

    nchars = dwBytes >> 1;

    result_bytes = 2 * FormatMessageW (
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL,
        dwErrorCode,
        LANG_SYSTEM_DEFAULT,
        (LPWSTR)&tmp,
        0,
        NULL
    );    

    if (result_bytes == 0) {
        return -1;
    } 

    // FormatMessage's return is 1 character too short.
    result_bytes += 2;

    wcsncpy(lpResult, tmp, nchars);
    lpResult[nchars - 1] = 0;
    LocalFree((HLOCAL)tmp);

    if (result_bytes <= dwBytes) {
        return 0;
    } else {
        return result_bytes * 2;
    }
}

インラインバージョン(GetErrorMessage.h):

#ifndef GetErrorMessage_H 
#define GetErrorMessage_H 
#include <Windows.h>    

/***
 * returns 0 if there was enough space, size of buffer in bytes needed
 * to fit the result, if there wasn't enough space. -1 on error.
 */
static inline int GetErrorMessageA(DWORD dwErrorCode, LPSTR lpResult, DWORD dwBytes)
{    
    LPSTR tmp;
    DWORD result_len;

    result_len = FormatMessageA (
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL,
        dwErrorCode,
        LANG_SYSTEM_DEFAULT,
        (LPSTR)&tmp,
        0,
        NULL
    );        

    if (result_len == 0) {
        return -1;
    }

    // FormatMessage's return is 1 character too short.
    ++result_len;

    strncpy(lpResult, tmp, dwBytes);

    lpResult[dwBytes - 1] = 0;
    LocalFree((HLOCAL)tmp);

    if (result_len <= dwBytes) {
        return 0;
    } else {
        return result_len;
    }
}

/***
 * returns 0 if there was enough space, size of buffer in bytes needed
 * to fit the result, if there wasn't enough space. -1 on error.
 */
static inline int GetErrorMessageW(DWORD dwErrorCode, LPWSTR lpResult, DWORD dwBytes)
{   
    LPWSTR tmp;
    DWORD nchars;
    DWORD result_bytes;

    nchars = dwBytes >> 1;

    result_bytes = 2 * FormatMessageW (
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL,
        dwErrorCode,
        LANG_SYSTEM_DEFAULT,
        (LPWSTR)&tmp,
        0,
        NULL
    );    

    if (result_bytes == 0) {
        return -1;
    } 

    // FormatMessage's return is 1 character too short.
    result_bytes += 2;

    wcsncpy(lpResult, tmp, nchars);
    lpResult[nchars - 1] = 0;
    LocalFree((HLOCAL)tmp);

    if (result_bytes <= dwBytes) {
        return 0;
    } else {
        return result_bytes * 2;
    }
}

#endif /* GetErrorMessage_H */

動的なユースケース(エラーコードが有効であると想定。それ以外の場合は-1のチェックが必要):

#include <Windows.h>
#include <Winbase.h>
#include <assert.h>
#include <stdio.h>

int main(int argc, char **argv)
{   
    int (*GetErrorMessageA)(DWORD, LPSTR, DWORD);
    int (*GetErrorMessageW)(DWORD, LPWSTR, DWORD);
    char result1[260];
    wchar_t result2[260];

    assert(LoadLibraryA("GetErrorMessageLib.dll"));

    GetErrorMessageA = (int (*)(DWORD, LPSTR, DWORD))GetProcAddress (
        GetModuleHandle("GetErrorMessageLib.dll"),
        "GetErrorMessageA"
    );        
    GetErrorMessageW = (int (*)(DWORD, LPWSTR, DWORD))GetProcAddress (
        GetModuleHandle("GetErrorMessageLib.dll"),
        "GetErrorMessageW"
    );        

    GetErrorMessageA(33, result1, sizeof(result1));
    GetErrorMessageW(33, result2, sizeof(result2));

    puts(result1);
    _putws(result2);

    return 0;
}

通常の使用例(エラーコードが有効であると想定します。それ以外の場合は、-1の戻りチェックが必要です):

#include <stdio.h>
#include "GetErrorMessage.h"
#include <stdio.h>

int main(int argc, char **argv)
{
    char result1[260];
    wchar_t result2[260];

    GetErrorMessageA(33, result1, sizeof(result1));
    puts(result1);

    GetErrorMessageW(33, result2, sizeof(result2));
    _putws(result2);

    return 0;
}

MinGW32のようにアセンブリgnuで使用する例(ここでも、エラーコードは有効であると想定しています。それ以外の場合は、-1のチェックが必要です)。

    .global _WinMain@16

    .section .text
_WinMain@16:
    // eax = LoadLibraryA("GetErrorMessageLib.dll")
    push $sz0
    call _LoadLibraryA@4 // stdcall, no cleanup needed

    // eax = GetProcAddress(eax, "GetErrorMessageW")
    push $sz1
    push %eax
    call _GetProcAddress@8 // stdcall, no cleanup needed

    // (*eax)(errorCode, szErrorMessage)
    push $200
    push $szErrorMessage
    push errorCode       
    call *%eax // cdecl, cleanup needed
    add $12, %esp

    push $szErrorMessage
    call __putws // cdecl, cleanup needed
    add $4, %esp

    ret $16

    .section .rodata
sz0: .asciz "GetErrorMessageLib.dll"    
sz1: .asciz "GetErrorMessageW"
errorCode: .long 33

    .section .data
szErrorMessage: .space 200

結果: The process cannot access the file because another process has locked a portion of the file.


1
これは本当に何も有用なものにはなりません。その上、FormatMessage明らかな理由もなくANSIバージョンのを呼び出し、理由を問わず、任意に80文字に制限しています。残念ながら、これは役に立ちません。
IInspectable 2017

Unicodeバージョンの欠如について誰も気付かないことを望んでいました。としてgnuでUnicode文字列を定義する方法を確認し、私のソリューションを変更します。不正をごめんなさい。
ドミトリー

わかりましたunicodeバージョンがアップしています。そしてそれは恣意的な理由ではありません。すべてのエラーメッセージは80文字未満であるか、読む価値がないかのいずれかであり、エラーコードはエラーメッセージよりも重要です。80文字を超える標準エラーメッセージはないため、これは安全な前提であり、そうでない場合はメモリリークは発生しません。
ドミトリー

3
「すべてのエラーメッセージは80文字未満であるか、読む価値がない」 -それは誤りです。ERROR_LOCK_VIOLATION(33)のエラーメッセージは次のとおりです。「別のプロセスがファイルの一部をロックしているため、プロセスはファイルにアクセスできません。」問題を解決しようとして診断ログファイルでこれを見つけた場合、どちらも明らかに80文字より長く、非常に読む価値があります。この回答は、既存の回答に実質的な価値を追加するものではありません。
IInspectable 2017

これも同じようにナイーブです。失敗する頻度は低くなります。バッファーサイズに依存するアセンブリの実装を除きます(100のスペースしかない場合、200文字のスペースがあると主張します)。繰り返しますが、これは実質的なものを追加するものではありません。それは、他のどの回答にもまだ含まれていません。特に、この回答よりも悪いです。そこで箇条書きのリストを参照し、先ほど指摘したものに加えて、提案された実装にどのような問題があるかを観察してください。
IInspectable 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.