【问题标题】:ReadFile Function doesn't Read Files Correctly - Win32 APIReadFile 函数无法正确读取文件 - Win32 API
【发布时间】:2021-01-30 00:11:29
【问题描述】:

作为我学习 Win32 的一部分,我创建了一个简单的程序,它写入一个名为 file1.txt 的现有文件,然后从该文件读取缓冲区并在控制台上显示缓冲区。文件内容以Hello 开头。该问题将在代码之后讨论。这是代码(在 Visual Studio 2017 上用 C++/Win32 编写):

#include <iostream>
#include <tchar.h>
#include <Windows.h>

#define UNICODE
#define _UNICODE

using namespace std;

int _tmain(int _argc, TCHAR *_argv[])
{
    SECURITY_ATTRIBUTES sa;
    ZeroMemory(&sa, sizeof(sa));
    sa.nLength = sizeof(sa);
    sa.bInheritHandle = false;
    sa.lpSecurityDescriptor = NULL;

    HANDLE hFile = CreateFile(L"file1.txt", GENERIC_READ | GENERIC_WRITE, 0, &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    LARGE_INTEGER size, physize;
    DWORD fptr = 0;
    BYTE buff[] = " World";

    if (hFile == INVALID_HANDLE_VALUE)
    {
        cout << "Error: " << GetLastError() << "\n";
        return -1;
    }

    cout << "File created!\n";

    GetFileSizeEx(hFile, &size);
    cout << "File Size: " << size.QuadPart << "\n";

    physize.LowPart = GetCompressedFileSize(L"File1.txt", (LPDWORD)&physize.HighPart);
    cout << "Physical Size: " << physize.QuadPart << "\n";

    SetFilePointer(hFile, 0, (PLONG)&fptr, FILE_END);
    WriteFile(hFile, buff, (DWORD)strlen((const char *)buff), &fptr, NULL);

    WaitForSingleObject(hFile, INFINITE);

    SetFilePointer(hFile, 0, (PLONG)&fptr, FILE_BEGIN);
    GetFileSizeEx(hFile, &size);
    cout << "Size now is: " << size.QuadPart << "\n";

    BYTE *buff2 = new BYTE[size.QuadPart + 1];
    buff2[size.QuadPart] = '\0';

    if (!ReadFile(hFile, (LPVOID)buff2, size.QuadPart, &fptr, NULL)) 
    {
        cout << "Error: " << GetLastError() << "\n";
        return -1;
    }

    cout << buff2 << "\n";

    CloseHandle(hFile);
    delete[] buff2, buff;
    return 0;
}

写入后,文件内容为Hello World。但是控制台中的输出是:

File created!
File Size: 5
Physical Size: 5
Size now is: 11
═══════════

我搜索了网络,甚至搜索了这个网站,但没有找到任何对我有帮助的解决方案。在我看来,问题出在阅读上,因为写作已经成功完成。我什至使用函数WaitForSignalObject 来确保写作结束。有什么我错过的吗?谢谢你的帮助。

附:我的朋友已被禁止在此网站提问,他可以做些什么来解除禁令并继续使用该网站?

P.S.2 如果有任何语法错误,我很抱歉,英语不是我的母语。

【问题讨论】:

  • WaitForSingleObject 是干什么用的?
  • @jab 可以等待文件句柄(与任何其他内核对象句柄一样)。尽管几乎没有人这样做,因为文件句柄何时发出信号的语义通常与开发人员的需求(或期望)不一致。
  • @IInspectable,并非所有内核对象类型都是可等待的——只有支持SYNCHRONIZE 访问的类型。最常见的类型是可等待的,包括 File 对象,但也有一些不是,例如 Key、Token、Section、WindowStation 和 Desktop。也就是说,对以同步模式打开的文件调用WaitForSingleObject 毫无意义。 I/O 管理器本身会等待一个同步的 File 对象来完成 I/O 请求,因此WriteFile 在写入完成之前不会返回。
  • 你永远不会检查寻找开始是否失败。为什么选择 SECURITY_ATTRIBUTES?压缩后的大小无关紧要。你定义 UNICODE 太晚了。

标签: c++ windows visual-studio file winapi


【解决方案1】:

您在此处将文件指针设置得太远了:

SetFilePointer(hFile, 0, (PLONG)&fptr, FILE_BEGIN);

根据SetFilePointer的文档,第三个参数是:

lpDistanceToMoveHigh

指向要移动的有符号 64 位距离的高 32 位的指针。 如果不需要高位 32 位,则必须将此指针设置为 NULL。

您将移动的低位 32 位设置为 0,将高位 32 位设置为 fptr 的值,到达此行时该值不为 0。因此,您不会像您认为的那样移动到偏移量 0。

你的意思是:

SetFilePointer(hFile, fptr, NULL, FILE_BEGIN);

或:

SetFilePointer(hFile, 0, NULL, FILE_BEGIN);

我强烈建议摆脱 TCHAR 的东西;它在当今世界没有立足之地。只需明确使用charwchar_t,您应该知道您需要什么。

【讨论】:

  • 非常感谢,我以为SetFilePointer 的第三个参数是文件指针,第三个参数是要移动的字节的高位部分。顺便说一句,我使用“TCHAR”还是“wchar_t”有关系吗?因为定义是typedef wchar_t TCHAR
  • @Matan TCHAR 的定义取决于您的#define _UNICODE。在某些时候,MS 认为这会很方便——你在任何地方都使用TCHAR,他们会把它变成“合适的”类型。但是,您应该(必须?)自己知道,他们的猜测可能会失败。此外,您使用的定义错误(为时已晚,在包含 &lt;tchar.h&gt; 之后,正如上面的一位 cmets 所指出的那样。我宁愿删除它,而不是修复它。
  • 我发誓我之前看到有人对我的回答投了赞成票!无论如何,我的观点是 - 看起来 OP 想要指定一个偏移量,但将其放入错误的参数中(高位而不是低位)。
  • @VladFeinstein 当我第一次看到发布的答案时我投了赞成票,然后当我真正阅读它并误解了它的意思时我撤回了它,所以它变成了 -1 而不是 0。但后来我意识到我误解并删除了该评论,但撤回仍然存在。您的答案指向正确的问题,但没有解释为什么代码是错误的,所以我现在支持 -1。如果您编辑答案以详细说明,我可以再次投票。
  • @VladFeinstein 我扩大了你的答案,然后投了赞成票。
【解决方案2】:

问题在于您将fptrSetFilePointer() 滥用。

fptr 最初设置为 0。因此,当您调用SetFilePointer(hFile, 0, (PLONG)&amp;fptr, FILE_END) 时,您是在告诉它从文件末尾偏移low:0,high:0 = 0。因此,正如预期的那样,文件指针位于偏移量 5 处。 SetFilePointer() 输出新的文件位置给你,所以fptr 被更新为偏移量 5 的高 32 位,也就是 0。

然后你WriteFile() 新数据到文件,但你使用fptr 接收实际写入的字节数。所以现在 fptr 更新为 6(假设 WriteFile() 成功,您没有检查)。

然后,您将fptr 传递给SetFilePointer(hFile, 0, (PLONG)&amp;fptr, FILE_BEGIN) 作为高位部分。因此,不是告诉它从文件开头偏移low:0,high:0 = 0,而是告诉它从开头偏移low:0,high=6 = 0x600000000 = 25769803776

然后您尝试从偏移量 25769803776 而不是偏移量 0 读取 size (11) 个字节,当然在那个高偏移量处没有有效数据,因此您的缓冲区会被垃圾填满。

最简单的解决方法是在寻找FILE_BEGIN之前将fptr重置为0,例如:

fptr = 0; // <-- add this
SetFilePointer(hFile, 0, (PLONG)&fptr, FILE_BEGIN);

或者,您可以完全省略第三个参数,因为您处理的不是> 4GB 的大文件:

SetFilePointer(hFile, 0, NULL, FILE_END); // offset 0 from end
...
SetFilePointer(hFile, 0, NULL, FILE_BEGIN); // offset 0 from beginning

如果您想正确支持大于 4GB 的大文件,请不要对低位和高位部分使用单独的变量,而是使用 (U)LARGE_INTEGER(就像您已经使用 GetFileSizeEx()GetCompressedFileSize() 一样):

#define UNICODE
#define _UNICODE

#include <iostream>
#include <tchar.h>
#include <Windows.h>

using namespace std;

int _tmain(int _argc, TCHAR *_argv[])
{
    SECURITY_ATTRIBUTES sa;
    ZeroMemory(&sa, sizeof(sa));
    sa.nLength = sizeof(sa);
    sa.bInheritHandle = false;
    sa.lpSecurityDescriptor = NULL;

    HANDLE hFile = CreateFile(L"file1.txt", GENERIC_READ | GENERIC_WRITE, 0, &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    LARGE_INTEGER size, physize, fptr;
    BYTE buff[] = " World";
    DWORD written;

    if (hFile == INVALID_HANDLE_VALUE)
    {
        cout << "Error: " << GetLastError() << "\n";
        return -1;
    }

    cout << "File created!\n";

    GetFileSizeEx(hFile, &size);
    cout << "File Size: " << size.QuadPart << "\n";

    physize.LowPart = GetCompressedFileSize(L"File1.txt", (LPDWORD)&physize.HighPart);
    cout << "Physical Size: " << physize.QuadPart << "\n";

    fptr.QuadPart = 0;
    fptr.LowPart = SetFilePointer(hFile, fptr.LowPart, &fptr.HighPart, FILE_END);
    WriteFile(hFile, buff, (DWORD)strlen((const char *)buff), &written, NULL);

    fptr.QuadPart = 0;
    fptr.LowPart = SetFilePointer(hFile, fptr.LowPart, &fptr.HighPart, FILE_BEGIN);

    GetFileSizeEx(hFile, &size);
    cout << "Size now is: " << size.QuadPart << "\n";

    BYTE *buff2 = new BYTE[size.QuadPart + 1];
    buff2[size.QuadPart] = '\0';

    if (!ReadFile(hFile, (LPVOID)buff2, size.QuadPart, &fptr, NULL)) 
    {
        cout << "Error: " << GetLastError() << "\n";
        return -1;
    }

    cout << buff2 << "\n";

    CloseHandle(hFile);
    delete[] buff2;

    return 0;
}

或者,您可以(并且应该)改用SetFilePointerEx()

LARGE_INTEGER ..., fptr;
fptr.QuadPart = 0;

SetFilePointerEx(hFile, fptr, NULL, FILE_END);
...
SetFilePointerEx(hFile, fptr, NULL, FILE_BEGIN);

【讨论】:

    猜你喜欢
    • 2012-09-21
    • 2020-10-20
    • 1970-01-01
    • 1970-01-01
    • 2021-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-13
    相关资源
    最近更新 更多