Yesterday I wrote about the evolution of work items. Work items evolved because there was a need to have a reference on the device object until the work item's callback routine returned. As I illustrated yesterday, there is a small piece of assembly in between the last line of code and returning to the caller. The solution was that the I/O manager would maintain this reference and release it after the function returned. Unfortunately this particular issue is not limited to work items, it is also an issue for completion routines.

To solve this problem IoSetCompletionRoutineEx() was introduced in Windows XP. You should use it if possible, but BE WARNED, IoSetCompletionRoutineEx() is not a drop in replacement for IoSetCompletionRoutine()!!! If you look at the signature for IoSetCompletionRoutineEx() you have:

        PDEVICE_OBJECT DeviceObject,
        PIRP Irp,
        PIO_COMPLETION_ROUTINE CompletionRoutine,
        PVOID Context,
        BOOLEAN InvokeOnSuccess,
        BOOLEAN InvokeOnError,
        BOOLEAN InvokeOnCancel
Note that while IoSetCompletionRoutine() does not have a return value, while IoSetCompletionRoutineEx() does. It returns an NTSTATUS. This means that the call can now fail, so you cannot just do a string replacement for the Ex() version, you must also add code that captures the return value and reacts appropriately when it returns !NT_SUCCESS().

Checking the return value is not enough though! Internally IoSetCompletionRoutineEx() allocates memory so that it can do its work and this memory is freed when the completion routine is invoked. This means that your driver must call IoCallDriver() after successfully calling IoSetCompletionRoutineEx(). Otherwise, your driver will cause a memory leak (which is not detectable by driver verifier because the allocation occurred in the kernel itself, not your driver). This requirement might make your driver's PIRP management more complicated, it certainly had that affect on KMDF when I discovered that this requirement existed.