In order to address these two issues, several new routines were added to make things more flexible:
A driver can now use IoSizeofWorkItem() and IoInitializeWorkItem() to allocate and initialize their own work items and embed them in a structure such as the following (which you could have at the end of a device extension, for example):
typedef struct _WORK_ITEM_CONTEXT { ULONG Size; ULONG Context; UCHAR WorkItemStart[1]; } WORK_ITEM_CONTEXT, *PWORK_ITEM_CONTEXT; PWORK_ITEM_CONTEXT pContext; ULONG size; // // Compute the size up to the variable length array // size = FIELD_OFFSET(WORK_ITEM_CONTEXT, WorkItemStart); // // Now add the size of the work item // size += IoSizeofWorkItem(); pContext = ExAllocatePoolWithTag(NonPagedPool, size, <tag>); if (pContext == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } pContext->Size = size; // // And now we need to initialize the work item itself // IoInitializeWorkItem(DeviceObject, (PIO_WORKITEM) &pContext->WorkItemStart[0]);
IoUninitializeWorkItem((PIO_WORKITEM) &pContext->WorkItemStart[0]);
This means the signature for the callback that you pass as a parameter to IoQueueWorkItemEx() is different than the callback you used to pass as a parameter to IoQueueWorkItem(). The IoQueueWorkItemEx() callback takes the extra work item parameter and has a PVOID as the first entry (so it can handle either a driver object or a device object):
typedef VOID IO_WORKITEM_ROUTINE_EX ( __in PVOID IoObject, __in_opt PVOID Context, __in PIO_WORKITEM IoWorkItem );
IoQueueWorkItemEx((PIO_WORKITEM) &pContext->WorkItemStart[0], WorkerRoutine, DelayedWorkQueue, SomeOtherContext);
VOID WorkItemRoutine ( __in PVOID IoObject, __in_opt PVOID Context, __in PIO_WORKITEM IoWorkItem ) { PWORK_ITEM_CONTEXT pWorkContext; pWorkContext = CONTAINING_RECORD(IoWorkItem, WORK_ITEM_CONTEXT, WorkItemStart); // ... do work ... IoUninitializeWorkItem(IoWorkItem); ExFreePool(pWorkContext); pWorkContext = NULL; }