我不确定我的实现是否存在根本问题,或者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);
}
注意:
code
和language
是实例化列表中设置的类的成员(使用GetLastError
和LANGIDFROMLCID(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"
,char
,wchar_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
答案 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异常。