Your completion routine context can be anything you want
It sounds obvious, but sometimes it needs to be stated. For instance, let's say that you are allocating your own IRP, your context contains I/O related data (like a URB) and you encounter the issue where the DeviceObject passed to your I/O completion routine is NULL. Adding another stack location is one solution I wrote about, but it is a little complicated and has a certain black magic feeling to it, besides it does not work with any IoBuildXxx API calls. Since you are allocating memory for your context, just allocate a little bit more. Instead of allocating just the URB
PURB urb = ExAllocatePoolWithTag(NonPagedPool, sizeof(URB), ...);
PIRP irp = IoAllocateIrp(...);
...
// format the URB
IoSetCompletionRoutine(Irp, CompletionRoutine, urb, TRUE, TRUE, TRUE);
...
IoCallDriver(..., Irp);
...
NTSTATUS CompletionRoutine(PDEVIC_OBJECT Device, PIRP Irp, PURB Urb)
{
// Do not touch Device, it is NULL!
// Process Urb and Irp results
ExFreePool(Urb);
IoFreeIrp(Irp);
return STATUS_MORE_PROCESSING_REQUIRED;
}
allocate a structure to contain the URB and your DeviceObject pointer (or WDFDEVICE or anything else)
typedef struct _COMPLETION_DATA {
PDEVICE_OBJECT DeviceObject;
URB Urb;
} COMPLETION_DATA, *PCOMPLETION_DATA;
PCOMPLETION_DATA data = ExAllocatePoolWithTag(NonPagedPool, sizeof(COMPLETION_DATA), ...);
PIRP irp = IoAllocateIrp(...);
...
// format data->Urb
data->DeviceObject = DeviceObject;
IoSetCompletionRoutine(Irp, CompletionRoutine, data, TRUE, TRUE, TRUE);
...
IoCallDriver(..., Irp);
...
NTSTATUS CompletionRoutine(PDEVIC_OBJECT Device, PIRP Irp, PCOMPLETION_DATA Data)
{
// Do not touch Device, it is NULL, but Data->Device is valid!
// Process Data->Urb and Irp results
ExFreePool(Data);
IoFreeIrp(Irp);
return STATUS_MORE_PROCESSING_REQUIRED;
}
Like I said, it is an obvious thing to do :), but when you are in the depths of debugging a bugcheck, sometimes the obvious solution is the last thing you think of.