Безопасный / гибкий фасад для 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++