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

Winsock 内核 (WSK) 网络编程接口 (NPI) 使用 IRP 异步完成网络 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 后,它可以设置 IRP 的 IoCompletion 例程,以便在 IRP 由 WSK 子系统完成时调用。 WSK 应用程序通过调用 IoSetCompletionRoutine 函数为 IRP 设置 IoCompletion 例程。 根据 IRP 的产生方式, IoCompletion 例程是必需或可选的。

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

  • 如果 WSK 应用程序使用由更高级别的驱动程序或 I/O 管理器传递给它的 IRP,则只有在 WSK 函数执行的操作完成时,它才应在调用 WSK 函数之前为该 IRP 设置 IoCompletion 例程。 如果 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 的 PendingReturned 成员,并在 PendingReturned 成员为 TRUE 时调用 IoMarkIrpPending 函数。 有关详细信息,请参阅 实现 IoCompletion 例程

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

除了设置 IoCompletion 例程之外,WSK 应用程序不会初始化传递给 WSK 函数的 IRP。 当 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;
}

有关使用 IRP 的详细信息,请参阅 处理 IRP