ReadDirectoryChangesW and GetOverlappedResult - c++

I am calling ReadDirectoryChangesW asynchronously to monitor directory changes in a background thread.
This how the directory (basePath) is opened and the "reading" thread is started:
m_hDIR = CreateFileW(
basePath,
FILE_LIST_DIRECTORY | GENERIC_READ,
FILE_SHARE_WRITE | FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL);
if (m_hDIR == INVALID_HANDLE_VALUE)
throw CrException(CrWin32ErrorString());
//Start reading changes in background thread
m_Callback = std::move(a_Callback);
m_Reading = true;
m_ReadThread = std::thread(&CrDirectoryWatcher::StartRead, this);
This is StartRead(): (Note: m_Reading is atomic<bool>)
void StartRead()
{
DWORD dwBytes = 0;
FILE_NOTIFY_INFORMATION fni{0};
OVERLAPPED o{0};
//Be sure to set the hEvent member of the OVERLAPPED structure to a unique event.
o.hEvent = CreateEvent(0, 0, 0, 0);
while(m_Reading)
{
if (!ReadDirectoryChangesW(m_hDIR,
&fni, sizeof(fni),
TRUE, FILE_NOTIFY_CHANGE_LAST_WRITE,
&dwBytes, &o, NULL))
{
CrAssert(0, CrWin32ErrorString());
}
if (!GetOverlappedResult(m_hDIR, &o, &dwBytes, FALSE))
CrAssert(0, CrWin32ErrorString());
if (fni.Action != 0)
{
std::wstring fileName(fni.FileName, fni.FileNameLength);
m_Callback(fileName);
fni.Action = 0;
}
}
}
Basically, I am "polling" for new changes every frame.
Now when I call GetOverlappedResult() it fails and yields the following error:
Overlapped I/O event is not in a signaled state.
Am I missing something? Is ReadDirectoryChangesW meant to be called every "tick"? Or just when new changes were detected?
Note: When I leave out the OVERLAPPED struct (and GetOverlappedResult) it works, but blocks the thread until changes were read. This prevents my application to properly terminate. (i.e. I can't join the thread)

When calling GetOverlappedResult(), if you set the bWait parameter to FALSE and the I/O operation hasn't completed yet, GetOverlappedResult() fails with an ERROR_IO_INCOMPLETE error code:
bWait [in]
If this parameter is TRUE, and the Internal member of the lpOverlapped structure is STATUS_PENDING, the function does not return until the operation has been completed. If this parameter is FALSE and the operation is still pending, the function returns FALSE and the GetLastError function returns ERROR_IO_INCOMPLETE.
That is not a fatal error, so just ignore that error and move on.
And yes, make sure you don't call ReadDirectoryChangesW() again until GetOverlappedResult() has reported the previous I/O operation has completed first.
Now, with that said, there is another problem with your code. Your thread is allocating a single FILE_NOTIFY_INFORMATION instance on the stack. If you look at the definition of FILE_NOTIFY_INFORMATION, its FileName field is variable-length:
typedef struct _FILE_NOTIFY_INFORMATION {
DWORD NextEntryOffset;
DWORD Action;
DWORD FileNameLength;
WCHAR FileName[1];
} FILE_NOTIFY_INFORMATION, *PFILE_NOTIFY_INFORMATION;
FileName
A variable-length field that contains the file name relative to the directory handle. The file name is in the Unicode character format and is not null-terminated.
Which means allocating a FILE_NOTIFY_INFORMATION statically is going to be too small, and dwBytes will almost always be 0 since ReadDirectoryChangesW() won't be able to return a full FILE_NOTIFY_INFORMATION to you (unless the FileName is exactly 1 character in length):
When you first call ReadDirectoryChangesW, the system allocates a buffer to store change information. This buffer is associated with the directory handle until it is closed and its size does not change during its lifetime. Directory changes that occur between calls to this function are added to the buffer and then returned with the next call. If the buffer overflows, the entire contents of the buffer are discarded, the lpBytesReturned parameter contains zero, and the ReadDirectoryChangesW function fails with the error code ERROR_NOTIFY_ENUM_DIR.
ERROR_NOTIFY_ENUM_DIR
1022 (0x3FE)
A notify change request is being completed and the information is not being returned in the caller's buffer. The caller now needs to enumerate the files to find the changes.
So, you need to dynamically allocate a large byte buffer for receiving FILE_NOTIFY_INFORMATION data, and then you can walk that buffer whenever GetOverlappedResult() reports that data is available.
Your thread should look something more like this:
void StartRead()
{
DWORD dwBytes = 0;
std::vector<BYTE> buffer(1024*64);
OVERLAPPED o{0};
bool bPending = false;
//Be sure to set the hEvent member of the OVERLAPPED structure to a unique event.
o.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!o.hEvent) {
CrAssert(0, CrWin32ErrorString());
}
while (m_Reading)
{
bPending = ReadDirectoryChangesW(m_hDIR,
&buffer[0], buffer.size(),
TRUE, FILE_NOTIFY_CHANGE_LAST_WRITE,
&dwBytes, &o, NULL);
if (!bPending)
{
CrAssert(0, CrWin32ErrorString());
}
while (m_Reading)
{
if (GetOverlappedResult(m_hDIR, &o, &dwBytes, FALSE))
{
bPending = false;
if (dwBytes != 0)
{
FILE_NOTIFY_INFORMATION *fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(&buffer[0]);
do
{
if (fni->Action != 0)
{
std::wstring fileName(fni->FileName, fni->FileNameLength);
m_Callback(fileName);
}
if (fni->NextEntryOffset == 0)
break;
fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(reinterpret_cast<BYTE*>(fni) + fni->NextEntryOffset);
}
while (true);
}
break;
}
if (GetLastError() != ERROR_IO_INCOMPLETE) {
CrAssert(0, CrWin32ErrorString());
}
Sleep(10);
}
if (bPending)
{
CancelIo(m_hDIR);
GetOverlappedResult(m_hDIR, &o, &dwBytes, TRUE);
}
}
CloseHandle(o.hEvent);
}
An alternative way to implement this without polling the I/O status regularly would be to get rid of m_Reading and use a waitable event instead. Let the OS signal the thread when it should call GetOverlappedResult() or terminate, that way it can sleep the rest of the time it is not busy doing something:
m_hDIR = CreateFileW(
basePath,
FILE_LIST_DIRECTORY | GENERIC_READ,
FILE_SHARE_WRITE | FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL);
if (m_hDIR == INVALID_HANDLE_VALUE)
throw CrException(CrWin32ErrorString());
m_TermEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!m_TermEvent)
throw CrException(CrWin32ErrorString());
//Start reading changes in background thread
m_Callback = std::move(a_Callback);
m_ReadThread = std::thread(&CrDirectoryWatcher::StartRead, this);
...
SetEvent(m_TermEvent);
m_ReadThread.join();
void StartRead()
{
DWORD dwBytes = 0;
std::vector<BYTE> buffer(1024*64);
OVERLAPPED o{0};
bool bPending = false, bKeepRunning = true;
//Be sure to set the hEvent member of the OVERLAPPED structure to a unique event.
o.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!o.hEvent) {
CrAssert(0, CrWin32ErrorString());
}
HANDLE h[2] = {o.hEvent, h_TermEvent};
do
{
bPending = ReadDirectoryChangesW(m_hDIR,
&buffer[0], buffer.size(),
TRUE, FILE_NOTIFY_CHANGE_LAST_WRITE,
&dwBytes, &o, NULL);
if (!bPending)
{
CrAssert(0, CrWin32ErrorString());
}
switch (WaitForMultipleObjects(2, h, FALSE, INFINITE))
{
case WAIT_OBJECT_0:
{
if (!GetOverlappedResult(m_hDIR, &o, &dwBytes, TRUE)) {
CrAssert(0, CrWin32ErrorString());
}
bPending = false;
if (dwBytes == 0)
break;
FILE_NOTIFY_INFORMATION *fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(&buffer[0]);
do
{
if (fni->Action != 0)
{
std::wstring fileName(fni->FileName, fni->FileNameLength);
m_Callback(fileName);
}
if (fni->NextEntryOffset == 0)
break;
fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(reinterpret_cast<BYTE*>(fni) + fni->NextEntryOffset);
}
while (true);
break;
}
case WAIT_OBJECT_0+1:
bKeepRunning = false;
break;
case WAIT_FAILED:
CrAssert(0, CrWin32ErrorString());
break;
}
}
while (bKeepRunning);
if (bPending)
{
CancelIo(m_hDIR);
GetOverlappedResult(m_hDIR, &o, &dwBytes, TRUE);
}
CloseHandle(o.hEvent);
}

Related

WaitForMultipleObjects() does not return until buffer is full, unless I use Putty

I've got a child thread that calls WaitForMultipleObjects() and blocks waiting for, among other things, an incoming message over COM4. Attached to the other side of COM4 is a board sending a message once every five seconds.
The problem is, WaitForMultipleObjects() is only notified that a message was received when the read buffer I allocated for it is full, rather than becoming notified whenever a single message is transmitted.
The weird part is that this behavior changes if I connect to the board with putty, close putty, and then reopen my application - WaitForMultipleObjects() will thereafter return whenever a message is received, buffer filled or not. This behavior is preferable, and I would like to know what putty is doing so that I may have it enabled all the time.
I checked the DCB object returned from GetCommState() both with and without using Putty, and it doesn't appear that Putty is changing the structure in a way that I am not already doing.
Here is the code initializing the HANDLE object for the com port (referred to as "hCom"):
bool init(struct ReadComArg readComArg) {
BOOL fSuccess;
log(comPort);
// Open a handle to the specified com port.
hCom = CreateFile(comPort,
GENERIC_READ | GENERIC_WRITE,
0, // must be opened with exclusive-access
NULL, // default security attributes
OPEN_EXISTING, // must use OPEN_EXISTING
FILE_FLAG_OVERLAPPED, // can't write in one thread while blocked for a read in another unless port is declared overlapped
NULL); // hTemplate must be NULL for comm devices
if (hCom == INVALID_HANDLE_VALUE) {
// Handle the error.
log("Failed to create hCom handle");
return false;
}
DWORD lptEvtMask;
fSuccess = GetCommMask(hCom, &lptEvtMask);
if (!fSuccess) {
log("GetCommMask failed");
CloseHandle(hCom);
return false;
}
fSuccess = SetCommMask(hCom, lptEvtMask);
printAll(&lptEvtMask);
// Initialize the DCB structure.
DCB dcb;
SecureZeroMemory(&dcb, sizeof(DCB));
dcb.DCBlength = sizeof(DCB);
// Build on the current configuration by first retrieving all current
// settings.
/*fSuccess = GetCommState(hCom, &dcb);
if (!fSuccess) {
log("GetCommState failed");
CloseHandle(hCom);
return false;
}
*/
dcb.BaudRate = readComArg.baudRate; // 115200 baud rate
dcb.ByteSize = readComArg.dataBits; // 8 data size, xmit and rcv
/*
dcb.Parity = readComArg.parity; // parity bit
dcb.StopBits = readComArg.stopBits; // stop bit
dcb.fRtsControl = RTS_CONTROL_ENABLE;
dcb.fDtrControl = DTR_CONTROL_ENABLE;
*/
fSuccess = SetCommState(hCom, &dcb);
if (!fSuccess) {
log("SetCommState failed");
CloseHandle(hCom);
return false;
}
// Get the comm config again.
fSuccess = GetCommState(hCom, &dcb);
if (!fSuccess) {
log("GetCommState failed");
CloseHandle(hCom);
return false;
}
log("Serial Port successfully reconfigured");
hFile = CreateFileW(fileName, // name of the write
FILE_APPEND_DATA, // open for appending
0, // no sharing
NULL, // default security
OPEN_ALWAYS, // open existing file or create new file
FILE_ATTRIBUTE_NORMAL, // normal file
NULL); // no attr. template
if (hFile == NULL) {
log("Failed to create hFile handle");
CloseHandle(hCom);
return false;
}
PostMessage(readComArg.MainWindow, WM_THREAD_UP, 0, 0);
return true;
}
Putty doesn't touch any of the DCB fields except DCBlength, BaudRate, ByteSize, Parity, and StopBits, and setting the fields that aren't these five to arbitrary values doesn't seem to affect the code's behavior, so I left the rest at zero.
Here is the main loop code:
void loop(struct ReadComArg readComArg) {
//TODO: close all handles when function returns
const unsigned long BUFFSIZE = 256;
BYTE buffer[BUFFSIZE];
DWORD bytesRead, bytesWritten, result;
OVERLAPPED osReader;
OVERLAPPED osWriter;
HANDLE eventArray[4];
eventArray[0] = readComArg.killEvent;
eventArray[2] = readComArg.writeRequestEvent;
BOOL continueLooping = true;
BOOL fWaitingOnRead = FALSE;
BOOL fWaitingOnWrite = FALSE;
while (continueLooping) {
//There is no outstanding read request and we must create a new one:
if (!fWaitingOnRead) {
//First, check to make sure we haven't received a killEvent.
result = WaitForSingleObject(readComArg.killEvent, 0); //returns immediately
if (result == WAIT_OBJECT_0) {
//received killEvent, exiting
log("killEvent was signaled. Exiting loop");
if (!ResetEvent(readComArg.killEvent))
log("failed to reset killEvent");
break;
}
else if (result != WAIT_TIMEOUT) {
//some error occured
log("WaitForSingleObject returned an error");
break;
}
//Otherwise, there was no kill request, continue as normal.
//Attempt to create new read request
osReader = { 0 };
osReader.hEvent = CreateEvent(NULL, true, false, NULL);
//read event failed to allocate, return:
if (osReader.hEvent == NULL) {
log("failed to create readEvent");
break;
}
eventArray[1] = osReader.hEvent;
//Execute the asynchronous read:
if (!ReadFile(hCom, buffer, BUFFSIZE, &bytesRead, &osReader)) {
//The asynchronous read request succeeded and will be completed later:
if (GetLastError() == ERROR_IO_PENDING) {
log("Read request queued and pending");
fWaitingOnRead = TRUE;
}
//The asynchronous read request failed:
else {
log("ReadFile returned an error");
CloseHandle(osReader.hEvent);
break;
}
}
//The asynchronous read request succeeded and completed immediately:
else {
log("Read request queued and returned immediately");
CloseHandle(osReader.hEvent);
handleRead(readComArg, buffer, bytesRead);
}
}
//We are waiting on an outstanding read request:
else {
//block until a signal arrives
//if we are waiting on a write, then there is an additional signal we must block for
if (fWaitingOnWrite) {
log("blocking for kills, reads, writeRequests, and writeResponses.");
result = WaitForMultipleObjects(4, eventArray, FALSE, INFINITE);
}
else {
log("blocking for kills, reads, and writeRequests. No outstanding write Request.");
result = WaitForMultipleObjects(3, eventArray, FALSE, INFINITE);
}
continueLooping = false;
switch (result) {
//The killEvent handle received a signal. This has priority over every other signal.
case WAIT_OBJECT_0:
log("Received killEvent. Exiting loop");
if (!ResetEvent(readComArg.killEvent))
log("failed to reset killEvent");
break;
//The com port handle received a signal
case WAIT_OBJECT_0+1:
log("received signal");
//Unsuccessful read
if (!GetOverlappedResult(hCom, &osReader, &bytesRead, FALSE)) {
log("GetOverlappedResult returned an error");
break;
}
//Successful read, continue looping
log("Outstanding read request fulfilled");
handleRead(readComArg, buffer, bytesRead);
fWaitingOnRead = FALSE;
CloseHandle(osReader.hEvent);
continueLooping = true;
break;
//The writeRequestEvent handle received a signal. Create an asynchronous write request:
case WAIT_OBJECT_0 + 2:
//reset writeRequestEvent signal.
if (!ResetEvent(readComArg.writeRequestEvent)) {
log("failed to reset writeRequestEvent");
break;
}
//attempt to create writeResponseEvent:
osWriter = { 0 };
osWriter.hEvent = CreateEvent(NULL, true, false, NULL);
if (osWriter.hEvent == NULL) {
log("failed to create writeResponseEvent");
break;
}
eventArray[3] = osWriter.hEvent;
//execute the asynchronous write:
if (!WriteFile(hCom, readComArg.writeBuffer, readComArg.numCharsToWrite, &bytesWritten, &osWriter)) {
//The asynchronous write request succeeded and will be completed later:
if (GetLastError() == ERROR_IO_PENDING) {
log("Write request queued and pending");
fWaitingOnWrite = true;
continueLooping = true;
break;
}
//The asynchronous write request failed:
else {
log("WriteFile returned an error");
CloseHandle(osWriter.hEvent);
break;
}
}
//The asynchronous write request succeeded and completed immediately
else {
log("Write request queued and returned immediately");
CloseHandle(osWriter.hEvent);
continueLooping = true;
PostMessage(readComArg.MainWindow, WM_THREAD_SENT, 0, 0);
break;
}
break;
//The writeResponseEvent handle received a signal
case WAIT_OBJECT_0+3:
//Unsuccessful write
if (!GetOverlappedResult(hCom, &osWriter, &bytesWritten, FALSE)) {
log("GetOverlappedResult returned an error");
break;
}
//Successful write, continue looping
PostMessage(readComArg.MainWindow, WM_THREAD_SENT, 0, 0);
log("Outstanding write request fulfilled");
fWaitingOnWrite = FALSE;
CloseHandle(osWriter.hEvent);
continueLooping = true;
break;
// Error in WaitForMultipleObjects()
default:
log("WaitForMultipleObjects returned an error");
break;
}
}
}
CancelIo(hCom);
if(fWaitingOnRead)
CloseHandle(osReader.hEvent);
if (fWaitingOnWrite)
CloseHandle(osWriter.hEvent);
}
I am not very familiar with StackOverflow ettiquette, so if there is something incorrect with how I am asking my question, I apologize in advance and will correct it as soon as I am able. Thank you.

CreateEvent initial state signaled is not signaling event

I am working on IO redirection program and i successfully created poc for it. The program spawn the child process and communicate with it using named pipes. I used Event object to get event whenever there is data on the pipe. I set the event to signaled state by default but i am not getting the event for the first time. To get the event i have to write on input pipe. When i write some command on input pipe i get the event and and get the output for the old command, not the current command(Please see the output).
below is the working code.
#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include <thread>
#include <string>
using namespace std;
#define input_pipe_name L"\\\\.\\pipe\\input"
#define output_pipe_name L"\\\\.\\pipe\\output"
#define process_name L"cmd.exe"
HANDLE input_pipe_handle;
HANDLE output_pipe_handle;
HANDLE input_file_handle;
HANDLE output_file_handle;
OVERLAPPED output_overlapped = { 0 };
BOOL InitHandels()
{
input_pipe_handle = CreateNamedPipe(input_pipe_name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, 1, 4096, 4096, 120000, 0);
SetHandleInformation(input_pipe_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
if (input_pipe_handle == INVALID_HANDLE_VALUE)
{
cout << "pipe creation error: " << GetLastError() << endl;
return FALSE;
}
output_pipe_handle = CreateNamedPipe(output_pipe_name, PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, 1, 4096, 4096, 120000, 0);
SetHandleInformation(output_pipe_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
if (output_pipe_handle == INVALID_HANDLE_VALUE)
{
cout << "pipe creation error: " << GetLastError() << endl;
return FALSE;
}
input_file_handle = CreateFile(input_pipe_name, GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (input_file_handle == INVALID_HANDLE_VALUE)
{
cout << "file creation error: " << GetLastError() << endl;
return FALSE;
}
output_file_handle = CreateFile(output_pipe_name, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (output_file_handle == INVALID_HANDLE_VALUE)
{
cout << "file creation error: " << GetLastError() << endl;
return FALSE;
}
output_overlapped.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
ConnectNamedPipe(output_pipe_handle, &output_overlapped);
}
void CreateChildProcess()
{
TCHAR szCmdline[] = L"cmd.exe";
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = output_pipe_handle;
siStartInfo.hStdOutput = output_pipe_handle;
siStartInfo.hStdInput = input_pipe_handle;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
if (!CreateProcess(NULL, szCmdline, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo))
{
cout << "process creation error: " << GetLastError() << endl;
//return FALSE;
}
else
{
HANDLE h_array[] = {output_overlapped.hEvent, piProcInfo.hProcess};
for (;;)
{
DWORD result = WaitForMultipleObjects(2, h_array, FALSE, 1000);
DWORD bwritten = 0, bread = 0;
char buffer[4096];
switch (result)
{
case WAIT_TIMEOUT:
//cout << "TimeOut" << endl;
break;
case WAIT_OBJECT_0:
ReadFile(output_file_handle, buffer, sizeof(buffer), &bread, &output_overlapped);
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buffer, bread, &bwritten, 0);
ResetEvent(output_overlapped.hEvent);
break;
case WAIT_OBJECT_0 + 1:
break;
//return FALSE;
}
}
}
}
int main()
{
DWORD bwritten;
InitHandels();
//CreateChildProcess();
std::thread t1(CreateChildProcess);
for (;;Sleep(1000))
{
std::string mystring;
std::cin >> mystring;
mystring.append("\n");
WriteFile(input_file_handle, mystring.c_str(), mystring.length(), &bwritten, &output_overlapped);
//WriteFile(input_file_handle, "dir\n", 4, &bwritten, &output_overlapped);
}
t1.join();
return 0;
}
I get the following output
dir
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. All rights reserved.
D:\Programming\VS\to_post_on_stack\to_post_on_stack>hello
dir
Volume in drive D has no label.
Volume Serial Number is 54FB-7A94
Directory of D:\Programming\VS\to_post_on_stack\to_post_on_stack
01/13/2018 05:36 PM <DIR> .
01/13/2018 05:36 PM <DIR> ..
01/13/2018 05:36 PM <DIR> Debug
01/12/2018 08:54 PM 608 stdafx.cpp
01/12/2018 08:54 PM 642 stdafx.h
01/12/2018 08:54 PM 630 targetver.h
01/13/2018 05:36 PM 7,434 to_post_on_stack.cpp
01/12/2018 08:54 PM 8,038 to_post_on_stack.vcxproj
01/12/2018 08:54 PM 1,277 to_post_on_stack.vcxproj.filters
6 File(s) 18,629 bytes
3 Dir(s) 39,347,019,776 bytes free
D:\Programming\VS\to_post_on_stack\to_post_on_stack>dir
hello
'hello' is not recognized as an internal or external command,
operable program or batch file.
D:\Programming\VS\to_post_on_stack\to_post_on_stack>dir
As you can see in the output when i send dir command i get the old output. When i send hello i get the output for the dir command which i executed before hello.
SO can anyone point out the mistake that why i am not getting signal for the very first time. And why the output is not getting in sequence?
the code example full of critical bugs:
the first and main:
If hFile was opened with FILE_FLAG_OVERLAPPED, the following
conditions are in effect:
The lpOverlapped parameter must point to a valid and unique
OVERLAPPED structure, otherwise the function can incorrectly report
that the io operation is complete.
and
io operation resets the event specified by the hEvent member of the
OVERLAPPED structure to a nonsignaled state when it begins the I/O
operation. Therefore, the caller does not need to do that.
when io operation is complete - io subsystem write to lpOverlapped final status of operation, number of bytes transferred, and if it containing event - set this event to signal state. if you use the same lpOverlapped in concurrent - they overwrite result each other, and you never know - which operation is really complete - event is one, common !, also if you use event - system reset event before begin io - as result - one operation can complete and set event, then another operation reset it after this
you call in 2 threads in concurrent:
WriteFile(input_file_handle, ..&output_overlapped);
ReadFile(output_file_handle, .. &output_overlapped);
with this you already have UB because the same &output_overlapped used in concurrent. we need allocate unique overlapped to every operation. and if you use events for detect completion - you need create several events - this is not good way at all. much better use iocp completion here - we not need create events, we not need create separate thread.
ReadFile(output_file_handle, buffer, sizeof(buffer), &bread, &output_overlapped);
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buffer, bread, &bwritten, 0);
ResetEvent(output_overlapped.hEvent);
at first ReadFile resets the event specified by the hEvent member of the OVERLAPPED structure to a nonsignaled state when it begins the I/O operation. Therefore, the caller does not need to do that. and more - when you call ResetEvent - operation can be already completed - so you reset already signaled event - and as result you lost completion signal. if call ReasetEvent this need do before io operation (ReadFile in concrete case) but not after - which is error. however we not need do this and before - because io subsystem anyway do this.
else one critical bug - we can not use buffer, breadin WriteFile just after asynchronous call to ReadFile - the call is can be not completed yet. and context of buffer yet not defined.
the &bread is undefined always in asynchronous call and must not be used at all
The lpNumberOfBytesRead parameter should be set to NULL. Use the
GetOverlappedResult function to get the actual number of bytes read.
If the hFile parameter is associated with an I/O completion port,
you can also get the number of bytes read by calling the
GetQueuedCompletionStatus function.
else one very common mistake - that we create 2 pipe pairs (input_pipe_handle, output_file_handle) - this is absolute not need - we can use the 1 pipe pair.
the call to SetHandleInformation excess - we need just create handle with inherit properties via SECURITY_ATTRIBUTES.
code example:
//#define _XP_SUPPORT_
struct IO_COUNT
{
HANDLE _hFile;
HANDLE _hEvent;
LONG _dwIoCount;
IO_COUNT()
{
_dwIoCount = 1;
_hEvent = 0;
}
~IO_COUNT()
{
if (_hEvent)
{
CloseHandle(_hEvent);
}
}
ULONG Create(HANDLE hFile);
void BeginIo()
{
InterlockedIncrement(&_dwIoCount);
}
void EndIo()
{
if (!InterlockedDecrement(&_dwIoCount))
{
SetEvent(_hEvent);
}
}
void Wait()
{
WaitForSingleObject(_hEvent, INFINITE);
}
};
class U_IRP : OVERLAPPED
{
enum { connect, read, write };
IO_COUNT* _pIoObject;
ULONG _code;
LONG _dwRef;
char _buffer[256];
~U_IRP()
{
_pIoObject->EndIo();
}
ULONG Read()
{
_code = read;
AddRef();
return CheckIoResult(ReadFile(_pIoObject->_hFile, _buffer, sizeof(_buffer), 0, this));
}
ULONG CheckIoResult(BOOL fOk)
{
if (fOk)
{
#ifndef _XP_SUPPORT_
OnIoComplete(NOERROR, InternalHigh);
#endif
return NOERROR;
}
ULONG dwErrorCode = GetLastError();
if (dwErrorCode != ERROR_IO_PENDING)
{
OnIoComplete(dwErrorCode, 0);
}
return dwErrorCode;
}
VOID OnIoComplete(DWORD dwErrorCode, DWORD_PTR dwNumberOfBytesTransfered)
{
switch (_code)
{
case connect:
switch (dwErrorCode)
{
case ERROR_PIPE_CONNECTED:
case ERROR_NO_DATA:
dwErrorCode = NOERROR;
case NOERROR:
Read();
}
break;
case read:
if (dwErrorCode == NOERROR)
{
if (dwNumberOfBytesTransfered)
{
if (int cchWideChar = MultiByteToWideChar(CP_OEMCP, 0, _buffer, (ULONG)dwNumberOfBytesTransfered, 0, 0))
{
PWSTR wz = (PWSTR)alloca(cchWideChar * sizeof(WCHAR));
if (MultiByteToWideChar(CP_OEMCP, 0, _buffer, (ULONG)dwNumberOfBytesTransfered, wz, cchWideChar))
{
if (int cbMultiByte = WideCharToMultiByte(CP_ACP, 0, wz, cchWideChar, 0, 0, 0, 0))
{
PSTR sz = (PSTR)alloca(cbMultiByte);
if (WideCharToMultiByte(CP_ACP, 0, wz, cchWideChar, sz, cbMultiByte, 0, 0))
{
DbgPrint("%.*s", cbMultiByte, sz);
}
}
}
}
}
Read();
}
break;
case write:
break;
default:
__debugbreak();
}
Release();
if (dwErrorCode)
{
DbgPrint("[%u]: error=%u\n", _code, dwErrorCode);
}
}
static VOID WINAPI _OnIoComplete(
DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped
)
{
static_cast<U_IRP*>(lpOverlapped)->OnIoComplete(RtlNtStatusToDosError(dwErrorCode), dwNumberOfBytesTransfered);
}
public:
void AddRef()
{
InterlockedIncrement(&_dwRef);
}
void Release()
{
if (!InterlockedDecrement(&_dwRef)) delete this;
}
U_IRP(IO_COUNT* pIoObject) : _pIoObject(pIoObject)
{
_dwRef = 1;
pIoObject->BeginIo();
RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
}
ULONG Write(const void* pvBuffer, ULONG cbBuffer)
{
_code = write;
AddRef();
return CheckIoResult(WriteFile(_pIoObject->_hFile, pvBuffer, cbBuffer, 0, this));
}
ULONG Connect()
{
_code = connect;
AddRef();
return CheckIoResult(ConnectNamedPipe(_pIoObject->_hFile, this));
}
static ULONG Bind(HANDLE hFile)
{
return BindIoCompletionCallback(hFile, U_IRP::_OnIoComplete, 0)
#ifndef _XP_SUPPORT_
&& SetFileCompletionNotificationModes(hFile, FILE_SKIP_COMPLETION_PORT_ON_SUCCESS)
#endif
? NOERROR : GetLastError();
}
};
ULONG IO_COUNT::Create(HANDLE hFile)
{
_hFile = hFile;
if (_hEvent = CreateEvent(0, TRUE, FALSE, 0))
{
return U_IRP::Bind(hFile);
}
return GetLastError();
}
void ChildTest()
{
static const WCHAR name[] = L"\\\\?\\pipe\\somename";
HANDLE hFile = CreateNamedPipeW(name,
PIPE_ACCESS_DUPLEX|FILE_READ_DATA|FILE_WRITE_DATA|FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE|PIPE_READMODE_BYTE, 1, 0, 0, NMPWAIT_USE_DEFAULT_WAIT, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
IO_COUNT obj;
if (obj.Create(hFile) == NOERROR)
{
BOOL fOk = FALSE;
static SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };
STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi;
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdError = CreateFileW(name, FILE_GENERIC_READ|FILE_GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE, &sa, OPEN_EXISTING, 0, 0);
if (si.hStdError != INVALID_HANDLE_VALUE)
{
si.hStdInput = si.hStdOutput = si.hStdError;
WCHAR ApplicationName[MAX_PATH];
if (GetEnvironmentVariableW(L"ComSpec", ApplicationName, RTL_NUMBER_OF(ApplicationName)))
{
if (CreateProcessW(ApplicationName , 0, 0, 0, TRUE, 0, 0, 0, &si, &pi))
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
fOk = TRUE;
}
}
CloseHandle(si.hStdError);
}
if (fOk)
{
U_IRP* p;
if (p = new U_IRP(&obj))
{
p->Connect();
p->Release();
}
obj.EndIo();
//++ simulate user commands
static PCSTR commands[] = { "dir\r\n", "ver\r\n", "exit\r\n" };
ULONG n = RTL_NUMBER_OF(commands);
PCSTR* psz = commands;
do
{
if (p = new U_IRP(&obj))
{
PCSTR command = *psz++;
p->Write(command, (ULONG)strlen(command) * sizeof(CHAR));
p->Release();
}
} while (--n);
//--
obj.Wait();
}
}
CloseHandle(hFile);
}
}

ReadFile using NamedPipe blocks though using overlapped

I am using NamedPipes to communicate between different modules of our windows application. At one point it is possible that a read operation could take long time so we would like to add a timeout.
I added the OVERLAPPED-flag like this:
pipeHandle = CreateFile(
pipename,
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
0,
nullptr,
OPEN_EXISTING,
0,
nullptr);
Then the read operation looks like this:
OVERLAPPED overlapped;
ZeroMemory(&overlapped, sizeof(OVERLAPPED));
overlapped.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
successful = ReadFile(
pipeHandle,
buffer,
4096 * sizeof(wchar_t),
&numBytesRead,
&overlapped
);
if (successful == FALSE)
{
LOG("Reading was not successful");
if (GetLastError() != ERROR_IO_PENDING)
{
// The function call failed. ToDo: recovery.
LOG_LAST_ERROR("Failed because of Error: ");
return ERROR_READ_FAULT;
}
else
{
// TODO: add a timeout...
successful = GetOverlappedResult(pipeHandle, &overlapped, &numBytesRead, TRUE);
}
}
else
{
LOG("Reading was successful");
// I/O completed:
return ERROR_SUCCESS;
}
Now I would expect that call to ReadFile would return immediatley so I could handle possible timeouts, but instead it blocks till the call returns.
What am I doing wrong?
In your code, the FILE_FLAG_OVERLAPPED is in the dwDesiredAccess parameter, but it should be specified in the dwFlagsAndAttributes parameter.
So, this might work better:
pipeHandle = CreateFile(
pipename,
PIPE_ACCESS_DUPLEX,
0,
nullptr,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
nullptr);

Using ReadDirectoryChangesW asynchronously in a loop

INTRODUCTION:
I am trying to use ReadDirectoryChangesW asynchronously in a loop.
Below snippet illustrates what I am trying to achieve:
DWORD example()
{
DWORD error = 0;
OVERLAPPED ovl = { 0 };
ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
if (NULL == ovl.hEvent) return ::GetLastError();
char buffer[1024];
while(1)
{
process_list_of_existing_files();
error = ::ReadDirectoryChangesW(
m_hDirectory, // I have added FILE_FLAG_OVERLAPPED in CreateFile
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);
// we have new files, append them to the list
if(error) append_new_files_to_the_list(buffer);
// just continue with the loop
else if(::GetLastError() == ERROR_IO_PENDING) continue;
// RDCW error, this is critical -> exit
else return ::GetLastError();
}
}
PROBLEM:
I do not know how to handle the case when ReadDirectoryChangesW returns FALSE with GetLastError() code being ERROR_IO_PENDING.
In that case I should just continue with the loop and keep looping until ReadDirectoryChangesW returns buffer I can process.
MY EFFORTS TO SOLVE THIS:
I have tried using WaitForSingleObject(ovl.hEvent, 1000) but it crashes with error 1450 ERROR_NO_SYSTEM_RESOURCES. Below is the MVCE that reproduces this behavior:
#include <iostream>
#include <Windows.h>
DWORD processDirectoryChanges(const char *buffer)
{
DWORD offset = 0;
char fileName[MAX_PATH] = "";
FILE_NOTIFY_INFORMATION *fni = NULL;
do
{
fni = (FILE_NOTIFY_INFORMATION*)(&buffer[offset]);
// since we do not use UNICODE,
// we must convert fni->FileName from UNICODE to multibyte
int ret = ::WideCharToMultiByte(CP_ACP, 0, fni->FileName,
fni->FileNameLength / sizeof(WCHAR),
fileName, sizeof(fileName), NULL, NULL);
switch (fni->Action)
{
case FILE_ACTION_ADDED:
{
std::cout << fileName << std::endl;
}
break;
default:
break;
}
::memset(fileName, '\0', sizeof(fileName));
offset += fni->NextEntryOffset;
} while (fni->NextEntryOffset != 0);
return 0;
}
int main()
{
HANDLE hDir = ::CreateFile("C:\\Users\\nenad.smiljkovic\\Desktop\\test",
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
if (INVALID_HANDLE_VALUE == hDir) return ::GetLastError();
OVERLAPPED ovl = { 0 };
ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
if (NULL == ovl.hEvent) return ::GetLastError();
DWORD error = 0, br;
char buffer[1024];
while (1)
{
error = ::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);
if (0 == error)
{
error = ::GetLastError();
if (ERROR_IO_PENDING != error)
{
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
}
}
error = ::WaitForSingleObject(ovl.hEvent, 0);
switch (error)
{
case WAIT_TIMEOUT:
break;
case WAIT_OBJECT_0:
{
error = processDirectoryChanges(buffer);
if (error > 0)
{
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
}
if (0 == ::ResetEvent(ovl.hEvent))
{
error = ::GetLastError();
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
}
}
break;
default:
error = ::GetLastError();
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
break;
}
}
return 0;
}
Reading through the documentation, it seems that I need GetOverlappedResult with last parameter set to FALSE but I do not know how to use this API properly.
QUESTION:
Since the MVCE illustrates very well what I am trying to do (print the names of the newly added files), can you show me what must be fixed in the while loop in order for it to work?
Again, the point is to use ReadDirectoryChangesW asynchronously, in a loop, as shown in the snippet from the INTRODUCTION.
The basic structure of your program looks more or less OK, you're just using the asynchronous I/O calls incorrectly. Whenever there are no new files, the wait on the event handle times out immediately, which is fine, but you then issue a brand new I/O request, which isn't.
That's why you're running out of system resources; you're issuing I/O requests full tilt without waiting for any of them to complete. You should only issue a new request after the existing request has completed.
(Also, you should be calling GetOverlappedResult to check whether the I/O was successful or not.)
So your loop should look more like this:
::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);
while (1)
{
DWORD dw;
DWORD result = ::WaitForSingleObject(ovl.hEvent, 0);
switch (result)
{
case WAIT_TIMEOUT:
processBackgroundTasks();
break;
case WAIT_OBJECT_0:
::GetOverlappedResult(hDir, &ovl, &dw, FALSE);
processDirectoryChanges(buffer);
::ResetEvent(ovl.hEvent);
::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);
break;
}
}
Notes:
The error handling has been elided for simplicity; I have not done any testing or checked your code for any other problems.
If there might not be any background tasks to perform, you should test for that case and set the timeout to INFINITE rather than 0 when it occurs, otherwise you will be spinning.
I wanted to only show the minimal changes necessary to make it work, but calling WaitForSingleObject followed by GetOverlappedResult is redundant; a single call to GetOverlappedResult can both check whether the I/O is complete and retrieve the results if it is.
As requested, the modified version using only GetOverlappedResult and with minimal error checking. I've also added an example of how you might deal with the case where you've run out of work to do; if whatever processing you're doing on the files really does run forever, you don't need that bit.
::ResetEvent(ovl.hEvent);
if (!::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL))
{
error = GetLastError();
if (error != ERROR_IO_PENDING) fail();
}
while (1)
{
BOOL wait;
result = process_list_of_existing_files();
if (result == MORE_WORK_PENDING)
{
wait = FALSE;
}
else if (result == NO_MORE_WORK_PENDING)
{
wait = TRUE;
}
if (!::GetOverlappedResult(hDir, &ovl, &dw, wait))
{
error = GetLastError();
if (error == ERROR_IO_INCOMPLETE) continue;
fail();
}
processDirectoryChanges(buffer);
::ResetEvent(ovl.hEvent);
if (!::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL))
{
error = GetLastError();
if (error != ERROR_IO_PENDING) fail();
}
}
Variant of indirect using IOCP
Create a class/struct inherited (containing) OVERLAPPED (or
IO_STATUS_BLOCK), a reference counter, directory handle and data which
you need
Call BindIoCompletionCallback (RtlSetIoCompletionCallback) for
directory handle, for setup your callback
Have a DoRead() routine, which we'll call first-time from the main thread, and then from the callback
In DoRead(), before every call to ReadDirectoryChangesW call
AddRef(); because we pass reference (across OVERLAPPED) to our
struct to kernel
Main (say GUI thread) can continue to do own task after the initial call
to DoRead(), unlike the APC variant, we do not need to wait in alertable state
In the callback, we got a pointer to our struct from inherited (containing)
OVERLAPPED. Do any tasks (processDirectoryChanges), if need
continue spy - call DoRead() and finally call Release()
If ReadDirectoryChangesW from DoRead() fails (as result will be no callback) - we need direct call callback
with error code
For stopping we can simply close the directory handle - as a result, we got
STATUS_NOTIFY_CLEANUP in callback
==================================
//#define _USE_NT_VERSION_
class SPYDATA :
#ifdef _USE_NT_VERSION_
IO_STATUS_BLOCK
#else
OVERLAPPED
#endif
{
HANDLE _hFile;
LONG _dwRef;
union {
FILE_NOTIFY_INFORMATION _fni;
UCHAR _buf[PAGE_SIZE];
};
void DumpDirectoryChanges()
{
union {
PVOID buf;
PBYTE pb;
PFILE_NOTIFY_INFORMATION pfni;
};
buf = _buf;
for (;;)
{
DbgPrint("%x <%.*S>\n", pfni->Action, pfni->FileNameLength >> 1, pfni->FileName);
ULONG NextEntryOffset = pfni->NextEntryOffset;
if (!NextEntryOffset)
{
break;
}
pb += NextEntryOffset;
}
}
#ifdef _USE_NT_VERSION_
static VOID WINAPI _OvCompRoutine(
_In_ NTSTATUS dwErrorCode,
_In_ ULONG_PTR dwNumberOfBytesTransfered,
_Inout_ PIO_STATUS_BLOCK Iosb
)
{
static_cast<SPYDATA*>(Iosb)->OvCompRoutine(dwErrorCode, (ULONG)dwNumberOfBytesTransfered);
}
#else
static VOID WINAPI _OvCompRoutine(
_In_ DWORD dwErrorCode, // really this is NTSTATUS
_In_ DWORD dwNumberOfBytesTransfered,
_Inout_ LPOVERLAPPED lpOverlapped
)
{
static_cast<SPYDATA*>(lpOverlapped)->OvCompRoutine(dwErrorCode, dwNumberOfBytesTransfered);
}
#endif
VOID OvCompRoutine(NTSTATUS status, DWORD dwNumberOfBytesTransfered)
{
DbgPrint("[%x,%x]\n", status, dwNumberOfBytesTransfered);
if (0 <= status)
{
if (status != STATUS_NOTIFY_CLEANUP)
{
if (dwNumberOfBytesTransfered) DumpDirectoryChanges();
process_list_of_existing_files();// so hard do this here ?!?
DoRead();
}
else
{
DbgPrint("\n---- NOTIFY_CLEANUP -----\n");
}
}
Release();
MyReleaseRundownProtection();
}
~SPYDATA()
{
Cancel();
}
public:
void DoRead()
{
if (MyAcquireRundownProtection())
{
AddRef();
#ifdef _USE_NT_VERSION_
NTSTATUS status = ZwNotifyChangeDirectoryFile(_hFile, 0, 0, this, this, &_fni, sizeof(_buf), FILE_NOTIFY_VALID_MASK, TRUE);
if (NT_ERROR(status))
{
OvCompRoutine(status, 0);
}
#else
if (!ReadDirectoryChangesW(_hFile, _buf, sizeof(_buf), TRUE, FILE_NOTIFY_VALID_MASK, (PDWORD)&InternalHigh, this, 0))
{
OvCompRoutine(RtlGetLastNtStatus(), 0);
}
#endif
}
}
SPYDATA()
{
_hFile = 0;// ! not INVALID_HANDLE_VALUE because use ntapi for open file
_dwRef = 1;
#ifndef _USE_NT_VERSION_
RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
#endif
}
void AddRef()
{
InterlockedIncrement(&_dwRef);
}
void Release()
{
if (!InterlockedDecrement(&_dwRef))
{
delete this;
}
}
BOOL Create(POBJECT_ATTRIBUTES poa)
{
IO_STATUS_BLOCK iosb;
NTSTATUS status = ZwOpenFile(&_hFile, FILE_GENERIC_READ, poa, &iosb, FILE_SHARE_VALID_FLAGS, FILE_DIRECTORY_FILE);
if (0 <= status)
{
return
#ifdef _USE_NT_VERSION_
0 <= RtlSetIoCompletionCallback(_hFile, _OvCompRoutine, 0);
#else
BindIoCompletionCallback(_hFile, _OvCompRoutine, 0);
#endif
}
return FALSE;
}
void Cancel()
{
if (HANDLE hFile = InterlockedExchangePointer(&_hFile, 0))
{
NtClose(hFile);
}
}
};
void DemoF()
{
if (MyInitializeRundownProtection())
{
STATIC_OBJECT_ATTRIBUTES(oa, "<SOME_DIRECTORY>");
if (SPYDATA* p = new SPYDATA)
{
if (p->Create(&oa))
{
p->DoRead();
}
//++ GUI thread run
MessageBoxW(0, L"wait close program...", L"", MB_OK);
//-- GUI thread end
p->Cancel();
p->Release();
}
MyWaitForRundownProtectionRelease();
}
}

Read lines from file async using WINAPI ReadFile

I need to read several lines from file simultaneously, i.e. asynchronously.
Lines in file are of the same size.
For instance, I need to read the second and the fourth lines of file to separate variables or to an array.
I'm more used to c#'s async/await and all these OVERLAPPED things are a bit difficult for understanding to me.
Using msdn examples, I achieved this, it just reads the data from the file as a one string (is that even asynchronous reading?):
BOOL ReadFromFileAsync(PCTSTR path)
{
HANDLE hFile = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
_tprintf_s(TEXT("INVALID_HANDLE_VALUE\n"));
return FALSE;
}
BOOL bResult;
BYTE bReadBuf[2048];
OVERLAPPED oRead = { 0 };
oRead.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
bResult = ReadFile(hFile, bReadBuf, _countof(bReadBuf), NULL, &oRead);
if (!bResult && GetLastError() != ERROR_IO_PENDING)
{
_tprintf_s(TEXT("ERROR io pending"));
CloseHandle(hFile);
return FALSE;
}
// perform some stuff asynchronously
_tprintf_s(TEXT("HEY\n"));
HANDLE hEvents[2];
hEvents[0] = oRead.hEvent;
hEvents[1] = oRead.hEvent;
DWORD dwWaitRes = WaitForMultipleObjects(_countof(hEvents), hEvents, FALSE, INFINITE);
switch (dwWaitRes - WAIT_OBJECT_0)
{
case 0: // reading finished
_tprintf_s(TEXT("String that was read from file: "));
for (int i = 0; i < oRead.InternalHigh; ++i)
_tprintf_s(TEXT("%c"), bReadBuf[i]);
_tprintf_s(TEXT("\n"));
break;
default:
_tprintf_s(TEXT("Nooo"));
}
CloseHandle(hFile);
return TRUE;
}
Could you help me with reading two lines from file asynchronously?
Should I use SetFilePointer for that to move through the lines?
When you open a file with the FILE_FLAG_OVERLAPPED flag and then use an OVERLAPPED structure with ReadFile(), use the OVERLAPPED.Offset and OVERLAPPED.OffsetHigh fields to specify the byte offset where reading should start from. Also, you must use a separate OVERLAPPED instance for each ReadFile() if you run them simultaneously. This is clearly stated in the documentation:
If hFile is opened with FILE_FLAG_OVERLAPPED, the lpOverlapped parameter must point to a valid and unique OVERLAPPED structure, otherwise the function can incorrectly report that the read operation is complete.
For an hFile that supports byte offsets, if you use this parameter you must specify a byte offset at which to start reading from the file or device. This offset is specified by setting the Offset and OffsetHigh members of the OVERLAPPED structure. For an hFile that does not support byte offsets, Offset and OffsetHigh are ignored.
As your lines are the same length, you can easily calculate the offsets of the second and fourth lines and then issue two asynchronous ReadFile() calls for those offsets, and then wait for the two operations to complete as needed.
On a side note, you really should not be using FILE_FLAG_NO_BUFFERING unless you REALLY know what you are doing:
There are strict requirements for successfully working with files opened with CreateFile using the FILE_FLAG_NO_BUFFERING flag, for details see File Buffering.
Try something more like this instead:
#include <vector>
BOOL ReadFromFileAsync(PCTSTR path)
{
BOOL bResult = FALSE;
HANDLE hFile = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED /*| FILE_FLAG_NO_BUFFERING*/, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
_tprintf_s(TEXT("Error opening file: %s\n"), path);
return FALSE;
}
DWORD dwLineSize = ...; // size of each line, in bytes
std::vector<BYTE> bSecondLineBuf(dwLineSize);
std::vector<BYTE> bFourthLineBuf(dwLineSize);
OVERLAPPED oReadSecondLine = { 0 };
OVERLAPPED oReadFourthLine = { 0 };
oReadSecondLine.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!oReadSecondLine.hEvent)
{
_tprintf_s(TEXT("Error creating I/O event for reading second line\n"));
goto done;
}
oReadSecondLine.Offset = ...; // offset of second line
oReadFourthLine.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!oReadFourthLine.hEvent)
{
_tprintf_s(TEXT("Error creating I/O event for reading fourth line\n"));
goto done;
}
oReadFourthLine.Offset = ...; // offset of fourth line
if (!ReadFile(hFile, &bSecondLineBuf[0], dwLineSize, NULL, &oReadSecondLine))
{
if (GetLastError() != ERROR_IO_PENDING)
{
_tprintf_s(TEXT("Error starting I/O to read second line\n"));
goto done;
}
}
if (!ReadFile(hFile, &bFourthLineBuf[0], dwLineSize, NULL, &oReadFourthLine))
{
if (GetLastError() != ERROR_IO_PENDING)
{
_tprintf_s(TEXT("Error starting I/O to read fourth line\n"));
CancelIo(hFile);
goto done;
}
}
// perform some stuff asynchronously
_tprintf_s(TEXT("HEY\n"));
HANDLE hEvents[2];
hEvents[0] = oReadSecondLine.hEvent;
hEvents[1] = oReadFourthLine.hEvent;
DWORD dwWaitRes = WaitForMultipleObjects(_countof(hEvents), hEvents, TRUE, INFINITE);
if (dwWaitRes == WAIT_FAILED)
{
_tprintf_s(TEXT("Error waiting for I/O to finish\n"));
CancelIo(hFile);
goto done;
}
_tprintf_s(TEXT("Strings that were read from file: "));
for (int i = 0; i < oReadSecondLine.InternalHigh; ++i)
_tprintf_s(TEXT("%c"), (TCHAR) &bSecondLineBuf[i]);
_tprintf_s(TEXT("\n"));
for (int i = 0; i < oReadFourthLine.InternalHigh; ++i)
_tprintf_s(TEXT("%c"), (TCHAR) &bFourthLineBuf[i]);
_tprintf_s(TEXT("\n"));
done:
if (oReadSecondLine.hEvent) CloseHandle(oReadSecondLine.hEvent);
if (oReadFourthLine.hEvent) CloseHandle(oReadFourthLine.hEvent);
CloseHandle(hFile);
return bResult;
}
Alternatively:
#include <vector>
BOOL ReadFromFileAsync(PCTSTR path)
{
BOOL bResult = FALSE;
HANDLE hFile = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED /*| FILE_FLAG_NO_BUFFERING*/, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
_tprintf_s(TEXT("Error opening file: %s\n"), path);
return FALSE;
}
DWORD dwLineSize = ...; // size of each line, in bytes
std::vector<BYTE> bSecondLineBuf(dwLineSize);
std::vector<BYTE> bFourthLineBuf(dwLineSize);
OVERLAPPED oReadSecondLine = { 0 };
OVERLAPPED oReadFourthLine = { 0 };
oReadSecondLine.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!oReadSecondLine.hEvent)
{
_tprintf_s(TEXT("Error creating I/O event for reading second line\n"));
goto done;
}
oReadSecondLine.Offset = ...; // offset of second line
oReadFourthLine.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!oReadFourthLine.hEvent)
{
_tprintf_s(TEXT("Error creating I/O event for reading fourth line\n"));
goto done;
}
oReadFourthLine.Offset = ...; // offset of fourth line
if (!ReadFile(hFile, &bSecondLineBuf[0], dwLineSize, NULL, &oReadSecondLine))
{
if (GetLastError() != ERROR_IO_PENDING)
{
_tprintf_s(TEXT("Error starting I/O to read second line\n"));
goto done;
}
}
if (!ReadFile(hFile, &bFourthLineBuf[0], dwLineSize, NULL, &oReadFourthLine))
{
if (GetLastError() != ERROR_IO_PENDING)
{
_tprintf_s(TEXT("Error starting I/O to read fourth line\n"));
CancelIo(hFile);
goto done;
}
}
// perform some stuff asynchronously
_tprintf_s(TEXT("HEY\n"));
HANDLE hEvents[2];
hEvents[0] = oReadSecondLine.hEvent;
hEvents[1] = oReadFourthLine.hEvent;
OVERLAPPED* pOverlappeds[2];
pOverlappeds[0] = &oReadSecondLine;
pOverlappeds[1] = &oReadFourthLine;
BYTE* pBufs[2];
pBufs[0] = &bSecondLineBuf[0];
pBufs[1] = &bFourthLineBuf[0];
DWORD dwNumReads = _countof(hEvents);
do
{
DWORD dwWaitRes = WaitForMultipleObjects(dwNumReads, hEvents, FALSE, INFINITE);
if (dwWaitRes == WAIT_FAILED)
{
_tprintf_s(TEXT("Error waiting for I/O to finish\n"));
CancelIo(hFile);
goto done;
}
if ((dwWaitRes >= WAIT_OBJECT_0) && (dwWaitRes < (WAIT_OBJECT_0+dwNumReads)))
{
DWORD dwIndex = dwWaitRes - WAIT_OBJECT_0;
_tprintf_s(TEXT("String that was read from file: "));
for (int i = 0; i < pOverlappeds[dwIndex]->InternalHigh; ++i)
_tprintf_s(TEXT("%c"), (TCHAR) pBufs[dwIndex][i]);
_tprintf_s(TEXT("\n"));
--dwNumReads;
if (dwNumReads == 0)
break;
if (dwIndex == 0)
{
hEvents[0] = hEvents[1];
pOverlappeds[0] = pOverlappeds[1];
pBufs[0] = pBufs[1];
}
}
}
while (true);
done:
if (oReadSecondLine.hEvent) CloseHandle(oReadSecondLine.hEvent);
if (oReadFourthLine.hEvent) CloseHandle(oReadFourthLine.hEvent);
CloseHandle(hFile);
return bResult;
}