When you learn about WdfRequestSend, you typically learn that there are three different ways that you can send a Request to an I/O Target:
- Sending a Request synchronously,;
- Sending a Request asynchronously, and telling the framework to thereafter disregard it. This is called “Send and Forget” processing. In this case, your driver does not receive a callback at its Completion Routine Event Processing Callback when the Request has been completed.
- Sending a Request asynchronously, and asking the Framework to provide you with a callback when the Request is complete.
It makes a nice story to describe these three options as equally viable for a driver writer. Unfortunately, in the majority of cases the only practical option is to send a Request asynchronously and specify a Completion Routine Event Processing Callback.
Why is this so?
Let’s start by looking at the synchronous option. You instruct WdfRequestSend to operate synchronously by setting WDF_REQUEST_SEND_OPTION_SYNCHRONOUS in the Flags field of the WDF_REQUEST_SEND options structure. The problem with sending Requests synchronously is that you can only do this at IRQL PASSIVE_LEVEL. In WDF, that means you must have previously set an IRQL PASSIVE_LEVEL Execution Level Constraint that willguarantee that the routine from which you call WdfRequestSend with _OPTION_SYNCHRONOUS will run at IRQL PASSIVE_LEVEL. New WDF driver devs often fail to realize that even though their EvtIo Event Processing Callbacks are actually running at IRQL PASSIVE_LEVE, execution at this IRQL is only guaranteed by setting an appropriate Execution Level constraint. And, unfortunately, SDV doesn’t flag as an error synchronous calls to WdfRequestSend without a PASSIVE_LEVEL Execution Level constraint being set. So why not just set a PASSIVE_LEVEL Execution Level constraint? Well, depending on where you are in the device tree you may not want to do this because it can introduce additional overhead to your Request handling.
If the synchronous option is not always advisable or possible, what’s the problem with using “Send and Forget” processing? The “Send and Forget” style of WdfRequestSend is requested by setting WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET in the Flags field of the WDF_REQUEST_SEND options structure. When you initially read about it, the ability to send a Request and not get wait for it to complete and not get a completion callback sounds like it might be pretty useful in a number of situations. For the most part, however, this style of send is mostly intended to be used by Filter Drivers sending Requests they do not handle down their own Device Stack. For this use, “Send and Forget” works great. Unfortunately, there are a significant number of limitations on other uses for “Send and Forget.” You can’t use it to send Requests that you create in your driver (i.e. Requests that are not queue presented). And you can’t use it to send Requests to Remote I/O Targets that you’ve opened by name (that is, you’ve done the equivalent of initializing a WDF_IO_TARGET_OPEN structure with the WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME macro). There are other restrictions as well. So, you really DO have to be careful with “Send and Forget” style operations.
So that leaves us with the third style of calling WdfRequestSend: Sending the Request asynchronously, and asking for a callback when the Request is finished. This is done by specifying a Completion Routine Event Processing Callback, and then calling WdfRequestSend using the default processing option in a WDF_REQUEST_SEND options structure (or not specifying a WDF_REQUEST_SEND options structure at all). Asynchronous with callback is the most generally useful variant of WdfRequestSend. You can use it at any IRQL. The I/O Target can be your local I/O target or a remote I/O target. If it’s remote I/O target the target can be opened any way.