将 IRP 与 Winsock 内核函数配合使用

NPI (WSK) 网络编程接口的 Winsock 内核 (NPI) 使用 IIP 异步完成网络 I/O 操作。 每个 WSK 函数都采用指向 IRP 的指针作为参数。 WSK 子系统在 WSK 函数执行的操作完成后完成 IRP。

WSK 应用程序用于传递给 WSK 函数的 IRP 可以源自以下方式之一。

  • WSK 应用程序通过调用 IoAllocateIrp 函数来分配 IRP。 在这种情况下,WSK 应用程序必须分配具有至少一个 I/O 堆栈位置的 IRP。

  • WSK 应用程序重用以前分配的已完成 IRP。 在这种情况下,WSK 必须调用 IoReuseIrp 函数来重新初始化 IRP。

  • WSK 应用程序使用由更高级别驱动程序或 I/O 管理器向下传递到它的 IRP。 在这种情况下,IRP 必须至少有一个剩余的 I/O 堆栈位置可供 WSK 子系统使用。

在 WSK 应用程序具有用于调用 WSK 函数的 IRP 后,它可以设置 一个 IoCompletion 例程,供 WSK 子系统在 IRP 完成时调用 IRP。 WSK 应用程序通过调用 IoSetCompletionRoutine 函数为 IRP 设置 IoCompletion 例程。 IoCompletion 例程是必需的或可选的,具体取决于 IRP 的发起方式。

  • 如果 WSK 应用程序分配了 IRP,或者正在重新使用以前分配的 IRP,则它必须在调用 WSK 函数之前为 IRP 设置 IoCompletion 例程。 在这种情况下,WSK 应用程序必须为传递给 IoSetCompletionRoutine 函数的 InvokeOnSuccessInvokeOnErrorInvokeOnCancel 参数指定 TRUE,以确保始终调用 IoCompletion 例程。 此外,为 IRP 设置的 IoCompletion 例程必须始终返回 STATUS_MORE_PROCESSING_REQUIRED以终止 IRP 的完成处理。 如果在调用 IoCompletion 例程后使用 IRP 完成了 WSK 应用程序,则它应在从 IoCompletion 例程返回之前调用 IoFreeIrp 函数以释放 IRP。 如果 WSK 应用程序未释放 IRP,则它可以重复使用 IRP 来调用另一个 WSK 函数。

  • 如果 WSK 应用程序使用由更高级别驱动程序或 I/O 管理器向下传递给它的 IRP,则它应在调用 WSK 函数之前为 IRP 设置 IoCompletion 例程,仅在 WSK 函数执行的操作完成时必须通知它。 如果 WSK 应用程序未为 IRP 设置 IoCompletion 例程,则 IRP 完成后,IRP 将按常规 IRP 完成处理过程传递回更高级别的驱动程序或 I/O 管理器。 如果 WSK 应用程序为 IRP 设置 IoCompletion 例程, 则 IoCompletion 例程可以返回STATUS_SUCCESS或STATUS_MORE_PROCESSING_REQUIRED。 如果 IoCompletion 例程返回STATUS_SUCCESS,则 IRP 完成处理将继续正常。 如果 IoCompletion 例程返回 STATUS_MORE_PROCESSING_REQUIRED,则 WSK 应用程序必须在处理完 WSK 函数执行的操作结果后,通过调用 IoCompleteRequest 完成 IRP。 WSK 应用程序不应释放由更高级别驱动程序或 I/O 管理器向下传递的 IRP。

注意 如果 WSK 应用程序为由更高级别驱动程序或 I/O 管理器传递给它的 IRP 设置 IoCompletion 例程,则 IoCompletion 例程必须检查 IRP 的 PendingRe且 调用 IoMarkIrpPending 函数(如果 PendingRe且 member为 TRUE)。 有关详细信息,请参阅 实现 IoCompletion 例程

注意 WSK 应用程序不应在 IoCompletion 例程的上下文中调用新的 WSK 函数。 这样做可能会导致递归调用并耗尽内核模式堆栈。 在 IRQL = DISPATCH_LEVEL时,这还可能导致其他线程资源不足。

WSK 应用程序不会初始化除设置 IoCompletion 例程外传递给 WSK 函数的 IIP。 当 WSK 应用程序将 IRP 传递给 WSK 函数时,WSK 子系统将代表应用程序设置下一个 I/O 堆栈位置。

下面的代码示例演示 WSK 应用程序如何在套接字上执行接收操作时分配和使用 IRP。

// Prototype for the receive IoCompletion routine
NTSTATUS
  ReceiveComplete(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PVOID Context
    );

// Function to receive data
NTSTATUS
  ReceiveData(
    PWSK_SOCKET Socket,
    PWSK_BUF DataBuffer
    )
{
  PWSK_PROVIDER_CONNECTION_DISPATCH Dispatch;
  PIRP Irp;
  NTSTATUS Status;

  // Get pointer to the provider dispatch structure
  Dispatch =
    (PWSK_PROVIDER_CONNECTION_DISPATCH)(Socket->Dispatch);

  // Allocate an IRP
  Irp =
    IoAllocateIrp(
      1,
      FALSE
      );

  // Check result
  if (!Irp)
  {
    // Return error
    return STATUS_INSUFFICIENT_RESOURCES;
  }

  // Set the completion routine for the IRP
  IoSetCompletionRoutine(
    Irp,
    ReceiveComplete,
    DataBuffer,  // Use the data buffer for the context
    TRUE,
    TRUE,
    TRUE
    );

  // Initiate the receive operation on the socket
  Status =
    Dispatch->WskReceive(
      Socket,
      DataBuffer,
      0,  // No flags are specified
      Irp
      );

  // Return the status of the call to WskReceive()
  return Status;
}

// Receive IoCompletion routine
NTSTATUS
  ReceiveComplete(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PVOID Context
    )
{
  UNREFERENCED_PARAMETER(DeviceObject);

  PWSK_BUF DataBuffer;
  ULONG ByteCount;

  // Check the result of the receive operation
  if (Irp->IoStatus.Status == STATUS_SUCCESS)
  {
    // Get the pointer to the data buffer
    DataBuffer = (PWSK_BUF)Context;
 
    // Get the number of bytes received
    ByteCount = (ULONG)(Irp->IoStatus.Information);

    // Process the received data
    ...
  }

  // Error status
  else
  {
    // Handle error
    ...
  }

  // Free the IRP
  IoFreeIrp(Irp);

  // Always return STATUS_MORE_PROCESSING_REQUIRED to
  // terminate the completion processing of the IRP.
  return STATUS_MORE_PROCESSING_REQUIRED;
}

上一个示例中显示的模型(其中 WSK 应用程序分配 IRP,然后在完成例程中释放它)是整个 WSK 文档的其余部分中示例中使用的模型。

下面的代码示例演示在套接字上执行接收操作时,WSK 应用程序如何使用更高级别驱动程序或 I/O 管理器传递给它的 IRP。

// Prototype for the receive IoCompletion routine
NTSTATUS
  ReceiveComplete(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PVOID Context
    );

// Function to receive data
NTSTATUS
  ReceiveData(
    PWSK_SOCKET Socket,
    PWSK_BUF DataBuffer,
    PIRP Irp;  // IRP from a higher level driver or the I/O manager
    )
{
  PWSK_PROVIDER_CONNECTION_DISPATCH Dispatch;
  NTSTATUS Status;

  // Get pointer to the provider dispatch structure
  Dispatch =
    (PWSK_PROVIDER_CONNECTION_DISPATCH)(Socket->Dispatch);

  // Set the completion routine for the IRP such that it is
  // only called if the receive operation succeeds.
  IoSetCompletionRoutine(
    Irp,
    ReceiveComplete,
    DataBuffer,  // Use the data buffer for the context
    TRUE,
    FALSE,
    FALSE
    );

  // Initiate the receive operation on the socket
  Status =
    Dispatch->WskReceive(
      Socket,
      DataBuffer,
      0,  // No flags are specified
      Irp
      );

  // Return the status of the call to WskReceive()
  return Status;
}

// Receive IoCompletion routine
NTSTATUS
  ReceiveComplete(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PVOID Context
    )
{
  UNREFERENCED_PARAMETER(DeviceObject);

  PWSK_BUF DataBuffer;
  ULONG ByteCount;

  // Since the completion routine was only specified to
  // be called if the operation succeeds, this should
  // always be true.
  ASSERT(Irp->IoStatus.Status == STATUS_SUCCESS);

  // Check the pending status of the IRP
  if (Irp->PendingReturned == TRUE)
  {
    // Mark the IRP as pending
    IoMarkIrpPending(Irp);
  }

  // Get the pointer to the data buffer
  DataBuffer = (PWSK_BUF)Context;
 
  // Get the number of bytes received
  ByteCount = (ULONG)(Irp->IoStatus.Information);

  // Process the received data
  ...

  // Return STATUS_SUCCESS to continue the
  // completion processing of the IRP.
  return STATUS_SUCCESS;
}

有关使用 IRPs 的信息,请参阅 处理 IRPs