【问题标题】:GetFileAttributeW fails for non-ASCII charactersGetFileAttributeW 对非 ASCII 字符失败
【发布时间】:2018-04-08 07:26:23
【问题描述】:

所以我正在尝试检查给定文件是否存在。在this 回答之后,我尝试了GetFileAttributesW。它适用于任何 ascii 输入,但对于 ß、ü 和 á(以及我怀疑的任何其他非 ascii 字符)都失败了。我得到ERROR_FILE_NOT_FOUND 的文件名和ERROR_PATH_NOT_FOUND 的路径名,如果它们不存在的话。

我 100% 确定他们做到了。我花了 15 分钟来复制文件名以免打错字,并使用文字来避免任何错误输入。我找不到任何错误。

由于所有这些字符都是非 ascii 字符,我停止尝试,因为我怀疑我可能搞砸了编码。我就是看不出来。有什么我想念的吗?我链接到Kernel32.lib

谢谢!

#include <stdio.h>
#include <iostream>
#include <string>
#include "Windows.h"


void main(){
    while(true){
        std::wstring file_path;
        std::getline(std::wcin, file_path);

        DWORD dwAttrib = GetFileAttributesW(file_path.data());
        if(dwAttrib == INVALID_FILE_ATTRIBUTES){
            printf("error: %d\n", GetLastError());
            continue;
        }

        if(!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))
            printf("valid!\n");
        else
            printf("invalid!\n");
    }
}

【问题讨论】:

  • 使用文字来避免任何错误的输入。 -- 这是否意味着您在源代码中输入了字符?我认为这不是一个好主意,因为您不知道编译器对该字符文字做了什么。不如先写一个小的FindFirstFile / NextFile 程序,看看你会得到什么?然后获取返回的文件名并在该名称上调用GetFileAttributes
  • stdio.h 和朋友是旧的 C 兼容标头 - 改用 cstdio 和朋友。
  • 以这种方式尝试(使用常量 Unicode 字符串)DWORD dwAttrib = GetFileAttributesW( L"c:\\dir\\your_ß_file" );。如果有效,则问题出在wstring 转换或getline
  • @PaulMcKenzie “不知道编译器对那个字符文字做了什么”:当然,你知道。您为源代码使用您选择的特定字符编码,将其告知编译器,并告诉编译器将其转换为哪种编码。 (请参阅此answer。)即使您使用编译器的默认值,这也不是一个选项。
  • @PaulMcKenzie 我使用您建议的FindFirstFile / NextFile 方法为自己找到了有问题的文件名。应该/是“ß”的字符在输出中是“▀”(我希望它可以显示在网站上)这看起来不像是通用的“我不知道”,而是特定的unicode特点。如果我使用这个文件名作为程序的输入,一切正常。我仍然不知道为什么这是一个与预期不同的角色。

标签: c++ file wchar-t wstring kernel32


【解决方案1】:

要让 Unicode 在 Windows 上的控制台程序中正常工作非常困难,所以让我们从删除它的这方面开始(现在)。

修改您的程序,使其如下所示:

#include <cstdio>
#include <iostream>
#include <string>
#include "Windows.h"

int main() {
    std::wstring file_path = L"fooß.txt";

    DWORD dwAttrib = GetFileAttributesW(file_path.data());
    if (dwAttrib == INVALID_FILE_ATTRIBUTES)
        printf("error: %d\n", GetLastError());

    if (!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))
        printf("valid!\n");
    else
        printf("invalid!\n");

    return 0;
}

确保使用字节顺序标记 (BOM) 保存此文件,即使您使用的是 UTF-8。 Windows 应用程序,包括 Visual Studio 和编译器,可能对此非常挑剔。如果您的编辑器不这样做,请使用 Visual Studio 编辑文件,然后使用另存为,单击“保存”按钮旁边的向下箭头,选择“使用编码”。在“高级保存选项”对话框中,选择“Unicode (UTF-8 with signature) - Codepage 65001”。

确保当前文件夹中有一个名为 fooß.txt 的文件。我强烈建议使用 GUI 程序来创建此文件,例如记事本或资源管理器。

这个程序有效。如果您仍然收到文件未找到消息,请检查以确保临时文件位于工作目录中或更改程序以使用绝对路径。如果您使用绝对路径,请使用反斜杠并确保它们都正确转义。检查拼写错误、扩展名等。此代码确实有效。

现在,如果您从标准输入中获取文件名:

    std::wstring file_path;
    std::getline(std::wcin, file_path);

而你在控制台窗口中输入fooß.txt,你可能会发现它不起作用。如果你查看调试器,你会发现应该是ß 的字符是别的东西。对我来说,它是 á,但如果你的控制台代码页是别的东西,对你来说可能会有所不同。

ß 是 Unicode 中的 U+00DF。在 Windows 1252(美国 Windows 用户最常见的代码页)中,它是 0xDF,因此看起来似乎没有转换问题的可能性。但是控制台窗口(默认情况下)使用 OEM 代码页。在美国,常见的 OEM 代码页是 437。所以当我尝试在控制台中输入 ß 时,它实际上被编码为 0xE1。惊喜!这与á 的Unicode 值相同。如果你设法输入了一个值为 0xDF 的字符,你会看到它对应于你在原始问题中报告的块字符。

您会认为(嗯,会认为)从std::wcin 请求输入会进行任何必要的转换。但事实并非如此,并且可能有一些遗留的向后兼容性原因。您可以尝试使用“正确的”代码页来灌输流,但这会变得复杂,而且我从来没有费心尝试让它工作。我只是停止尝试在控制台上使用除 ASCII 以外的任何东西。

【讨论】:

  • 感谢您的回答!我用 BOM 保存了文件,它工作正常。我发现通过将-utf-8 标志传递给编译器可以获得相同的效果,这不需要我用BOM 再次保存每个文件。尽管这可能是可取的。控制台中没有 unicode 是一个不幸的限制,但这不是一个大问题,所以我可能会忽略它。我的程序在某个时候会有一个 GUI,所以我可以添加 unicode 支持。谢谢!
猜你喜欢
  • 2010-10-21
  • 2012-01-02
  • 2016-06-13
  • 2013-06-04
  • 1970-01-01
  • 2019-04-06
  • 1970-01-01
  • 1970-01-01
  • 2011-06-05
相关资源
最近更新 更多