ゆなこんブログ

ゆなこんぴゅーたー (Yuna Computer) の公式ブログ

Windowsのシステムエラーコードからエラーメッセージを取得する方法

GetLastError() 関数から返されるシステムエラーコードに対応するエラーメッセージ文字列を取得するために、SystemMessageクラスを書きました。 定番の処理なので車輪の再発明なのは分かっていますが、新しい C++ で書きたいよね。string_view 大好き! ってなわけで、このSystemMessageクラスは単純で安全なリソース管理をします。

// SystemMessage.hpp
#ifndef SYSTEM_MESSAGE_HPP
#define SYSTEM_MESSAGE_HPP

#include <Windows.h>
#include <string_view>
#include <stdexcept>

template <typename = void>
class SystemMessageImpl {
    LPWSTR str = nullptr;
    std::size_t len = 0;

public:
    explicit SystemMessageImpl(DWORD dwMessageId, DWORD dwLanguageId = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)) {
        constexpr DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM;
        len = ::FormatMessageW(flags, nullptr, dwMessageId, dwLanguageId, reinterpret_cast<LPWSTR>(&str), 0, nullptr);
        if (!len) {
            throw std::runtime_error("エラーメッセージの取得に失敗しました。");
        }
    }

    ~SystemMessageImpl() {
        ::LocalFree(str);
    }

    SystemMessageImpl(const SystemMessageImpl&) = delete;
    SystemMessageImpl& operator=(const SystemMessageImpl&) = delete;
    SystemMessageImpl(SystemMessageImpl&&) = delete;
    SystemMessageImpl& operator=(SystemMessageImpl&&) = delete;

    std::wstring_view view() const {
        return std::wstring_view(str, len);
    }
};

using SystemMessage = SystemMessageImpl<>;

#endif // SYSTEM_MESSAGE_HPP

クラステンプレートとして実装されているので、ヘッダーオンリーのライブラリとして使えます。 これにより、複数の翻訳単位での #include ができますし、リンケージの問題を避けられます。たぶん。 また、エラーメッセージの取得に失敗した場合には例外を投げます。

Windowsのシステムエラーコードからエラーメッセージを取得する上でおそらく問題となるのは、訳が分からない FormatMessageW() / FormatMessage() の仕様と、FORMAT_MESSAGE_ALLOCATE_BUFFER の指定で確保されたバッファをどう管理するかだと思うのですが、その辺はクラスに閉じ込めてます。 本質となるエラーメッセージは、 view() メンバ関数呼び出して wstring_view として使ってねって設計です。

ムーブ周りまで = delete してますが、必要であれば正しく定義してください。 ここでは不要ですし、実装するのも面倒なので、楽に安全側に倒しています。

以下の例は、SystemMessageクラスを使って、現在のスレッドの最後のエラーコードに対応するメッセージを取得して表示する例です。 システムエラーメッセージを正しく表示するために setlocale() でロケールを日本語に設定しています。

#include <clocale>
#include <iostream>
#include "SystemMessage.hpp"

int main()
{
    std::setlocale(LC_CTYPE, "ja_JP.UTF-8");
    try {
        SystemMessage message{ ::GetLastError() };
        std::wcout << message.view();
    } catch (const std::runtime_error& e) {
        std::wcerr << L"エラーメッセージの取得に失敗: " << e.what() << std::endl;
    }
}

実行結果は以下の通り。GetLastError() が返す値によってメッセージが変わります。

この操作を正しく終了しました。