そのハンドルはまだ有効ですか?

はじめに : 5 つのシナリオ

カーネル モード ドライバ フレームワーク (KMDF) で、WDFREQUEST オブジェクトは、I/O 要求を表します。各 WDFREQUEST オブジェクトは 1 つ以上の WDFMEMORY オブジェクトに関連付けられ、各 WDFMEMORY オブジェクトは要求で入力または出力に使用されるバッファを表します。下図はこれらの関係を示します。

WDFmem1.gif
クリックしてフル サイズの画像を表示

KMDF が着信 I/O 要求を表すために WDFREQUEST と WDFMEMORY オブジェクトを作成するとき、WDFREQUEST オブジェクトを関連付けられた WDFMEMORY オブジェクトの親として設定します。したがって、WDFMEMORY オブジェクトは、WDFREQUEST オブジェクトの有効期間より長くは持続できません。KMDF ドライバが I/O 要求を完了すると、フレームワークは WDFREQUEST オブジェクトと WDFMEMORY オブジェクトを削除します。そのため、これら 2 つのオブジェクトへのハンドルは無効になります。

ただし、基礎をなしているバッファは、異なります。どのコンポーネントがバッファを作成したか、そしてバッファがどう作成されたかによって、バッファは参照カウントを持っている場合があり、WDFMEMORY オブジェクトによって "所有される" 場合があります (そうでない場合もあります)。WDFMEMORY オブジェクトがバッファを所有する場合、バッファは参照カウントを持っており、その有効期間は、WDFMEMORY オブジェクトの有効期間に限定されます。他の一部のコンポーネントがバッファを作成した場合、バッファの有効期間と WDFMEMORY オブジェクトの有効期間は無関係です。

また、KMDF ドライバは、独自の WDFREQUEST オブジェクトを作成して、I/O ターゲットに送信できます。ドライバによって作成された要求は、ドライバが I/O 要求で受信した既存の WDFMEMORY オブジェクトを再利用できます。要求を頻繁に I/O ターゲットへ送信するドライバは、自ら作成した WDFREQUEST オブジェクトを再利用できます。ただし、KMDF から受信する WDFREQUEST オブジェクトを再利用することはできません。

WDFREQUEST オブジェクトの有効期間、WDFMEMORY オブジェクト、および基礎をなしているバッファを理解することは、ドライバが無効なハンドルやバッファ ポインタを参照するのを防ぐのに重要です。

次のような利用シナリオを考えてみましょう :

シナリオ 1: ドライバが KMDF から I/O 要求を受け取り、処理して、完了する。

シナリオ 2: ドライバが KMDF から I/O 要求を受け取り、I/O ターゲットに転送する。

シナリオ 3: ドライバが、新しい WDFMEMORY オブジェクトを使用する I/O 要求を発行する。

シナリオ 4: ドライバが、既存の WDFMEMORY オブジェクトを使用する I/O 要求を発行する。

シナリオ 5: ドライバが自ら作成した WDFREQUEST オブジェクトを再利用する。

シナリオ 1: ドライバが KMDF から I/O 要求を受け取り、処理して、完了する

最も単純なシナリオで、KMDF はドライバに要求をディスパッチし、ドライバは I/O を実行して、要求を完了します。この場合、基礎をなしているバッファは、ユーザー モード アプリケーション、別のドライバ、またはオペレーティング システム自体によって作成された可能性があります。ドライバがバッファ I/O または直接 I/O を実行する場合、下記の方法のいずれかでバッファにアクセスできます。

WdfRequestRetrieveInputMemory または WdfRequestRetrieveOutputMemory (書き込み要求か読み取り要求かによる) を呼び出して、WDFREQUEST に関連付けられた WDFMEMORY オブジェクトへのハンドルを取得し、WdfMemoryGetBuffer を呼び出して、バッファへのポインタを取得します。バッファを読み取り書き込むために、ドライバは、WdfMemoryCopyFromBuffer または WdfMemoryCopyToBuffer を呼び出します。

WdfRequestRetrieveInputBuffer または WdfRequestRetrieveOutputBuffer (書き込み要求か読み取り要求かによる) を呼び出してバッファへのポインタを取得します。

ドライバが WdfRequestComplete を呼び出して I/O 要求を完了すると、フレームワークは、WDFMEMORY オブジェクトを削除します。このため、バッファ ポインタは無効になります。

シナリオ 2: ドライバが KMDF から I/O 要求を受け取り、I/O ターゲットに転送する

もしドライバが、自ら I/O を実行する代わりに、要求を I/O ターゲットに転送したらどうなるでしょう?この場合、ドライバは次のように続行します。

1.

WdfDeviceGetIoTarget を呼び出して、I/O ターゲット オブジェクトへのハンドルを取得します。

2.

WdfRequestFormatUsingCurrentType を呼び出して、I/O ターゲットに対する要求をフォーマットします。または、WdfRequestRetrieveXxxMemory を呼び出して、メモリ オブジェクトを取得し、WdfIoTargetFormatRequestForXxx を呼び出して、I/O ターゲットに対する要求をフォーマットします。

3.

WdfRequestSend を呼び出して、要求を I/O ターゲットに送信します。

ドライバは、いかなる場合でも、WDFREQUEST オブジェクトまたは WDFMEMORY オブジェクトを変更してはなりません。ドライバが I/O 完了コールバックを要求に設定した場合、I/O ターゲットが要求を完了すると、KMDF はコールバックを呼び出します。ドライバ (I/O ターゲットではなく) が WdfRequestComplete を呼び出すまで、バッファ ポインタは有効なままです。

下記のサンプル コードは、ドライバが、着信 WDFREQUEST オブジェクトから WDFMEMORY オブジェクトへのハンドルを取得して、I/O ターゲットに送信する要求をフォーマットして、要求を送信する方法を示します。

VOID
EvtIoRead(
    IN WDFQUEUE Queue,
    IN WDFREQUEST Request,
    IN size_t Length
    )
{
    NTSTATUS status;
    WDFMEMORY memory;
    WDFIOTARGET ioTarget;
    BOOLEAN ret;
    ioTarget = WdfDeviceGetIoTarget(WdfIoQueueGetDevice(Queue));
 
    status = WdfRequestRetrieveOutputMemory(Request, &memory);
    if (!NT_SUCCESS(status)) {
        goto End;
    }
 
    status = WdfIoTargetFormatRequestForRead(ioTarget,
                                    Request,
                                    memory,
                                    NULL,
                                    NULL);
    if (!NT_SUCCESS(status)) {
        goto End;
    }
 
    WdfRequestSetCompletionRoutine(Request,
                                    RequestCompletionRoutine,
                                    WDF_NO_CONTEXT);
 
    ret = WdfRequestSend (Request, ioTarget, WDF_NO_SEND_OPTIONS);
    if (!ret) {
        status = WdfRequestGetStatus (Request);
        goto End;
    }
 
    return;
 
End:
    WdfRequestComplete(Request, status);
    return;
 
}

I/O ターゲットが要求を完了すると、KMDF は、ドライバが要求に設定した完了コールバックを呼び出します。下記は、このルーチンのコードです。

VOID
RequestCompletionRoutine(
    IN WDFREQUEST                  Request,
    IN WDFIOTARGET                 Target,
    PWDF_REQUEST_COMPLETION_PARAMS CompletionParams,
    IN WDFCONTEXT                  Context
    )
{
    UNREFERENCED_PARAMETER(Target);
    UNREFERENCED_PARAMETER(Context);
 
    WdfRequestComplete(Request, CompletionParams->IoStatus.Status);
 
    return;
 
}

ドライバがその完了コールバックで WdfCompleteRequest を呼び出すと、KMDF は WDFMEMORY オブジェクトを破壊します。ドライバが EvtIoRead 関数で取得した WDFMEMORY オブジェクト ハンドルは、今や無効です。

シナリオ 3: ドライバが、既存の WDFMEMORY オブジェクトを使用する I/O 要求を発行する

一部のドライバは、独自の I/O 要求を発行し、I/O ターゲットに送信します。それらは、WDFIOTARGET オブジェクトによって表されます。このような要求は、ドライバによって作成された WDFREQUEST オブジェクトを使用する必要があります。ドライバは、KMDF から受信した WDFREQUEST オブジェクトを再利用することはできません。ただし、ドライバは、着信 WDFREQUEST から WDFMEMORY オブジェクトを取得して、その WDFMEMORY オブジェクトを使用する新しい要求を作成できます。ドライバは、基礎をなしているバッファを変更してはなりませんが、新しい I/O 要求をフォーマットするとき、バッファ オフセットを渡すことができます。既存の WDFMEMORY オブジェクトを使用する新しい I/O 要求をフォーマットするため、ドライバは、WdfIoTargetFormatRequestXxx または WdfIoTargetSendXxxSynchronously メソッドのいずれかを呼び出します。ドライバは、要求を非同期に送信する場合、要求の完了時に KMDF が通知できるよう、I/O 完了コールバックを登録すべきです。

KMDF は、I/O ターゲットに送信する要求をフォーマットするとき、I/O ターゲット オブジェクトのために、再利用された WDFMEMORY オブジェクトへの参照を取り出します。下記のいずれかが起こるまで、I/O ターゲット オブジェクトは、この参照を維持します。

要求が完了します。

ドライバは、WdfIoTargetFormatRequestXxx または WdfIoTargetSendXxxSynchronously メソッドのいずれかを呼び出して、再び WDFREQUEST オブジェクトを再フォーマットします。

ドライバは、WdfRequestReuse を呼び出します。

新しい I/O 要求が完了すると、WDF は、ドライバがこの要求に設定した I/O 完了コールバックを呼び出します。この時点で、WDFIOTARGET オブジェクトは、WDFMEMORY オブジェクトへの参照をまだ保持しています。したがって、I/O 完了コールバックで、ドライバは、WDFMEMORY オブジェクトを取得した元の要求を完了する前に、ドライバによって作成された WDFREQUEST オブジェクトに対して、WdfRequestReuse を呼び出す必要があります。ドライバが WdfRequestReuse を呼び出さない場合、余分な参照のため、バグ チェックが起こります。

シナリオ 4: ドライバが、新しい WDFMEMORY オブジェクトを使用する要求を発行する

KMDF は、基礎をなしているバッファのソースによって、新しい WDFMEMORY オブジェクトをドライバが作成する方法を 3 種類提供します。

KMDF にバッファを割り当てさせるには、ドライバは、WdfMemoryCreate を呼び出します。ドライバの仕様によって、バッファは、ページ プールまたは非ページ プールから割り当てることができます。

定義済みのルック アサイド リストからバッファを割り当てるには、ドライバは、WdfMemoryCreateFromLookaside を呼び出します。

前に割り当てられたバッファを新しい WDFMEMORY オブジェクトに割り当てるには、ドライバは、WdfMemoryCreatePreallocated を呼び出します。ドライバは、WdfMemoryAssignBuffer を呼び出すことによって、同じ WDFMEMORY オブジェクトに異なるバッファを後で割り当てることができます。

バッファが KMDF によって割り当てられる場合、またはドライバによって作成された WDFLOOKASIDE リストから割り当てられる場合、WDFMEMORY オブジェクトは、バッファを "所有" します。そのため、WDFMEMORY オブジェクトが存在する限り、バッファ ポインタは有効なままです。非同期 I/O 要求を発行するドライバは、WDFMEMORY オブジェクトによって "所有" されているバッファを常に使用すべきです。これにより、KMDF は、I/O 要求が完了して発行元ドライバに戻るまで、バッファが持続していることを確認できます。したがって、ドライバは、WdfMemoryCreate または WdfMemoryCreateFromLookaside のいずれかを使用して、非同期 I/O 要求用に WDFMEMORY オブジェクトを作成すべきです。

ドライバが、WdfMemoryCreatePreallocated を呼び出して、前に割り当てられたバッファを新しい WDFMEMORY オブジェクトに割り当てる場合、WDFMEMORY オブジェクトはバッファを "所有" しません。この場合、WDFMEMORY オブジェクトの有効期間と、基礎をなしているバッファの有効期間は無関連です。ドライバは、バッファの有効期間を管理する必要があり、無効なバッファの使用を試みてはなりません。

ドライバは、I/O 要求で受信した WDFMEMORY オブジェクトで、WdfMemoryAssignBuffer を使用することはできません。このメソッドは、ドライバによって作成された新しい WDFMEMORY オブジェクトでのみ使用できます。

シナリオ 5: ドライバが自ら作成した WDFREQUEST オブジェクトを再利用する

ドライバは、自ら作成した WDFREQUEST オブジェクトを再利用できます、しかし、各再利用の前に、WdfRequestReuse を呼び出して、そのような各オブジェクトを再初期化する必要があります。WDFREQUEST オブジェクトを再初期化するサンプル コードについては、KMDF リリースで提供される Toastmon と NdisEdge サンプルを参照してください。

ユーザーがすべきこと

関連付けられた WDFREQUEST オブジェクトが完了した後、WDFMEMORY オブジェクトの基礎となっているバッファの参照を試みないでください。

非同期 I/O 要求を送信する場合、WdfMemoryCreate または WdfMemoryCreateFromLookaside によって割り当てられるバッファを常に使用してください。

ドライバは、再利用された WDFMEMORY オブジェクトを含む I/O 要求を送信する場合、新しい要求の完了後、WDFMEMORY オブジェクトを取得した要求が完了する前に、WdfRequestReuse を常に呼び出す必要があります。

新しい I/O 要求でオブジェクトを再利用する前に、WDFREQUEST オブジェクトを再初期化するため、WdfRequestReuse を常に呼び出してください。

詳細情報 :
カーネル モード ドライバ フレームワークのアーキテクチャ
カーネル モード ドライバ フレームワーク (KMDF)
KMDF ドキュメントの下記を参照してください。

Design Guide

Handling I/O Requests in Framework-based Drivers

Framework Request Objects

Programming Techniques for Framework-based Drivers

Using Memory Buffers



この情報はお役に立ちましたか?