CString 赋值正在改变 GetLastError() 的值

Posted

技术标签:

【中文标题】CString 赋值正在改变 GetLastError() 的值【英文标题】:CString assignment is changing value of GetLastError() 【发布时间】:2018-06-11 19:36:15 【问题描述】:

我正在使用 MFC CString 对象作为字符串。我遇到了一个问题,即设置 CString 会导致 GetLastError() 被意外设置为某个值。

以下是问题的简化版本:

CString csTest;
DWORD dwLastError = 0;

SetLastError(0);
csTest = _T("test");
dwLastError = GetLastError();  // dwLastError is still 0, as expected
csTest = "another test";  // Not using TCHAR
dwLastError = GetLastError(); // dwLastError now set to 122, "The data area passed to a system call is too small."

我可以使用 Visual Studio 2015 重现此问题,创建一个全新的 MFC 项目(基于对话框),并将此代码添加到它创建的对话框的 OnInitDialog() 函数中。

我知道第一个字符串是TCHAR,在此项目中默认为WCHAR。因此,char*CString 赋值运算符似乎导致了问题。

有没有办法解决这个问题?一种获取编译器警告以通知我char* 分配而不是WCHAR* 的方法?我认为它曾经在 Visual Studio 的某些早期版本中为此提供警告/错误,但现在似乎没有,所以也许我记错了。

【问题讨论】:

任何api函数调用都可以改变GetLastError的值。有什么问题? GetLastError() 与其他语言中的类似机制不同,它不是针对代码中每个潜在错误的包罗万象的函数。 @IInspectable 在下面的答案中更详细地介绍了他们的答案,但基本上你应该只在文档说你应该使用它时以及在你正在检查它之后立即使用它时使用 GetLastError() 作为验证机制。其他任何东西,并且您在 C++ 中没有正确使用它。 【参考方案1】:

您可以通过将宏 _CSTRING_DISABLE_NARROW_WIDE_CONVERSION 定义为 documented 来禁用隐式窄幅转换。

但这只是帮助您解决真正的问题:您只是调用GetLastError 太晚了。它的返回值只有在您调用另一个调用之前才有效。只是不要那样做。

现在,即使您成功阻止了任何转换,CString 实现仍然必须分配内存。当你运气不好时,operator new(调用malloc)将不得不使用操作系统的内存管理功能。

解决方案很简单:仅在文档告诉您它将返回有效值时调用 GetLastError,并且不要散布对 anyany 调用功能。

【讨论】:

【参考方案2】:

将 ANSI 分配给 CStringW 时,会调用 WinAPI MultiByteToWideChar。它有点类似于以下内容:

csTest = "a"; // <- step in to this line with debugger
->
wchar_t *buf = new wchar_t[10];

MultiByteToWideChar(CP_ACP, 0, "a", -1, buf, 1); 
//Error 122, ERROR_INSUFFICIENT_BUFFER
//MultiByteToWideChar is expecting 2, not 1, for len

DWORD err = GetLastError();
delete[]buf;

出现问题是因为CString 使用了"a" 的长度,并且没有考虑空字符。 MultiByteToWideChar 设置错误。

CString 稍后修复了该问题,但GetLastError 仍然设置。

只需使用csTest = L"a"; 即可避免转换的需要。或csTest = CA2W("a");

否则GetLastError 不应该以这种方式使用。在 WinAPI 函数失败后立即使用GetLastError

测试:

#include <Windows.h>
#include <AtlStr.h>

int main()

    DWORD err;
    CStringW str;

    wchar_t *buf = new wchar_t[10];
    int len = MultiByteToWideChar(CP_ACP, 0, "a", 1, NULL, 0);
    MultiByteToWideChar(CP_ACP, 0, "a", -1, buf, len);
    err = GetLastError();
    printf("MultiByteToWideChar error %d\n", err);
    delete[]buf;

    //Below is wrong usage of GetLastError(), it's only for this discussion...
    SetLastError(0);
    str = "a";
    err = GetLastError();
    printf("assignment operator error %d\n", err);

    SetLastError(0);
    str = CA2W("12345");
    err = GetLastError();
    printf("CA2W error %d\n", err); 

    return 0;

输出:

MultiByteToWideChar error 122
assignment operator error 122
CA2W error 0

【讨论】:

CA2W 仍将执​​行转换。没有任何收获。 @IInspectable csTest = CA2W("a") 不会影响GetLastError,它会立即为空字符增加空间。不像csTest = "a" 两者都不安全。这不像operator new 凭空创造记忆。在某一时刻,它会影响操作系统的内存管理功能。在其记录的规范之外使用GetLastError 根本无法想象。 @IInspectable 这基本上是CStringW 的复制构造函数和赋值运算符的问题,它们不能立即考虑空字符。这不是因为内存不足。 CStringW str = L"a"CStringA = "a" 也会调用 new...csTest = CA2W("a") 不会以同样的方式影响 GetLastError 你错了。 operator new 设置最后一个错误代码,如果堆已耗尽,并且需要从操作系统获取新内存。您不能在其记录的规范之外安全地调用GetLastError就是那么简单。

以上是关于CString 赋值正在改变 GetLastError() 的值的主要内容,如果未能解决你的问题,请参考以下文章

CString 赋值的区别

关于CString之间赋值的问题

C++问题,怎么把char数组以ASCII码赋值给CString

为啥CString变量无法赋值?

CString之间为啥不能互相赋值

char* 给CString 赋值。