I open the file with:
driverHandle_ = CreateFile(
path,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_WRITE | FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
nullptr);
and I associate it with a completion port like this:
completionPortHandle_ = CreateIoCompletionPort(driverHandle_, nullptr, 0, 0);
I only have one thread calling GetQueuedCompletionStatus
, but I still pass 0
, as the last parameter to CreateIoCompletionPort
to set NumberOfConcurrentThreads
. Does it matter what I pass if I only have one thread calling CreateIoCompletionPort
?
Here is a stipped down version of what I beleive is the interesting parts of how I call DeviceIOControl
:
typedef void (*AsyncNotificationFunctionPtr)(void* ref);
struct AsyncNotificationContext {
OVERLAPPED Overlapped{};
AsyncNotificationFunctionPtr funcPtr;
void* ref = nullptr;
};
WindowsAudioClient::AudioCallbackBuffers::consumeDataFromDevice() {
// Do processing, omitted.
// Send DeviceIoControl with OVERLAPPED to queue up another WDFREQUEST in the driver
// so that this buffer can be notified when the driver write data to it.
memset(&asyncNotificationContext_->Overlapped, 0, sizeof(OVERLAPPED));
asyncNotificationContext_.funcPtr = &audioCallbackFunction;
asyncNotificationContext_.ref = this;
DeviceIoControl(driverHandle_, IOCTL_AUDIO_ARM_CALLBACK,
&index_, // Index if this buffer
sizeof(int),
nullptr,
0,
nullptr, // BytesReturned
&AsyncNotificationContext_->Overlapped); // Ptr to Overlapped structure
// Send another message to the driver telling it that the user mode app is done processing.
// This call sometimes never return!
DeviceIoControl(driverHandle_, IOCTL_AUDIO_MARK_BUFFER_AS_CONSUMED,
&index, sizeof(index), nullptr, 0, nullptr, nullptr))
}
void audioCallbackFunction(void* const ref) {
static_cast<AudioCallbackBuffers*>(ref)->consumeDataFromDevice();
}
// Part of the function that calls GetQueuedCompletionStatus
:
while (!threadShouldExit_.load()) {
DWORD byteCount = 0;
ULONG_PTR compKey = 0;
OVERLAPPED* overlapped = nullptr;
// When GetQueuedCompletionStatus return it is the IOCTL_AUDIO_ARM_CALLBACK that is completed.
BOOL worked =
GetQueuedCompletionStatus(completionPortHandle_, &byteCount, &compKey, &overlapped, 1000);
if (byteCount == 0 || !worked || overlapped == nullptr) {
continue;
}
const auto* const wrap = reinterpret_cast<AsyncNotificationContext*>(overlapped);
wrap->funcPtr(wrap->ref); // Calls audioCallbackFunction
}
// This method is called from some other thread (not the thead that in in the while loop above)
void WindowsAudioClient::stopAudio() {
OVERLAPPED overlapped{};
overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
// driverHandle_ is the same handle that is in each AudioCallbackBuffers
DeviceIoControl(driverHandle_, IOCTL_AUDIO_STOP, nullptr, 0, nullptr, 0,nullptr, &overlapped);
// Error handling ommitted...
WaitForSingleObject(overlapped.hEvent, INFINITE);
CloseHandle(overlapped.hEvent);
return true;
}
---------- Driver code ---------
I have two queues:
- The default queue is setup to process DeviceIOControl messages with
WdfIoQueueDispatchParallel
.
nonAudioQueue
is setup to process with WdfIoQueueDispatchSequential
.
VOID USBDriverDefaultEvtIoDeviceControl(_In_ WDFQUEUE Queue, _In_ WDFREQUEST Request,
_In_ size_t OutputBufferLength,
_In_ size_t InputBufferLength,
_In_ ULONG IoControlCode) {
switch (IoControlCode) {
case IOCTL_AUDIO_MARK_BUFFER_AS_CONSUMED: {
// Get the index, move one int from one list to the other, then call WdfRequestCompleteWithInformation(...)
// No mutex, spinlock is taken and there is no loop that we cannot get out of.
break;
}
case IOCTL_AUDIO_ARM_CALLBACK: {
// Store the WDFREQUEST on a WDFQUEUE.
break;
}
default: {
if (const auto status =
WdfRequestForwardToIoQueue(Request, getDeviceContext(Queue)->nonAudioQueue);
!NT_SUCCESS(status)) {
// Log error...
}
}
}
}
The EvtIoDeviceControl for the other queue is not so interesting (I can see that WWdfRequestCompleteWithInformation is called for the WDFREQUEST for IOCTL_AUDIO_STOP
in logging)