Безопасный / гибкий фасад для Windows FormatMessage

Мне нужно использовать FormatMessage() для проекта, но мне не нравится его страшный интерфейс. Кто-нибудь знает о фасаде, который убирает его, но в то же время учитывает параметры замены?

Я только что прочитал вторую часть введения в FastFormat и собираюсь написать расширение для FormatMessage() (или спросить команду проекта FastFormat, есть ли у него такое в работе), но я стремлюсь получить что-то как можно скорее, поэтому если есть что-то еще приличное, я бы, вероятно, взялся за это.

Я хочу иметь возможность писать такой код:

HINSTANCE netevent = ::LoadLibrary("netevent.dll");
std::string msg = LookupError(netevent, EVENT_SERVICE_START_FAILED_II,
    "child-svr", "parent-svr", "ship happens");
::puts(msg.c_str());

Который дал бы результат:

The child-svr service depends on the parent-svr service which failed to start be cause of the following error:
ship happens

Текущая оболочка, которую я создал, имеет интерфейс:

std::string LookupError(HINSTANCE hinst, DWORD id, ...);

Есть две проблемы с этим:

  • Это не безопасно для типов, так как легко передать любой тип - int, std::string, void* - это не const char*
  • Несоответствие количества аргументов с количеством, требуемым для строки формата, представляющей ошибку, легко

Учитывая возможности FastFormat с точки зрения безопасности типов, я хочу знать, есть ли способ следовать его механизмам для работы с FormatMessage().

2 ответа

Поскольку число параметров, которые должны быть вставлены в строку формата, не может быть проверено компилятором, невозможно сделать это действительно безопасным во время компиляции.

Вы можете получить большую часть пути, просто имея несколько перегрузок для различного числа вставленных параметров, а затем указав вставленные значения с чем-то гибким, как boost::any, Таким образом, перегрузка для двух параметров будет:

std::string FormatMessage(HINSTANCE hinst, DWORD id, const boost::any &arg1, const boost::any &arg2);

Когда вы получаете значение из arg1, boost выдаст, если вы попытаетесь получить неправильный тип, поэтому вам просто нужно проверить строку формата и попытаться получить требуемый тип из каждого аргумента.

В качестве альтернативы вы можете использовать шаблоны и std::ostringstream (или boost::lexical_cast) для очень гибкой версии; снова будут перегрузки, позволяющие варьировать число аргументов, так что вот версия с одним аргументом:

template <class TArg1>
std::string FormatMessage(HINSTANCE hinst, DWORD id, const TArg1 &arg1)
{
    std::ostringstream arg1Stream;
    arg1Stream << arg1;
    std::string arg1String = arg1Stream.str();

    DWORD_PTR argArray = reinterpret_cast<DWORD_PTR>(arg1String.c_str());

    // ... etc
}

Таким образом, вы можете получить строку из каждого аргумента, если передаваемый тип может быть передан в потоковом режиме, и больше ничего не требуется, если строки формата ожидают вставки только строк.

Библиотека формата C++ позволяет форматировать собственные сообщения об ошибках Windows, соответствующие кодам ошибок, возвращаемым GetLastError() и сообщения об ошибках POSIX, соответствующие ошибкам, данным errno, Например:

// This throws a WindowsError with the description
//   cannot open file 'madeup': The system cannot find the file specified.
// or similar (system message may vary).
const char *filename = "madeup";
LPOFSTRUCT of = LPOFSTRUCT();
HFILE file = OpenFile(filename, &of, OF_READ);
if (file == HFILE_ERROR)
  throw fmt::WindowsError(GetLastError(), "cannot open file '{}'", filename);

Сообщение об ошибке Windows получено с помощью FormatMessage API-функция. Вы также можете отформатировать сообщение об ошибке с fmt::format_windows_error который не бросает исключения. См. Системные ошибки для более подробной информации.

Отказ от ответственности: я автор формата C++

Другие вопросы по тегам