Usando IRPs com funções de kernel Winsock

A NPI (Interface de Programação de Rede ) do WSK (Winsock Kernel) usa IRPs para conclusão assíncrona de operações de E/S de rede. Cada função do WSK usa um ponteiro para um IRP como um parâmetro. O subsistema WSK conclui o IRP depois que a operação executada pela função WSK é concluída.

Um IRP que um aplicativo WSK usa para passar para uma função do WSK pode se originar de uma das seguintes maneiras.

  • O aplicativo WSK aloca o IRP chamando a função IoAllocateIrp . Nessa situação, o aplicativo WSK deve alocar o IRP com pelo menos um local de pilha de E/S.

  • O aplicativo WSK reutiliza um IRP concluído que ele alocou anteriormente. Nessa situação, o WSK deve chamar a função IoReuseIrp para reinicializar o IRP.

  • O aplicativo WSK usa um IRP que foi passado para ele por um driver de nível mais alto ou pelo gerente de E/S. Nessa situação, o IRP deve ter pelo menos um local de pilha de E/S restante disponível para uso pelo subsistema WSK.

Depois que um aplicativo WSK tem um IRP a ser usado para chamar uma função WSK, ele pode definir uma rotina IoCompletion para que o IRP seja chamado quando o IRP for concluído pelo subsistema WSK. Um aplicativo WSK define uma rotina IoCompletion para um IRP chamando a função IoSetCompletionRoutine . Dependendo de como o IRP se originou, uma rotina IoCompletion é necessária ou opcional.

  • Se o aplicativo WSK tiver alocado o IRP ou estiver reutilizando um IRP alocado anteriormente, ele deverá definir uma rotina IoCompletion para o IRP antes de chamar uma função WSK. Nessa situação, o aplicativo WSK deve especificar TRUE para os parâmetros InvokeOnSuccess, InvokeOnError e InvokeOnCancel que são passados para a função IoSetCompletionRoutine para garantir que a rotina IoCompletion seja sempre chamada. Além disso, a rotina IoCompletion definida para o IRP sempre deve retornar STATUS_MORE_PROCESSING_REQUIRED para encerrar o processamento de conclusão do IRP. Se o aplicativo WSK for feito usando o IRP depois que a rotina IoCompletion tiver sido chamada, ele deverá chamar a função IoFreeIrp para liberar o IRP antes de retornar da rotina IoCompletion . Se o aplicativo WSK não liberar o IRP, ele poderá reutilizar o IRP para uma chamada para outra função do WSK.

  • Se o aplicativo WSK usar um IRP que foi passado para ele por um driver de nível mais alto ou pelo gerenciador de E/S, ele deverá definir uma rotina de IoCompletion para o IRP antes de chamar a função WSK somente se ela precisar ser notificada quando a operação executada pela função WSK for concluída. Se o aplicativo WSK não definir uma rotina IoCompletion para o IRP, quando o IRP for concluído, o IRP será passado de volta para o driver de nível mais alto ou para o gerenciador de E/S de acordo com o processamento normal de conclusão de IRP. Se o aplicativo WSK definir uma rotina IoCompletion para o IRP, a rotina IoCompletion poderá retornar STATUS_SUCCESS ou STATUS_MORE_PROCESSING_REQUIRED. Se a rotina IoCompletion retornar STATUS_SUCCESS, o processamento de conclusão do IRP continuará normalmente. Se a rotina IoCompletion retornar STATUS_MORE_PROCESSING_REQUIRED, o aplicativo WSK deverá concluir o IRP chamando IoCompleteRequest depois de concluir o processamento dos resultados da operação que foi executada pela função WSK. Um aplicativo WSK nunca deve liberar um IRP que foi passado para ele por um driver de nível mais alto ou pelo gerente de E/S.

Nota Se o aplicativo WSK definir uma rotina IoCompletion para um IRP que foi passado para ele por um driver de nível mais alto ou pelo gerenciador de E/S, a rotina IoCompletion deverá marcar o membro PendingReturned do IRP e chamar a função IoMarkIrpPending se o membro PendingReturned for TRUE. Para obter mais informações, consulte Implementando uma rotina IoCompletion.

Nota Um aplicativo WSK não deve chamar novas funções do WSK no contexto da rotina IoCompletion . Isso pode resultar em chamadas recursivas e esgotar a pilha do modo kernel. Ao executar em IRQL = DISPATCH_LEVEL, isso também pode levar à falta de outros threads.

Um aplicativo WSK não inicializa os IRPs que ele passa para as funções do WSK além de definir uma rotina IoCompletion . Quando um aplicativo WSK passa um IRP para uma função WSK, o subsistema WSK configura o próximo local de pilha de E/S em nome do aplicativo.

O exemplo de código a seguir mostra como um aplicativo WSK pode alocar e usar um IRP ao executar uma operação de recebimento em um soquete.

// 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;
}

O modelo mostrado no exemplo anterior, em que o aplicativo WSK aloca um IRP e o libera na rotina de conclusão, é o modelo usado nos exemplos durante todo o restante da documentação do WSK.

O exemplo de código a seguir mostra como um aplicativo WSK pode usar um IRP que foi passado para ele por um driver de nível mais alto ou pelo gerenciador de E/S ao executar uma operação de recebimento em um soquete.

// 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;
}

Para obter mais informações sobre como usar IRPs, consulte Manipulando IRPs.