IoSetCompletionRoutineEx is not a drop in replacement for IoSetCompletionRoutine

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:

 
    NTKERNELAPI
    NTSTATUS
    IoSetCompletionRoutineEx(
        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.