我在使用 Windows 文件 API 时遇到了一些奇怪的行为,特别是具有重叠 IO 的 ReadFile。
在某些情况下,GetOverlappedResult 将成功地将数据读入提供的缓冲区,但将 lpNumberOfBytesTransferred 设置为零,而不是读取的正确数量。
这似乎只发生在同一句柄上发出多个重叠读取请求时,当文件先前使用 FILE_FLAG_NO_BUFFERING 打开时。
这是说明问题的完整代码示例...
#include <Windows.h>
#include <string>
#include <iostream>
const int PageSize = 4096;
const int BufferSize = PageSize * 4;
struct OperationSlot
{
OVERLAPPED state;
unsigned char* buffer;
};
bool enableFail;
bool ReadTest(std::string filename, DWORD flags, int queueSize)
{
bool result = true;
if (enableFail)
{
HANDLE temp = CreateFile(filename.c_str(), GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, 0);
CloseHandle(temp);
}
OperationSlot* slots = new OperationSlot[queueSize];
for (int i = 0; i < queueSize; ++i)
{
slots[i].buffer = (unsigned char*)_aligned_malloc(BufferSize, PageSize);
ZeroMemory(slots[i].buffer, BufferSize);
}
HANDLE file = CreateFile(filename.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, flags, NULL);
HANDLE controlFile = CreateFile(filename.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
unsigned char* controlBuffer = new unsigned char[BufferSize];
// Start async read operations...
for (int i = 0; i < queueSize; ++i)
{
ZeroMemory(&slots[i].state, sizeof(OVERLAPPED));
slots[i].state.Offset = i * BufferSize;
bool ok = ReadFile(file, slots[i].buffer, BufferSize, NULL, &slots[i].state);
if (!ok)
{
DWORD err = GetLastError();
if (err != ERROR_IO_PENDING)
{
std::cout << "ReadFile set error code " << err << std::endl;
}
}
}
int readId = 0;
while (true)
{
OperationSlot& active_slot = slots[readId % queueSize];
DWORD bytes = 0;
bool ok = GetOverlappedResult(file, &active_slot.state, &bytes, true);
DWORD err = GetLastError();
DWORD controlBytes = 0;
ReadFile(controlFile, controlBuffer, BufferSize, &controlBytes, NULL);
bool dataok = memcmp(active_slot.buffer, controlBuffer, controlBytes) == 0;
if (!dataok)
std::cout << "Data mismatch." << std::endl;
if (bytes != controlBytes)
{
std::cout << "Error with QueueSize (" << queueSize << ") and flags: ";
if (flags & FILE_FLAG_OVERLAPPED)
{
std::cout << "FILE_FLAG_OVERLAPPED";
}
if (flags & FILE_FLAG_NO_BUFFERING)
{
std::cout << " | FILE_FLAG_NO_BUFFERING";
}
if (flags & FILE_FLAG_SEQUENTIAL_SCAN)
{
std::cout << " | FILE_FLAG_SEQUENTIAL_SCAN";
}
std::cout << std::endl;
std::cout << "Read size error, expected " << controlBytes << ", got " << bytes << std::endl;
std::cout << "GetOverlappedResult returned " << ok << " with error code " << err << std::endl;
result = false;
}
if (controlBytes < BufferSize)
break;
ZeroMemory(&active_slot.state, sizeof(OVERLAPPED));
active_slot.state.Offset = (readId + queueSize) * BufferSize;
ReadFile(file, active_slot.buffer, BufferSize, NULL, &active_slot.state);
++readId;
}
CloseHandle(file);
CloseHandle(controlFile);
delete[] controlBuffer;
for (int i = 0; i < queueSize; ++i)
{
_aligned_free(slots[i].buffer);
}
delete[] slots;
return !result;
}
int main()
{
enableFail = false;
int totalfail = 0;
std::cout << "Testing without fail." << std::endl;
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED, 1);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 1);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 1);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED, 4);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 4);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 4);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED, 1);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 1);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 1);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED, 4);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 4);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 4);
std::cout << totalfail << " calls failed." << std::endl;
enableFail = true;
totalfail = 0;
std::cout << "Testing with fail enabled." << std::endl;
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED, 1);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 1);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 1);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED, 4);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 4);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 4);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED, 1);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 1);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 1);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED, 4);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 4);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 4);
std::cout << totalfail << " calls failed." << std::endl;
system("pause");
return 0;
}
在我的系统上,这导致 4 个调用“失败”。 (“队列大小”大于一的那些。)
发生的情况是重叠版本报告它只读取 0 个字节,而“正常”文件句柄读取 20 个字节。(一个小文本文件,上面写着“这是一个测试”。) 另一件奇怪的事情是它实际上正在正确读取数据。正确的缓冲区填充了正确的数据,只有报告的传输数据量是错误的...
只有在打开文件之前使用 FILE_FLAG_NO_BUFFERING 打开和关闭文件时才会发生这种情况。
为什么之前接触文件会导致后续访问行为不同?
我是在做不受支持的事情,还是 API 没有按照预期的方式运行? (或者我的代码中可能存在我忽略的错误...?)
编辑: 似乎我被哄骗认为我正确使用了 API,因为它在大多数情况下都奇迹般地工作。正确的方法是为每个重叠结构指定唯一事件,如已接受的答案中所指出的那样。在为每个重叠请求赋予它自己的事件后,它会始终如一地工作。
最佳答案
您没有为您的 OVERLAPPED
结构提供独特的事件,因此所有 GetOverlappedResult()
必须等待的是文件句柄 - 并且无法保证有多个未完成的请求您请求的请求实际上是在文件句柄收到信号时完成的请求。
每个 OVERLAPPED
结构都应将其 hEvent
成员初始化为使用 CreateEvent()
创建的新事件句柄。
关于c++ - 这是 Windows 文件 API 的正确用法吗? (多个重叠请求),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40275466/