다음을 통해 공유


USB 파이프에서 데이터를 읽기 위해 연속 판독기를 사용하는 방법

이 항목에서는 WDF에서 제공하는 연속 판독기 개체에 대해 설명합니다. 이 항목의 절차에서는 개체를 구성하고 이를 사용하여 USB 파이프에서 데이터를 읽는 방법에 대한 단계별 지침을 제공합니다.

WDF(Windows 드라이버 프레임워크)는 연속 판독기라는 특수한 개체를 제공합니다. 이 개체를 사용하면 USB 클라이언트 드라이버가 사용 가능한 데이터가 있는 한 대량에서 데이터를 읽고 엔드포인트를 지속적으로 중단할 수 있습니다. 판독기를 사용하려면 클라이언트 드라이버에 드라이버가 데이터를 읽는 엔드포인트와 연결된 USB 대상 파이프 개체에 대한 핸들이 있어야 합니다. 엔드포인트는 활성 구성에 있어야 합니다. USB 구성을 선택하거나 현재 구성에서 대체 설정을 변경하여 두 가지 방법 중 하나로 구성을 활성화할 수 있습니다. 이러한 작업에 대한 자세한 내용은 USB 디바이스에 대한 구성을 선택하는 방법 및 USB 인터페이스에서 대체 설정을 선택하는 방법을 참조하세요.

연속 판독기를 만든 후 클라이언트 드라이버는 필요에 따라 판독기를 시작하고 중지할 수 있습니다. 대상 파이프 개체에서 읽기 요청을 항상 사용할 수 있고 클라이언트 드라이버가 항상 엔드포인트에서 데이터를 받을 준비가 되도록 하는 연속 판독기입니다.

연속 판독기는 프레임워크에서 자동으로 전원을 관리하지 않습니다. 즉, 디바이스가 더 낮은 전원 상태가 되면 클라이언트 드라이버가 판독기를 중지하고 디바이스가 작동 상태가 되면 판독기를 다시 시작해야 합니다.

이 문서에서는 다음을 활용합니다.

시작하기 전에

클라이언트 드라이버가 연속 판독기를 사용하기 전에 다음 요구 사항이 충족되는지 확인합니다.

  • USB 디바이스에는 IN 엔드포인트가 있어야 합니다. USBView에서 디바이스 구성을 확인합니다. Usbview.exe 모든 USB 컨트롤러와 연결된 USB 디바이스를 찾아볼 수 있는 애플리케이션입니다. 일반적으로 USBView는 WDK(Windows 드라이버 키트)의 디버거 폴더에 설치됩니다.

  • 클라이언트 드라이버가 프레임워크 USB 대상 디바이스 개체를 만들었어야 합니다.

    Microsoft Visual Studio Professional 2012와 함께 제공되는 USB 템플릿을 사용하는 경우 템플릿 코드는 이러한 작업을 수행합니다. 템플릿 코드는 대상 디바이스 개체에 대한 핸들을 가져오고 디바이스 컨텍스트에 저장합니다.

    KMDF 클라이언트 드라이버:

    KMDF 클라이언트 드라이버는 WdfUsbTargetDeviceCreateWithParameters 메서드를 호출하여 WDFUSBDEVICE 핸들을 가져와야 합니다. 자세한 내용은 KMDF(USB 클라이언트 드라이버 코드 구조) 이해의 "디바이스 소스 코드"를 참조하세요.

    UMDF 클라이언트 드라이버:

    UMDF 클라이언트 드라이버는 프레임워크 대상 디바이스 개체를 쿼리하여 IWDFUsbTargetDevice 포인터를 가져와야 합니다. 자세한 내용은 UMDF(USB 클라이언트 드라이버 코드 구조) 이해에서 "IPnpCallbackHardware 구현 및 USB 관련 작업"을 참조하세요.

  • 디바이스에 활성 구성이 있어야 합니다.

    USB 템플릿을 사용하는 경우 코드는 각 인터페이스에서 첫 번째 구성 및 기본 대체 설정을 선택합니다. 대체 설정을 변경하는 방법에 대한 자세한 내용은 USB 인터페이스에서 대체 설정을 선택하는 방법을 참조 하세요.

    KMDF 클라이언트 드라이버:

    KMDF 클라이언트 드라이버는 WdfUsbTargetDeviceSelectConfig 메서드를 호출해야 합니다.

    UMDF 클라이언트 드라이버:

    UMDF 클라이언트 드라이버의 경우 프레임워크는 해당 구성의 각 인터페이스에 대한 첫 번째 구성 및 기본 대체 설정을 선택합니다.

  • 클라이언트 드라이버에는 IN 엔드포인트에 대한 프레임워크 대상 파이프 개체에 대한 핸들이 있어야 합니다. 자세한 내용은 USB 파이프를 열거하는 방법을 참조 하세요.

KMDF 클라이언트 드라이버에서 연속 판독기 사용

연속 판독기 사용을 시작하기 전에 WDF_USB_CONTINUOUS_READER_CONFIG 구조를 초기화하여 구성해야 합니다.

KMDF 클라이언트 드라이버에서 연속 판독기 구성

  1. WDF_USB_CONTINUOUS_READER_CONFIG_INIT 매크로를 호출하여 WDF_USB_CONTINUOUS_READER_CONFIG 구조를 초기화합니다.

  2. WDF_USB_CONTINUOUS_READER_CONFIG 구조체에서 해당 구성 옵션을 지정합니다.

  3. WdfUsbTargetPipeConfigContinuousReader 메서드를 호출합니다.

    다음 예제 코드는 지정된 대상 파이프 개체에 대한 연속 판독기를 구성합니다.

    NTSTATUS FX3ConfigureContinuousReader(
        _In_ WDFDEVICE Device,
        _In_ WDFUSBPIPE Pipe)
    {
        NTSTATUS status;
        PDEVICE_CONTEXT                     pDeviceContext;
        WDF_USB_CONTINUOUS_READER_CONFIG    readerConfig;
        PPIPE_CONTEXT                       pipeContext;
    
        PAGED_CODE();
    
        pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);
        pipeContext = GetPipeContext (Pipe);
    
        WDF_USB_CONTINUOUS_READER_CONFIG_INIT(
            &readerConfig,
            FX3EvtReadComplete,
            pDeviceContext,
            pipeContext->MaxPacketSize);
    
        readerConfig.EvtUsbTargetPipeReadersFailed=FX3EvtReadFailed;
    
        status = WdfUsbTargetPipeConfigContinuousReader(
            Pipe,
            &readerConfig);
    
        if (!NT_SUCCESS (status))
        {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                "%!FUNC! WdfUsbTargetPipeConfigContinuousReader failed 0x%x", status);
    
            goto Exit;
        }
    
    Exit:
        return status;
    }
    

일반적으로 클라이언트 드라이버는 활성 설정에서 대상 파이프 개체를 열거한 후 EvtDevicePrepareHardware 콜백 함수에서 연속 판독기를 구성합니다.

앞의 예제에서 클라이언트 드라이버는 두 가지 방법으로 구성 옵션을 지정합니다. 먼저 WDF_USB_CONTINUOUS_READER_CONFIG_INIT 호출한 다음 WDF_USB_CONTINUOUS_READER_CONFIG 멤버를 설정합니다. WDF_USB_CONTINUOUS_READER_CONFIG_INIT 대한 매개 변수를 확인합니다. 이러한 값은 필수입니다. 이 예제에서 클라이언트 드라이버는 다음을 지정합니다.

  • 드라이버가 구현하는 완료 루틴에 대한 포인터입니다. 프레임워크는 읽기 요청을 완료할 때 이 루틴을 호출합니다. 완료 루틴에서 드라이버는 읽은 데이터가 포함된 메모리 위치에 액세스할 수 있습니다. 완료 루틴의 구현은 2단계에서 설명합니다.
  • 드라이버 정의 컨텍스트에 대한 포인터입니다.
  • 단일 전송으로 디바이스에서 읽을 수 있는 바이트 수입니다. 클라이언트 드라이버는 WdfUsbInterfaceGetConfiguredPipe 또는 WdfUsbTargetPipeGetInformation 메서드를 호출하여 WDF_USB_PIPE_INFORMATION 구조에서 해당 정보를 가져올 수 있습니다. 자세한 내용은 USB 파이프를 열거하는 방법을 참조 하세요.

WDF_USB_CONTINUOUS_READER_CONFIG_INIT NumPendingReads의 기본값을 사용하도록 연속 판독기를 구성합니다. 이 값은 프레임워크가 보류 중인 큐에 추가하는 읽기 요청 수를 결정합니다. 기본값은 많은 프로세서 구성에서 많은 디바이스에 대해 합리적으로 좋은 성능을 제공하도록 결정되었습니다.

이 예제에서는 WDF_USB_CONTINUOUS_READER_CONFIG_INIT 지정된 구성 매개 변수 외에도 WDF_USB_CONTINUOUS_READER_CONFIG 오류 루틴을 설정합니다. 이 오류 루틴은 선택 사항입니다.

오류 루틴 외에도 클라이언트 드라이버가 전송 버퍼의 레이아웃을 지정하는 데 사용할 수 있는 다른 멤버가 WDF_USB_CONTINUOUS_READER_CONFIG 있습니다. 예를 들어 연속 판독기를 사용하여 네트워크 패킷을 수신하는 네트워크 드라이버를 고려합니다. 각 패킷에는 헤더, 페이로드 및 바닥글 데이터가 포함됩니다. 패킷을 설명하려면 드라이버는 먼저 WDF_USB_CONTINUOUS_READER_CONFIG_INIT 호출에서 패킷의 크기를 지정해야 합니다. 그런 다음 드라이버는 WDF_USB_CONTINUOUS_READER_CONFIG HeaderLength 및 TrailerLength 멤버를 설정하여 머리글 및 바닥글의 길이를 지정해야 합니다. 프레임워크는 이러한 값을 사용하여 페이로드의 양쪽에서 바이트 오프셋을 계산합니다. 엔드포인트에서 페이로드 데이터를 읽을 때 프레임워크는 오프셋 사이에 해당 데이터를 버퍼의 일부에 저장합니다.

완료 루틴 구현

프레임워크는 요청이 완료될 때마다 클라이언트 드라이버 구현 완료 루틴을 호출합니다. 프레임워크는 읽은 바이트 수와 파이프에서 읽은 데이터가 버퍼에 포함된 WDFMEMORY 개체를 전달합니다.

다음 예제 코드는 완료 루틴 구현을 보여줍니다.

EVT_WDF_USB_READER_COMPLETION_ROUTINE FX3EvtReadComplete;

VOID FX3EvtReadComplete(
    __in  WDFUSBPIPE Pipe,
    __in  WDFMEMORY Buffer,
    __in  size_t NumBytesTransferred,
    __in  WDFCONTEXT Context
    )
{
    PDEVICE_CONTEXT  pDeviceContext;
    PVOID  requestBuffer;

    pDeviceContext = (PDEVICE_CONTEXT)Context;

    if (NumBytesTransferred == 0)
    {
        return;
    }

    requestBuffer = WdfMemoryGetBuffer(Buffer, NULL);

    if (Pipe == pDeviceContext->InterruptPipe)
    {
        KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
                                "Interrupt endpoint: %s.\n",
                                requestBuffer ));
    }

    return;
}

프레임워크는 요청이 완료될 때마다 클라이언트 드라이버 구현 완료 루틴을 호출합니다. 프레임워크는 각 읽기 작업에 대한 메모리 개체를 할당합니다. 완료 루틴에서 프레임워크는 읽은 바이트 수와 WDFMEMORY 핸들을 메모리 개체에 전달합니다. 메모리 개체 버퍼에는 파이프에서 읽은 데이터가 포함됩니다. 클라이언트 드라이버는 메모리 개체를 해제하면 안됩니다. 프레임워크는 각 완료 루틴이 반환된 후 개체를 해제합니다. 클라이언트 드라이버가 수신된 데이터를 저장하려는 경우 드라이버는 완료 루틴에서 버퍼의 내용을 복사해야 합니다.

오류 루틴 구현

프레임워크는 클라이언트 드라이버 구현 오류 루틴을 호출하여 드라이버에 읽기 요청을 처리하는 동안 연속 판독기에서 오류를 보고했음을 알릴 수 있습니다. 프레임워크는 요청이 실패한 대상 파이프 개체 및 오류 코드 값에 대한 포인터를 전달합니다. 이러한 오류 코드 값에 따라 드라이버는 오류 복구 메커니즘을 구현할 수 있습니다. 또한 드라이버는 프레임워크가 연속 판독기를 다시 시작해야 하는지 여부를 프레임워크에 나타내는 적절한 값을 반환해야 합니다.

다음 예제 코드는 오류 루틴 구현을 보여 있습니다.

EVT_WDF_USB_READERS_FAILED FX3EvtReadFailed;

BOOLEAN
FX3EvtReadFailed(
    WDFUSBPIPE      Pipe,
    NTSTATUS        Status,
    USBD_STATUS     UsbdStatus
    )
{
    UNREFERENCED_PARAMETER(Status);

    TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! ReadersFailedCallback failed NTSTATUS 0x%x, UsbdStatus 0x%x\n",
                    status,
                    UsbdStatus);

    return TRUE;
}

앞의 예제에서 드라이버는 TRUE를 반환합니다. 이 값은 파이프를 다시 시작한 다음 연속 판독기를 다시 시작해야 한다는 것을 프레임워크에 나타냅니다.

또는 클라이언트 드라이버는 FALSE를 반환하고 파이프에서 중단 조건이 발생하는 경우 오류 복구 메커니즘을 제공할 수 있습니다. 예를 들어 드라이버는 USBD 상태 검사 리셋 파이프 요청을 실행하여 중단 조건을 지울 수 있습니다.

파이프의 오류 복구에 대한 자세한 내용은 USB 파이프 오류에서 복구하는 방법을 참조 하세요.

연속 판독기 시작 및 중지

디바이스가 작업 상태로 들어갈 때 연속 판독기를 시작하도록 프레임워크에 지시합니다. 디바이스가 작동 상태를 벗어나면 판독기를 중지합니다. 이러한 메서드를 호출하고 대상 파이프 개체를 I/O 대상 개체로 지정합니다.

연속 판독기는 프레임워크에서 자동으로 전원을 관리하지 않습니다. 따라서 클라이언트 드라이버는 디바이스의 전원 상태가 변경되면 대상 파이프 개체를 명시적으로 시작하거나 중지해야 합니다. 드라이버는 드라이버의 EvtDeviceD0Entry 구현에서 WdfIoTargetStart를 호출합니다. 이 호출은 디바이스가 작동 상태일 때만 큐가 요청을 전달하도록 합니다. 반대로 드라이버는 드라이버 EvtDeviceD0Exit 구현에서 WdfIoTargetStop을 호출하여 디바이스가 더 낮은 전원 상태가 될 때 큐에서 요청 배달을 중지합니다.

다음 예제 코드는 지정된 대상 파이프 개체에 대한 연속 판독기를 구성합니다.

EVT_WDF_DEVICE_D0_ENTRY FX3EvtDeviceD0Entry;

NTSTATUS FX3EvtDeviceD0Entry(
    __in  WDFDEVICE Device,
    __in  WDF_POWER_DEVICE_STATE PreviousState
    )
{
    PDEVICE_CONTEXT  pDeviceContext;
    NTSTATUS status;

    PAGED_CODE();

    pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);
    status = WdfIoTargetStart (WdfUsbTargetPipeGetIoTarget (pDeviceContext->InterruptPipe));

    if (!NT_SUCCESS (status))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Could not start interrupt pipe failed 0x%x", status);
    }
}

EVT_WDF_DEVICE_D0_EXIT FX3EvtDeviceD0Exit;

NTSTATUS FX3EvtDeviceD0Exit(
    __in  WDFDEVICE Device,
    __in  WDF_POWER_DEVICE_STATE TargetState
    )
{
    PDEVICE_CONTEXT  pDeviceContext;
    NTSTATUS status;
    PAGED_CODE();
    pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);
    WdfIoTargetStop (WdfUsbTargetPipeGetIoTarget (pDeviceContext->InterruptPipe), WdfIoTargetCancelSentIo));
}

앞의 예제에서는 EvtDeviceD0Entry EvtDeviceD0Exit 콜백 루틴에 대한 구현을 보여 줍니다. WdfIoTargetStopAction 매개 변수를 사용하면 디바이스가 작업 상태를 떠날 때 클라이언트 드라이버가 큐의 보류 중인 요청에 대한 작업을 결정할 수 있습니다. 이 예제에서 드라이버는 WdfIoTargetCancelSentIo를 지정합니다. 이 옵션은 큐에서 보류 중인 모든 요청을 취소하도록 프레임워크에 지시합니다. 또는 드라이버는 I/O 대상을 중지하기 전에 보류 중인 요청이 완료될 때까지 기다리도록 프레임워크에 지시하거나 보류 중인 요청을 유지하고 I/O 대상이 다시 시작될 때 다시 시작하도록 지시할 수 있습니다.

UMDF 클라이언트 드라이버에서 연속 판독기 사용

연속 판독기 사용을 시작하기 전에 IPnpCallbackHardware::OnPrepareHardware 메서드 구현에서 판독기를 구성해야 합니다. IN 엔드포인트와 연결된 대상 파이프 개체의 IWDFUsbTargetPipe 인터페이스에 대한 포인터를 받은 후 다음 단계를 수행합니다.

UMDF 클라이언트 드라이버에서 연속 판독기 구성

  1. 대상 파이프 개체(IWDFUsbTargetPipe)에서 QueryInterface를 호출하고 IWDFUsbTargetPipe2 인터페이스에 대한 쿼리를 실행합니다.

  2. 디바이스 콜백 개체에서 QueryInterface를 호출하고 IUsbTargetPipeContinuousReaderCallbackReadComplete 인터페이스에 대한 쿼리를 실행합니다. 연속 판독기를 사용하려면 IUsbTargetPipeContinuousReaderCallbackReadComplete를 구현해야 합니다. 구현은 이 항목의 뒷부분에 설명되어 있습니다.

  3. 디바이스 콜백 개체에서 QueryInterface를 호출하고 오류 콜백을 구현한 경우 IUsbTargetPipeContinuousReaderCallbackReadersFailed 인터페이스에 대한 쿼리를 실행합니다. 구현은 이 항목의 뒷부분에 설명되어 있습니다.

  4. IWDFUsbTargetPipe2::ConfigureContinuousReader 메서드를 호출하고 헤더, 트레일러, 보류 중인 요청 수 및 완료 및 실패 콜백 메서드에 대한 참조와 같은 구성 매개 변수를 지정합니다.

    이 메서드는 대상 파이프 개체에 대한 연속 판독기를 구성합니다. 연속 판독기는 대상 파이프 개체에서 보내고 받을 때 읽기 요청 집합을 관리하는 큐를 만듭니다.

다음 예제 코드는 지정된 대상 파이프 개체에 대한 연속 판독기를 구성합니다. 이 예제에서는 호출자가 지정한 대상 파이프 개체가 IN 엔드포인트와 연결되어 있다고 가정합니다. 연속 판독기는 USBD_DEFAULT_MAXIMUM_TRANSFER_SIZE 바이트를 읽도록 구성됩니다. 프레임워크에서 사용하는 보류 중인 요청의 기본 수를 사용하려면 다음을 실행합니다. 클라이언트 드라이버 제공 완료 및 실패 콜백 메서드를 호출합니다. 수신된 버퍼에는 헤더 또는 트레일러 데이터가 포함되지 않습니다.

HRESULT CDeviceCallback::ConfigureContinuousReader (IWDFUsbTargetPipe* pFxPipe)
{
    if (!pFxPipe)
    {
        return E_INVALIDARG;
    }

    IUsbTargetPipeContinuousReaderCallbackReadComplete *pOnCompletionCallback = NULL;
    IUsbTargetPipeContinuousReaderCallbackReadersFailed *pOnFailureCallback = NULL;
    IWDFUsbTargetPipe2* pFxUsbPipe2 = NULL;

    HRESULT hr = S_OK;

    // Set up the continuous reader to read from the target pipe object.

    //Get a pointer to the target pipe2 object.
    hr = pFxPipe->QueryInterface(IID_PPV_ARGS(&pFxUsbPipe2));
    if (FAILED(hr))
    {
        goto ConfigureContinuousReaderExit;
    }

    //Get a pointer to the completion callback.
    hr = QueryInterface(IID_PPV_ARGS(&pOnCompletionCallback));
    if (FAILED(hr))
    {
        goto ConfigureContinuousReaderExit;
    }

    //Get a pointer to the failure callback.
    hr = QueryInterface(IID_PPV_ARGS(&pOnFailureCallback));
    if (FAILED(hr))
    {
        goto ConfigureContinuousReaderExit;
    }

    //Get a pointer to the target pipe2 object.
    hr = pFxUsbPipe2->ConfigureContinuousReader (
        USBD_DEFAULT_MAXIMUM_TRANSFER_SIZE, //size of data to be read
        0, //Header
        0, //Trailer
        0, // Number of pending requests queued by WDF
        NULL, // Cleanup callback. Not provided.
        pOnCompletionCallback, //Completion routine.
        NULL, //Completion routine context. Not provided.
        pOnFailureCallback); //Failure routine. Not provided

    if (FAILED(hr))
    {
        goto ConfigureContinuousReaderExit;
    }

ConfigureContinuousReaderExit:

    if (pOnFailureCallback)
    {
        pOnFailureCallback->Release();
        pOnFailureCallback = NULL;
    }

    if (pOnCompletionCallback)
    {
        pOnCompletionCallback->Release();
        pOnCompletionCallback = NULL;
    }

    if (pFxUsbPipe2)
    {
        pFxUsbPipe2->Release();
        pFxUsbPipe2 = NULL;
    }

    return hr;
}

다음으로, 디바이스가 D0(작업 상태)에 진입하여 종료되는 경우 대상 파이프 개체의 상태를 지정합니다.

클라이언트 드라이버가 전원 관리 큐를 사용하여 파이프에 요청을 보내는 경우 큐는 디바이스가 D0 상태일 때만 요청을 전달합니다. 디바이스의 전원 상태가 D0에서 낮은 전원 상태로 변경되면(D0 종료 시) 대상 파이프 개체는 보류 중인 요청을 완료하고 큐는 대상 파이프 개체에 대한 요청 제출을 중지합니다. 따라서 클라이언트 드라이버는 대상 파이프 개체를 시작하고 중지할 필요가 없습니다.

연속 판독기는 전원 관리 큐를 사용하여 요청을 제출하지 않습니다. 따라서 디바이스의 전원 상태가 변경되면 대상 파이프 개체를 명시적으로 시작하거나 중지해야 합니다. 대상 파이프 개체의 상태를 변경하려면 프레임워크에서 구현한 IWDFIoTargetStateManagement 인터페이스를 사용할 수 있습니다. IN 엔드포인트와 연결된 대상 파이프 개체의 IWDFUsbTargetPipe 인터페이스에 대한 포인터를 받은 후 다음 단계를 수행합니다.

상태 관리 구현

  1. IPnpCallbackHardware::OnPrepareHardware 구현에서 대상 파이프 개체(IWDFUsbTargetPipe)에서 QueryInterface를 호출하고 IWDFIoTargetStateManagement 인터페이스를 쿼리합니다. 디바이스 콜백 클래스의 멤버 변수에 참조를 저장합니다.

  2. 디바이스 콜백 개체에서 IPnpCallback 인터페이스를 구현합니다.

  3. IPnpCallback::OnD0Entry 메서드의 구현에서 IWDFIoTargetStateManagement::Start를 호출하여 연속 판독기를 시작합니다.

  4. IPnpCallback::OnD0Exit 메서드의 구현에서 IWDFIoTargetStateManagement::Stop을 호출하여 연속 판독기를 중지합니다.

디바이스가 작업 상태(D0)로 들어가면 프레임워크는 대상 파이프 개체를 시작하는 클라이언트 드라이버 제공 D0 항목 콜백 메서드를 호출합니다. 디바이스가 D0 상태를 벗어나면 프레임워크는 D0 종료 콜백 메서드를 호출합니다. 대상 파이프 개체는 클라이언트 드라이버에서 구성한 보류 중인 읽기 요청 수를 완료하고 새 요청 수락을 중지합니다. 다음 예제 코드는 디바이스 콜백 개체에서 IPnpCallback 인터페이스를 구현합니다.

class CDeviceCallback :
    public IPnpCallbackHardware,
    public IPnpCallback,
{
public:
    CDeviceCallback();
    ~CDeviceCallback();
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvObject);
    virtual ULONG STDMETHODCALLTYPE AddRef();
    virtual ULONG STDMETHODCALLTYPE Release();

    virtual HRESULT STDMETHODCALLTYPE OnPrepareHardware(IWDFDevice* pDevice);
    virtual HRESULT STDMETHODCALLTYPE OnReleaseHardware(IWDFDevice* pDevice);

    virtual HRESULT STDMETHODCALLTYPE OnD0Entry(IWDFDevice*  pWdfDevice, WDF_POWER_DEVICE_STATE  previousState);
    virtual HRESULT STDMETHODCALLTYPE OnD0Exit(IWDFDevice*  pWdfDevice, WDF_POWER_DEVICE_STATE  previousState);
    virtual void STDMETHODCALLTYPE OnSurpriseRemoval(IWDFDevice*  pWdfDevice);
    virtual HRESULT STDMETHODCALLTYPE OnQueryRemove(IWDFDevice*  pWdfDevice);
    virtual HRESULT STDMETHODCALLTYPE OnQueryStop(IWDFDevice*  pWdfDevice);

private:
    LONG m_cRefs;
    IWDFUsbTargetPipe* m_pFxUsbPipe;
    IWDFIoTargetStateManagement* m_pFxIoTargetInterruptPipeStateMgmt;

    HRESULT CreateUSBTargetDeviceObject (IWDFDevice* pFxDevice, IWDFUsbTargetDevice** ppUSBTargetDevice);
    HRESULT ConfigureContinuousReader (IWDFUsbTargetPipe* pFxPipe);
};

다음 예제 코드는 IPnpCallback::OnPrepareHardware 메서드에서 대상 파이프 개체의 IWDFIoTargetStateManagement 인터페이스에 대한 포인터를 가져오는 방법을 보여줍니다.

   //Enumerate the endpoints and get the interrupt pipe.
    for (UCHAR index = 0; index < NumEndpoints; index++)
    {
        hr = pFxInterface->RetrieveUsbPipeObject(index, &pFxPipe);

        if (SUCCEEDED (hr) && pFxPipe)
        {
            if ((pFxPipe->IsInEndPoint()) && (pFxPipe->GetType()==UsbdPipeTypeInterrupt))
            {
                //Pipe is for an interrupt IN endpoint.
                hr = pFxPipe->QueryInterface(IID_PPV_ARGS(&m_pFxIoTargetInterruptPipeStateMgmt));

                if (m_pFxIoTargetInterruptPipeStateMgmt)
                {
                    m_pFxUsbPipe = pFxPipe;
                    break;
                }

            }
            else
            {
                //Pipe is NOT for an interrupt IN endpoint.
                pFxPipe->Release();
                pFxPipe = NULL;
            }
        }
        else
        {
             //Pipe not found.
        }
    }

다음 예제 코드는 IPnpCallbackHardware::OnPrepareHardware 메서드에서 대상 파이프 개체의 IWDFIoTargetStateManagement 인터페이스에 대한 포인터를 가져오는 방법을 보여줍니다.

 HRESULT CDeviceCallback::OnD0Entry(
    IWDFDevice*  pWdfDevice,
    WDF_POWER_DEVICE_STATE  previousState
    )
{

    if (!m_pFxIoTargetInterruptPipeStateMgmt)
    {
        return E_FAIL;
    }

    HRESULT hr = m_pFxIoTargetInterruptPipeStateMgmt->Start();

    if (FAILED (hr))
    {
        goto OnD0EntryExit;
    }

OnD0EntryExit:
    return hr;
}

HRESULT CDeviceCallback::OnD0Exit(
    IWDFDevice*  pWdfDevice,
    WDF_POWER_DEVICE_STATE  previousState
    )
{
    if (!m_pFxIoTargetInterruptPipeStateMgmt)
    {
        return E_FAIL;
    }

    // Stop the I/O target always succeeds.
    (void)m_pFxIoTargetInterruptPipeStateMgmt->Stop(WdfIoTargetCancelSentIo);

    return S_OK;
}

연속 판독기에서 읽기 요청을 완료한 후 클라이언트 드라이버는 요청이 읽기 요청을 성공적으로 완료할 때 알림을 받는 방법을 제공해야 합니다. 클라이언트 드라이버는 디바이스 콜백 개체에 이 코드를 추가해야 합니다.

IUsbTargetPipeContinuousReaderCallbackReadComplete를 구현하여 완료 콜백 제공

  1. 디바이스 콜백 개체에서 IUsbTargetPipeContinuousReaderCallbackReadComplete 인터페이스를 구현합니다.

  2. 디바이스 콜백 개체의 QueryInterface 구현이 콜백 개체의 참조 수를 증가시키고 IUsbTargetPipeContinuousReaderCallbackReadComplete 인터페이스 포인터를 반환하는지 확인합니다.

  3. IUsbTargetPipeContinuousReaderCallbackReadComplete::OnReaderCompletion 메서드의 구현에서 파이프에서 읽은 데이터에 액세스합니다. pMemory 매개 변수는 데이터가 포함된 프레임워크에서 할당한 메모리를 가리킵니다. IWDFMemory::GetDataBuffer를 호출하여 데이터가 포함된 버퍼를 가져올 수 있습니다. 버퍼에는 헤더가 포함되지만 OnReaderCompletion의 NumBytesTransferred 매개 변수로 표시된 데이터의 길이에는 헤더 길이가 포함되지 않습니다. 헤더 길이는 IWDFUsbTargetPipe2::ConfigureContinuousReader에 대한 드라이버의 호출에서 연속 판독기를 구성하는 동안 클라이언트 드라이버에 의해 지정됩니다.

  4. IWDFUsbTargetPipe2::ConfigureContinuousReader 메서드의 pOnCompletion 매개 변수에서 완성 콜백에 대한 포인터를 제공합니다.

디바이스의 엔드포인트에서 데이터를 사용할 때마다 대상 파이프 개체가 읽기 요청을 완료합니다. 읽기 요청이 성공적으로 완료되면 프레임워크는 IUsbTargetPipeContinuousReaderCallbackReadComplete::OnReaderCompletion을 호출하여 클라이언트 드라이버에 알깁니다. 그렇지 않으면 대상 파이프 개체가 읽기 요청에 대한 오류를 보고할 때 프레임워크에서 클라이언트 드라이버 제공 오류 콜백을 호출합니다.

다음 예제 코드는 디바이스 콜백 개체에서 IUsbTargetPipeContinuousReaderCallbackReadComplete 인터페이스를 구현합니다.

class CDeviceCallback :
    public IPnpCallbackHardware,
    public IPnpCallback,
    public IUsbTargetPipeContinuousReaderCallbackReadComplete

{
public:
    CDeviceCallback();
    ~CDeviceCallback();
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvObject);
    virtual ULONG STDMETHODCALLTYPE AddRef();
    virtual ULONG STDMETHODCALLTYPE Release();

    virtual HRESULT STDMETHODCALLTYPE OnPrepareHardware(IWDFDevice* pDevice);
    virtual HRESULT STDMETHODCALLTYPE OnReleaseHardware(IWDFDevice* pDevice);

    virtual HRESULT STDMETHODCALLTYPE OnD0Entry(IWDFDevice*  pWdfDevice, WDF_POWER_DEVICE_STATE  previousState);
    virtual HRESULT STDMETHODCALLTYPE OnD0Exit(IWDFDevice*  pWdfDevice, WDF_POWER_DEVICE_STATE  previousState);
    virtual void STDMETHODCALLTYPE OnSurpriseRemoval(IWDFDevice*  pWdfDevice);
    virtual HRESULT STDMETHODCALLTYPE OnQueryRemove(IWDFDevice*  pWdfDevice);
    virtual HRESULT STDMETHODCALLTYPE OnQueryStop(IWDFDevice*  pWdfDevice);

    virtual VOID STDMETHODCALLTYPE OnReaderCompletion(IWDFUsbTargetPipe* pPipe, IWDFMemory* pMemory, SIZE_T NumBytesTransferred, PVOID Context);

private:
    LONG m_cRefs;
    IWDFUsbTargetPipe* m_pFxUsbPipe;
    IWDFIoTargetStateManagement* m_pFxIoTargetInterruptPipeStateMgmt;

    HRESULT CreateUSBTargetDeviceObject (IWDFDevice* pFxDevice, IWDFUsbTargetDevice** ppUSBTargetDevice);
    HRESULT ConfigureContinuousReader (IWDFUsbTargetPipe* pFxPipe);
};

다음 예제 코드는 디바이스 콜백 개체의 QueryInterface 구현을 보여줍니다.

HRESULT CDeviceCallback::QueryInterface(REFIID riid, LPVOID* ppvObject)
{
    if (ppvObject == NULL)
    {
        return E_INVALIDARG;
    }

    *ppvObject = NULL;

    HRESULT hr = E_NOINTERFACE;

    if(  IsEqualIID(riid, __uuidof(IPnpCallbackHardware))   ||  IsEqualIID(riid, __uuidof(IUnknown))  )
    {
        *ppvObject = static_cast<IPnpCallbackHardware*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;
    }

    if(  IsEqualIID(riid, __uuidof(IPnpCallback)))
    {
        *ppvObject = static_cast<IPnpCallback*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;
    }

    if(  IsEqualIID(riid, __uuidof(IUsbTargetPipeContinuousReaderCallbackReadComplete)))
    {
        *ppvObject = static_cast<IUsbTargetPipeContinuousReaderCallbackReadComplete*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;
    }

    return hr;
}

다음 예제 코드는 IUsbTargetPipeContinuousReaderCallbackReadComplete::OnReaderCompletion에서 반환된 버퍼에서 데이터를 가져오는 방법을 보여줍니다. 대상 파이프 개체가 읽기 요청을 성공적으로 완료할 때마다 프레임워크는 OnReaderCompletion을 호출 합니다. 이 예제에서는 데이터를 포함하는 버퍼를 가져오고 디버거 출력의 내용을 출력합니다.

 VOID CDeviceCallback::OnReaderCompletion(
    IWDFUsbTargetPipe* pPipe,
    IWDFMemory* pMemory,
    SIZE_T NumBytesTransferred,
    PVOID Context)
{
    if (pPipe != m_pFxUsbInterruptPipe)
    {
        return;
    }

    if (NumBytesTransferred == 0)
    {
        // NumBytesTransferred is zero.
        return;
    }

    PVOID pBuff = NULL;
    LONG CurrentData = 0;
    char data[20];

    pBuff = pMemory->GetDataBuffer(NULL);

    if (pBuff)
    {
        CopyMemory(&CurrentData, pBuff, sizeof(CurrentData));
        sprintf_s(data, 20, "%d\n", CurrentData);
        OutputDebugString(data);
        pBuff = NULL;
    }
    else
    {
        OutputDebugString(TEXT("Unable to get data buffer."));
    }
}

클라이언트 드라이버는 읽기 요청을 완료하는 동안 대상 파이프 개체에서 오류가 발생할 때 프레임워크에서 알림을 받을 수 있습니다. 알림을 받으려면 클라이언트 드라이버가 오류 콜백을 구현하고 연속 판독기를 구성하는 동안 콜백에 대한 포인터를 제공해야 합니다. 다음 절차에서는 오류 콜백을 구현하는 방법을 설명합니다.

IUsbTargetPipeContinuousReaderCallbackReadersFailed를 구현하여 오류 콜백 제공

  1. 디바이스 콜백 개체에서 IUsbTargetPipeContinuousReaderCallbackReadersFailed 인터페이스를 구현합니다.

  2. 디바이스 콜백 개체의 QueryInterface 구현이 콜백 개체의 참조 수를 증가시키고 IUsbTargetPipeContinuousReaderCallbackReadersFailed 인터페이스 포인터를 반환하는지 확인합니다.

  3. IUsbTargetPipeContinuousReaderCallbackReadersFailed::OnReaderFailure 메서드의 구현에서 실패한 읽기 요청의 오류 처리를 제공합니다.

    연속 판독기가 읽기 요청을 완료하지 못하고 클라이언트 드라이버가 오류 콜백을 제공하는 경우 프레임워크는 IUsbTargetPipeContinuousReaderCallbackReadersFailed::OnReaderFailure 메서드를 호출합니다. 프레임워크는 hrStatus 매개 변수에 대상 파이프 개체에서 발생한 오류 코드를 나타내는 HRESULT 값을 제공합니다. 해당 오류 코드에 따라 특정 오류 처리를 제공할 수 있습니다. 예를 들어 프레임워크에서 파이프를 다시 시작한 다음 연속 판독기를 다시 시작하려면 콜백이 TRUE를 반환하는지 확인합니다.

    참고 IWDFIoTargetStateManagement::StartIWDFIoTargetStateManagement::Stop을 호출하지 마세요. 오류 콜백 내에서 중지합니다.

  4. IWDFUsbTargetPipe2::ConfigureContinuousReader 메서드의 pOnFailure 매개 변수에서 오류 콜백에 대한 포인터를 제공합니다.

다음 예제 코드는 디바이스 콜백 개체에서 IUsbTargetPipeContinuousReaderCallbackReadersFailed 인터페이스를 구현합니다.

class CDeviceCallback :
    public IPnpCallbackHardware,
    public IPnpCallback,
    public IUsbTargetPipeContinuousReaderCallbackReadComplete,
    public IUsbTargetPipeContinuousReaderCallbackReadersFailed
{
public:
    CDeviceCallback();
    ~CDeviceCallback();
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvObject);
    virtual ULONG STDMETHODCALLTYPE AddRef();
    virtual ULONG STDMETHODCALLTYPE Release();

    virtual HRESULT STDMETHODCALLTYPE OnPrepareHardware(IWDFDevice* pDevice);
    virtual HRESULT STDMETHODCALLTYPE OnReleaseHardware(IWDFDevice* pDevice);

    virtual HRESULT STDMETHODCALLTYPE OnD0Entry(IWDFDevice*  pWdfDevice, WDF_POWER_DEVICE_STATE  previousState);
    virtual HRESULT STDMETHODCALLTYPE OnD0Exit(IWDFDevice*  pWdfDevice, WDF_POWER_DEVICE_STATE  previousState);
    virtual void STDMETHODCALLTYPE OnSurpriseRemoval(IWDFDevice*  pWdfDevice);
    virtual HRESULT STDMETHODCALLTYPE OnQueryRemove(IWDFDevice*  pWdfDevice);
    virtual HRESULT STDMETHODCALLTYPE OnQueryStop(IWDFDevice*  pWdfDevice);

    virtual VOID STDMETHODCALLTYPE OnReaderCompletion(IWDFUsbTargetPipe* pPipe, IWDFMemory* pMemory, SIZE_T NumBytesTransferred, PVOID Context);
    virtual BOOL STDMETHODCALLTYPE OnReaderFailure(IWDFUsbTargetPipe * pPipe, HRESULT hrCompletion);

private:
    LONG m_cRefs;
    IWDFUsbTargetPipe* m_pFxUsbInterruptPipe;
    IWDFIoTargetStateManagement* m_pFxIoTargetInterruptPipeStateMgmt;

    HRESULT CreateUSBTargetDeviceObject (IWDFDevice* pFxDevice, IWDFUsbTargetDevice** ppUSBTargetDevice);
    HRESULT RetrieveUSBDeviceDescriptor (IWDFUsbTargetDevice* pUSBTargetDevice, PUSB_DEVICE_DESCRIPTOR DescriptorHeader, PULONG cbDescriptor);
    HRESULT ConfigureContinuousReader (IWDFUsbTargetPipe* pFxPipe);
};

다음 예제 코드는 디바이스 콜백 개체의 QueryInterface 구현을 보여줍니다.

HRESULT CDeviceCallback::QueryInterface(REFIID riid, LPVOID* ppvObject)
{
    if (ppvObject == NULL)
    {
        return E_INVALIDARG;
    }

    *ppvObject = NULL;

    HRESULT hr = E_NOINTERFACE;

    if(  IsEqualIID(riid, __uuidof(IPnpCallbackHardware))   ||  IsEqualIID(riid, __uuidof(IUnknown))  )
    {
        *ppvObject = static_cast<IPnpCallbackHardware*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;

    }

    if(  IsEqualIID(riid, __uuidof(IPnpCallback)))
    {
        *ppvObject = static_cast<IPnpCallback*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;
    }

    if(  IsEqualIID(riid, __uuidof(IUsbTargetPipeContinuousReaderCallbackReadComplete)))
    {
        *ppvObject = static_cast<IUsbTargetPipeContinuousReaderCallbackReadComplete*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;
    }

    if(  IsEqualIID(riid, __uuidof(IUsbTargetPipeContinuousReaderCallbackReadersFailed)))
    {
        *ppvObject = static_cast<IUsbTargetPipeContinuousReaderCallbackReadersFailed*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;
    }

    return hr;
}

다음 예제 코드는 오류 콜백의 구현을 보여줍니다. 읽기 요청이 실패하면 메서드는 디버거에서 프레임워크에서 보고한 오류 코드를 출력하고 프레임워크에 파이프를 다시 설정한 다음 연속 판독기를 다시 시작하도록 지시합니다.

 BOOL CDeviceCallback::OnReaderFailure(
    IWDFUsbTargetPipe * pPipe,
    HRESULT hrCompletion
    )
{
    UNREFERENCED_PARAMETER(pPipe);
    UNREFERENCED_PARAMETER(hrCompletion);
    return TRUE;
}

클라이언트 드라이버가 오류 콜백을 제공하지 않고 오류가 발생하면 프레임워크는 USB 파이프를 다시 시작하고 연속 판독기를 다시 시작합니다.