TLDR: Trying to model an inverted IOCTL flow where my application will pend two asynchronous request to driver. Each pended request can invoke completion handler concurrently and should not corrupt the data returned by driver.
Long post:
I’ve been experimenting with ThreadpoolIo functions and encountered a challenge in accessing the output buffer in the completion routine when an asynchronous operation is completed. Here’s what I’ve accomplished so far:
Created a dedicated thread with the responsibility to:
Open a handle to the console
Create a ThreadPoolIo object
Send two asynchronous requests (using ReadFile) to the device (in this case terminal/console)
Wait for user input, and upon completion, trigger the completion routine
Ensure that the buffer populated by ReadFile is visible in the completion routine.
To avoid potential race conditions and data corruption, I cannot use a single buffer for two separate threads in the ThreadPool. Introducing locks would serialize the completion routine. Therefore, I’m aiming for concurrent execution of the completion routine by employing two separate buffers to prevent race conditions.
My question is :
1. How to access the data populated by ReadFile function?
2. Is there any way to extract information about which async operation has completed(1st or 2nd in my example)
I know this is not a proper use of ThreadpoolIO, this is just an experiment to try out of threadpoolIo function. The actual device to which readfile will be issued can’t be shown in the example due to proprietary reason.
#define MAX_NUM_REQUEST (2)
VOID CALLBACK mycompletionRoutine(
PTP_CALLBACK_INSTANCE pInstance, // See "Callback Termination Actions" section
PVOID pvContext,
PVOID pOverlapped,
ULONG IoResult,
ULONG_PTR NumberOfBytesTransferred,
PTP_IO pIo)
{
if (IoResult == NO_ERROR)
{
**// How can I access the buff[0] and buff[1] in which readfile stores the data?**
}
else
{
std::cout << "Completeion routine failure" << std::endl;
}
}
DWORD WINAPI ReadThread(LPVOID lpParam)
{
// Open handle to existing file in overlapped mode
HANDLE hFile = CreateFile( L"CONIN$", // This refers to console input
GENERIC_READ, // open for reading
0, // do not share
NULL, // default security
OPEN_EXISTING, // open existing file only
FILE_FLAG_OVERLAPPED, // overlapped/async operation
NULL); // no attr. template
if (hFile == INVALID_HANDLE_VALUE)
{
std::cout << "Invalid Handle value" << std::endl;
return -1;
}
// Buffer in which content from console will be stored,maximum 1024 characters will be read. Two separate buffer for each read request
char buff[MAX_NUM_REQUEST][1024] = { 0 };
// Create a thread pool object and associate it with a file handle.
PTP_IO thread_pool_obj = CreateThreadpoolIo(hFile, mycompletionRoutine, NULL, NULL);
if (thread_pool_obj == NULL)
{
DWORD status = GetLastError();
std::cout << "Thread pool obj is NULL" << status << std::endl;
return -1;
}
// Always issue two asynchronous request at a time.
while (1)
{
OVERLAPPED overlapped[MAX_NUM_REQUEST] = {};
HANDLE overlappedEvent[MAX_NUM_REQUEST];
for (int i = 0; i < MAX_NUM_REQUEST; i++)
{
// Start thread pool object, as per MSDN StartThreadpoolIo must be called before issuing read/write request
StartThreadpoolIo(thread_pool_obj);
// Issue a async read request
overlappedEvent[i] = CreateEventW(NULL, FALSE, FALSE, NULL);
overlapped[i].hEvent = overlappedEvent[i];
bool ret = ReadFile(hFile, &buff[i], 100, NULL, &overlapped[i]);
if (ret == 0)
{
DWORD status = GetLastError();
if (status != ERROR_IO_PENDING)
{
std::cout << "IO operation failed" << std::endl;
return -1;
}
}
}
Sleep(5000);
}
return 0;
}
int main()
{
HANDLE ReadThreadHandle = CreateThread(NULL,0, ReadThread,NULL,0,NULL);
if(ReadThreadHandle != NULL)
{
std::cout << "Thread creation is successfull" << std::endl;
// Wait until thread has terminated.
WaitForSingleObject(ReadThreadHandle, INFINITE);
// Close thread handle upon completion.
CloseHandle(ReadThreadHandle);
std::cout << "Program ran successfully" << std::endl;
}
else
{
std::cout << "Thread creation failed" << std::endl;
}
return 0;
}