如何使用FormatMessage函数填充va_list以获取系统错误消息?

时间:2015-03-02 15:05:35

标签: c++ windows winapi visual-studio-2013 error-handling

我不确定我的实现是否存在根本问题,或者Windows API中存在关于格式化系统错误消息的错误。

我有FormatMessage函数的Windows API包装器方法。我的实现允许调用者传入其他参数以允许格式化系统错误消息。

方法签名如下:

bool setFormattedMessage(int arguments, ...)

在这个方法中,我有以下代码块似乎不适用于我的所有单元测试:

if (arguments)
{
    va_list argumentsList;
    va_start(argumentsList, arguments);

    size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM, NULL, code, language, (LPWSTR)&buffer, 0,
        &argumentsList);

    va_end(argumentsList);
}

注意: codelanguage是实例化列表中设置的类的成员(使用GetLastErrorLANGIDFROMLCID(GetThreadLocale())

以下是一些没有任何问题的单元测试:

如果我为错误代码34创建了一个类的实例,那么如果我将方法调用为test.setFormattedMessage(0),我将得到以下内容(请注意,这不会进入我发布的代码块,因为if (arguments)行:

  

驱动器中有错误的软盘。将%2(卷序列号:%3)插入驱动器%1.`

如果我将方法称为test.setFormattedMessage(3, L"C:", L"disk 2", L"ABC123"),我会得到以下内容:

  

驱动器中有错误的软盘。将磁盘2(卷序列号:ABC123)插入驱动器C:。;

但是,对于包含%hs%08lx等示例的错误消息,我在尝试格式化文本时遇到了问题。

以错误代码573为例。当将0的参数传递给方法时,我得到以下文本(如预期的那样):

  

{Missing System File}所需的系统文件%hs错误或丢失。

但是,如果我传入(1, "test")我最终会:

  

{Missing System File}所需的系统文件hs错误或丢失。

事实上,我发现无论我是否传递了"test"L"test"charwchar_t等的副本,我总是以错误结束上面的消息。创建的消息与传递(0)的消息相同,但从字符串中删除%除外。

我发现所有其他参数都存在同样的问题,除非它们已编号(例如%1)。关于我是否犯了一个基本错误,或FormatMessage函数确实存在缺陷,我完全陷入困境。

我的印象是,我可以像%hs一样替换swprintf占位符:

char * t = "file 123";
wchar_t a[2000];
int v = swprintf(a, 2000, L"Blah blah %hs", t);
std::wstring someText(a, v);
  

Blah blah file 123

2 个答案:

答案 0 :(得分:3)

如果您阅读FormatMessage() documentation,则明确说明:

  

安全备注

     

如果在没有FORMAT_MESSAGE_IGNORE_INSERTS的情况下调用此函数,则Arguments参数必须包含足以满足消息字符串中所有插入序列的参数,并且它们必须具有正确的类型。因此,不要使用启用了插入的不受信任或未知的消息字符串,因为它们可以包含比Arguments提供的插入序列更多的插入序列,或者可能包含错误类型的插入序列。 特别是,从API返回任意系统错误代码并使用FORMAT_MESSAGE_FROM_SYSTEM而不使用FORMAT_MESSAGE_IGNORE_INSERTS 是不安全的。

您的代码正在使用FORMAT_MESSAGE_FROM_SYSTEM,而不使用FORMAT_MESSAGE_IGNORE_INSERTS,这是不安全的。

所以,除非完全确定你知道任何给定系统错误代码的确切格式,并且你的代码在决定哪个格式之前检查了该错误代码要传递的参数,并且完全确定 Microsoft将永远不会在OS版本之间更改该错误消息的格式,那么您根本无法安全地使用带有系统错误的格式化参数,所以不要甚至尝试。格式化参数只有在通过FORMAT_MESSAGE_FROM_HMODULE使用自己的错误消息访问自己的EXE / DLL模块资源时才能安全使用,或者FORMAT_MESSAGE_FROM_STRING在内存中提供自己的错误消息字符串。

答案 1 :(得分:1)

根据David Heffernan的澄清(参见原始问题中的评论),可以确认FormatMessage函数不支持替换系统错误消息占位符的能力,除非它们已编号(%1 )。因此,%hs等占位符将变为hs

请注意,只有在不使用FORMAT_MESSAGE_IGNORE_INSERTS标志时才会出现上述情况。

我已使用此链接检查系统错误消息:

https://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx

此处列出的错误消息似乎不会混合占位符样式(错误消息将使用编号占位符或使用替代格式)。

对于编号的占位符,可以继续使用原始问题中描述的功能。对于使用备用样式的错误消息,可以使用vswprintf,但仅在使用FormatMessage标志调用FORMAT_MESSAGE_IGNORE_INSERTS函数(在填充的缓冲区上调用vswprintf之后),并记得重新初始化va_list。)

在支持国际化的过程中,整体方法可能存在缺陷。占位符的顺序可能因不同语言而异。

如果确实需要调用FormatMessage来检索系统错误消息而不使用FORMAT_MESSAGE_IGNORE_INSERTS标志,那么工作量会大幅增加。除了上面描述的内容之外,如果您的va_list与占位符的数量(以及可能的类型)不匹配,您还可以获得C0000005异常。