USB クライアント ドライバー コード構造について (UMDF)

このトピックでは、UMDF ベースの USB クライアント ドライバーのソース コードについて説明します。 コード例は、Microsoft Visual Studio に含まれている USB ユーザー モード ドライバー テンプレートによって生成されます。 テンプレート コードは、アクティブ テンプレート ライブラリ (ATL) を使用して COM インフラストラクチャを生成します。 ATL とクライアント ドライバーの COM 実装の詳細については、ここでは説明しません。

UMDF テンプレート コードの生成手順については、「最初の USB クライアント ドライバー (UMDF) を記述する方法」を参照してください。 テンプレート コードについては、次のセクションで説明します。

テンプレート コードの詳細について説明する前に、UMDF ドライバー開発に関連するヘッダー ファイル (Internal.h) 内のいくつかの宣言を見てみましょう。

Internal.h には、Windows Driver Kit (WDK) に含まれる次のファイルが含まれています。

#include "atlbase.h"
#include "atlcom.h"

#include "wudfddi.h"
#include "wudfusb.h"

Atlbase.h と atlcom.h には、ATL サポートの宣言が含まれています。 クライアント ドライバーによって実装される各クラスは、ATL クラス public CComObjectRootEx を実装します。

Wudfddi.h は、UMDF ドライバー開発に常に含まれています。 ヘッダー ファイルには、UMDF ドライバーをコンパイルするために必要なメソッドと構造体のさまざまな宣言と定義が含まれています。

Wudfusb.h には、フレームワークによって提供される USB I/O ターゲット オブジェクトと通信するために必要な UMDF 構造とメソッドの宣言と定義が含まれています。

Internal.h の次のブロックは、デバイス インターフェイスの GUID 定数を宣言します。 アプリケーションはこの GUID を使用して、SetupDiXxx API を使用 してデバイスへのハンドルを開くことができます。 GUID は、フレームワークがデバイス オブジェクトを作成した後に登録されます。

// Device Interface GUID
// f74570e5-ed0c-4230-a7a5-a56264465548

DEFINE_GUID(GUID_DEVINTERFACE_MyUSBDriver_UMDF_,
    0xf74570e5,0xed0c,0x4230,0xa7,0xa5,0xa5,0x62,0x64,0x46,0x55,0x48);

次の部分では、トレース マクロとトレース GUID を宣言します。 トレース GUID をメモします。トレースを有効にするには必要です。

#define WPP_CONTROL_GUIDS                                              \
    WPP_DEFINE_CONTROL_GUID(                                           \
        MyDriver1TraceGuid, (f0261b19,c295,4a92,aa8e,c6316c82cdf0),    \
                                                                       \
        WPP_DEFINE_BIT(MYDRIVER_ALL_INFO)                              \
        WPP_DEFINE_BIT(TRACE_DRIVER)                                   \
        WPP_DEFINE_BIT(TRACE_DEVICE)                                   \
        WPP_DEFINE_BIT(TRACE_QUEUE)                                    \
        )                             

#define WPP_FLAG_LEVEL_LOGGER(flag, level)                             \
    WPP_LEVEL_LOGGER(flag)

#define WPP_FLAG_LEVEL_ENABLED(flag, level)                            \
    (WPP_LEVEL_ENABLED(flag) &&                                        \
     WPP_CONTROL(WPP_BIT_ ## flag).Level >= level)

#define WPP_LEVEL_FLAGS_LOGGER(lvl,flags) \
           WPP_LEVEL_LOGGER(flags)

#define WPP_LEVEL_FLAGS_ENABLED(lvl, flags) \
           (WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= lvl)

Internal.h の次の行で、キュー コールバック オブジェクトのクライアント ドライバー実装クラスを宣言します。 テンプレートによって生成された他のプロジェクト ファイルも含まれます。 実装およびプロジェクトのヘッダー ファイルについては、このトピックで後ほど説明します。

// Forward definition of queue.

typedef class CMyIoQueue *PCMyIoQueue;

// Include the type specific headers.

#include "Driver.h"
#include "Device.h"
#include "IoQueue.h"

クライアント ドライバーがインストールされると、Windows はクライアント ドライバーとフレームワークをホスト プロセスのインスタンスに読み込みます。 こここから、フレームワークはクライアント ドライバーをロードして初期化します。 このフレームワークは、次のタスクを実行します。

  1. クライアント ドライバーを表すドライバー オブジェクトをフレームワークに作成します。
  2. クラス ファクトリから IDriverEntry インターフェイス ポインターを要求します。
  3. フレームワークにデバイス オブジェクトを作成します。
  4. PnP マネージャーがデバイスを起動した後、デバイス オブジェクトを初期化します。

ドライバーの読み込みと初期化中に、いくつかのイベントが発生し、フレームワークによってクライアント ドライバーがイベントの処理に参加できるようになります。 クライアント ドライバー側では、ドライバーは次のタスクを実行します。

  1. フレームワークがドライバーへの参照を取得できるように、クライアント ドライバー モジュールから DllGetClassObject 関数を実装し、エクスポートします。
  2. IDriverEntry インターフェイスを実装するコールバック クラスを提供します。
  3. IPnpCallbackXxx インターフェイスを実装するコールバック クラスを提供します。
  4. デバイス オブジェクトへの参照を取得し、クライアント ドライバーの要件に従って構成します。

ドライバー コールバックのソース コード

フレームワークは、Windows によって読み込まれたクライアント ドライバーのインスタンスを表すドライバー オブジェクトを作成します。 クライアント ドライバーは、ドライバーをフレームワークに登録するドライバー コールバックを少なくとも 1 つ提供します。

ドライバー コールバックの完全なソース コードは、Driver.h と Driver.c にあります。

クライアント ドライバーは、IUnknown インターフェイスと IDriverEntry インターフェイスを実装するドライバー コールバック クラスを定義する必要があります。 ヘッダー ファイル Driver.h は、ドライバー コールバックを定義する CMyDriver というクラスを宣言します。

EXTERN_C const CLSID CLSID_Driver;

class CMyDriver :
    public CComObjectRootEx<CComMultiThreadModel>,
    public CComCoClass<CMyDriver, &CLSID_Driver>,
    public IDriverEntry
{
public:

    CMyDriver()
    {
    }

    DECLARE_NO_REGISTRY()

    DECLARE_NOT_AGGREGATABLE(CMyDriver)

    BEGIN_COM_MAP(CMyDriver)
        COM_INTERFACE_ENTRY(IDriverEntry)
    END_COM_MAP()

public:

    // IDriverEntry methods

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnInitialize(
        __in IWDFDriver *FxWdfDriver
        )
    {
        UNREFERENCED_PARAMETER(FxWdfDriver);
        return S_OK;
    }

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnDeviceAdd(
        __in IWDFDriver *FxWdfDriver,
        __in IWDFDeviceInitialize *FxDeviceInit
        );

    virtual
    VOID
    STDMETHODCALLTYPE
    OnDeinitialize(
        __in IWDFDriver *FxWdfDriver
        )
    {
        UNREFERENCED_PARAMETER(FxWdfDriver);
        return;
    }

};

OBJECT_ENTRY_AUTO(CLSID_Driver, CMyDriver)

ドライバー コールバックは COM クラスである必要があります。つまり、IUnknown および関連するメソッドを実装する必要があります。 テンプレート コードでは、ATL クラス CComObjectRootEx と CComCoClass に IUnknown メソッドが含まれています。

Windows がホスト プロセスをインスタンス化すると、フレームワークによってドライバー オブジェクトが作成されます。 これを行うために、フレームワークはドライバー コールバック クラスのインスタンスを作成し、DllGetClassObject のドライバー実装を呼び出し (「ドライバー エントリのソース コード」セクションで説明) し、クライアント ドライバーの IDriverEntry インターフェイス ポインターを取得します。 この呼び出しにより、ドライバー コールバック オブジェクトがフレームワーク ドライバー オブジェクトに登録されます。 登録が成功すると、特定のドライバー固有のイベントが発生すると、フレームワークはクライアント ドライバーの実装を呼び出します。 フレームワークが呼び出す最初のメソッドは、IDriverEntry::OnInitialize メソッドです。 クライアント ドライバーの IDriverEntry::OnInitialize の実装では、クライアント ドライバーはグローバル ドライバー リソースを割り当てることができます。 これらのリソースは、クライアント ドライバーのアンロードを準備する直前にフレームワークによって呼び出される IDriverEntry::OnDeinitialize で解放する必要があります。 テンプレート コードは、OnInitialize メソッドと OnDeinitialize メソッドの最小限の実装を提供します。

IDriverEntry の最も重要なメソッドは、IDriverEntry::OnDeviceAdd です。 フレームワークがフレームワーク デバイス オブジェクトを作成する前に (次のセクションで説明します)、ドライバーの IDriverEntry::OnDeviceAdd 実装を呼び出します。 メソッドを呼び出すと、フレームワークはドライバー オブジェクトと IWDFDeviceInitialize ポインターに IWDFDriver ポインターを渡します。 クライアント ドライバーは、IWDFDeviceInitialize メソッドを呼び出して、特定の構成オプションを指定できます。

通常、クライアント ドライバーは、IDriverEntry::OnDeviceAdd 実装で次のタスクを実行します。

  • 作成するデバイス オブジェクトの構成情報を指定します。
  • ドライバーのデバイス コールバック クラスをインスタンス化します。
  • フレームワーク デバイス オブジェクトを作成し、そのデバイス コールバック オブジェクトをフレームワークに登録します。
  • フレームワーク デバイス オブジェクトを初期化します。
  • クライアント ドライバーのデバイス インターフェイス GUID を登録します。

テンプレート コードでは、IDriverEntry::OnDeviceAdd は、デバイス コールバック クラスで定義されている静的メソッド CMyDevice::CreateInstanceAndInitialize を呼び出します。 静的メソッドは、最初にクライアント ドライバーのデバイス コールバック クラスをインスタンス化し、フレームワーク デバイス オブジェクトを作成します。 また、デバイス コールバック クラスは、前の一覧でメンションメインタスクを再実行する Configure という名前のパブリック メソッドも定義します。 デバイス コールバック クラスの実装については、次のセクションで説明します。 次のコード例は、テンプレート コードの IDriverEntry::OnDeviceAdd 実装を示しています。

HRESULT
CMyDriver::OnDeviceAdd(
    __in IWDFDriver *FxWdfDriver,
    __in IWDFDeviceInitialize *FxDeviceInit
    )
{
    HRESULT hr = S_OK;
    CMyDevice *device = NULL;

    hr = CMyDevice::CreateInstanceAndInitialize(FxWdfDriver,
                                                FxDeviceInit,
                                                &device);

    if (SUCCEEDED(hr))
    {
        hr = device->Configure();
    }

    return hr;
}

次のコード例は、Device.h でのデバイス クラスの宣言を示しています。

class CMyDevice :
    public CComObjectRootEx<CComMultiThreadModel>,
    public IPnpCallbackHardware
{

public:

    DECLARE_NOT_AGGREGATABLE(CMyDevice)

    BEGIN_COM_MAP(CMyDevice)
        COM_INTERFACE_ENTRY(IPnpCallbackHardware)
    END_COM_MAP()

    CMyDevice() :
        m_FxDevice(NULL),
        m_IoQueue(NULL),
        m_FxUsbDevice(NULL)
    {
    }

    ~CMyDevice()
    {
    }

private:

    IWDFDevice *            m_FxDevice;

    CMyIoQueue *            m_IoQueue;

    IWDFUsbTargetDevice *   m_FxUsbDevice;

private:

    HRESULT
    Initialize(
        __in IWDFDriver *FxDriver,
        __in IWDFDeviceInitialize *FxDeviceInit
        );

public:

    static
    HRESULT
    CreateInstanceAndInitialize(
        __in IWDFDriver *FxDriver,
        __in IWDFDeviceInitialize *FxDeviceInit,
        __out CMyDevice **Device
        );

    HRESULT
    Configure(
        VOID
        );
public:

    // IPnpCallbackHardware methods

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnPrepareHardware(
            __in IWDFDevice *FxDevice
            );

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnReleaseHardware(
        __in IWDFDevice *FxDevice
        );

};

デバイス コールバックのソース コード

フレームワーク デバイス オブジェクトは、クライアント ドライバーのデバイス スタックに読み込まれるデバイス オブジェクトを表すフレームワーク クラスのインスタンスです。 デバイス オブジェクトの機能の詳細については、「デバイス ノードとデバイス スタック」を参照してください。

デバイス オブジェクトの完全なソース コードは、Device.h と Device.c にあります。

フレームワーク デバイス クラスは、IWDFDevice インターフェイスを実装します。 クライアント ドライバーは、ドライバーの IDriverEntry::OnDeviceAdd の実装でそのクラスのインスタンスを作成する役割を担います。 オブジェクトが作成されると、クライアント ドライバーは新しいオブジェクトへの IWDFDevice ポインターを取得し、そのインターフェイスのメソッドを呼び出してデバイス オブジェクトの操作を管理します。

IDriverEntry::OnDeviceAdd 実装

前のセクションでは、クライアント ドライバーが IDriverEntry::OnDeviceAdd で実行するタスクについて簡単に説明しました。 これらのタスクの詳細については、以下を参照してください。 クライアント ドライバー:

  • 作成するデバイス オブジェクトの構成情報を指定します。

    IDriverEntry::OnDeviceAdd メソッドのクライアント ドライバーの実装に対するフレームワーク呼び出しでは、フレームワークは IWDFDeviceInitialize ポインターを渡します。 クライアント ドライバーは、このポインターを使用して、作成するデバイス オブジェクトの構成情報を指定します。 たとえば、クライアント ドライバーは、クライアント ドライバーがフィルターであるかファンクション ドライバーであるかを指定します。 クライアント ドライバーをフィルター ドライバーとして識別するために、IWDFDeviceInitialize::SetFilter を呼び出します。 その場合、フレームワークはフィルター デバイス オブジェクト (FiDO) を作成します。それ以外の場合は、関数デバイス オブジェクト (FDO) が作成されます。 設定できるもう 1 つのオプションは、IWDFDeviceInitialize::SetLockingConstraint を呼び出すことによる同期モードです。

  • IWDFDeviceInitialize インターフェイス ポインター、デバイス コールバック オブジェクトの IUnknown 参照、およびポインターからポインターへのポインター IWDFDevice 変数を渡すことによって、IWDFDriver::CreateDevice メソッドを呼び出します。

    IWDFDriver::CreateDevice 呼び出しが成功した場合:

    • フレームワークは、デバイス オブジェクトを作成します。

    • フレームワークは、デバイス コールバックをフレームワークに登録します。

      デバイス コールバックがフレームワーク デバイス オブジェクトとペアになると、フレームワークとクライアント ドライバーは、PnP 状態や電源状態の変化などの特定のイベントを処理します。 たとえば、PnP マネージャーがデバイスを起動すると、フレームワークに通知されます。 その後、フレームワークはデバイス コールバックの IPnpCallbackHardware::OnPrepareHardware 実装を呼び出します。 すべてのクライアント ドライバーは、少なくとも 1 つのデバイス コールバック オブジェクトを登録する必要があります。

    • クライアント ドライバーは、IWDFDevice 変数の新しいデバイス オブジェクトのアドレスを受け取ります。 フレームワーク デバイス オブジェクトへのポインターを受信すると、クライアント ドライバーは、I/O フローのキューの設定やデバイス インターフェイス GUID の登録などの初期化タスクを続行できます。

  • IWDFDevice::CreateDeviceInterface を呼び出して、クライアント ドライバーのデバイス インターフェイス GUID を登録します。 アプリケーションでは、GUID を使用してクライアント ドライバーに要求を送信できます。 GUID 定数は Internal.h で宣言されています。

  • デバイスとの間の I/O 転送のキューを初期化します。

テンプレート コードは、構成情報を指定してデバイス オブジェクトを作成するヘルパー メソッド Initialize を定義します。

次のコード例は、Initialize の実装を示しています。

HRESULT
CMyDevice::Initialize(
    __in IWDFDriver           * FxDriver,
    __in IWDFDeviceInitialize * FxDeviceInit
    )
{
    IWDFDevice *fxDevice = NULL;
    HRESULT hr = S_OK;
    IUnknown *unknown = NULL;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

    FxDeviceInit->SetLockingConstraint(None);

    FxDeviceInit->SetPowerPolicyOwnership(TRUE);

    hr = this->QueryInterface(__uuidof(IUnknown), (void **)&unknown);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to get IUnknown %!hresult!",
                    hr);
        goto Exit;
    }

    hr = FxDriver->CreateDevice(FxDeviceInit, unknown, &fxDevice);
    DriverSafeRelease(unknown);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to create a framework device %!hresult!",
                    hr);
        goto Exit;
    }

     m_FxDevice = fxDevice;

     DriverSafeRelease(fxDevice);

Exit:

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");

    return hr;
}

前述のコード例では、クライアント ドライバーがデバイス オブジェクトを作成し、そのデバイス コールバックを登録します。 デバイス オブジェクトを作成する前に、ドライバーは IWDFDeviceInitialize インターフェイス ポインターでメソッドを呼び出すことによって、その構成設定を指定します。 これは、クライアント ドライバーの IDriverEntry::OnDeviceAdd メソッドへの以前の呼び出しでフレームワークによって渡されたのと同じポインターです。

クライアント ドライバーは、デバイス オブジェクトの電源ポリシーの所有者になることを指定します。 クライアント ドライバーは、電源ポリシーの所有者として、システムの電源状態が変化したときにデバイスが移行する適切な電源状態を決定します。 ドライバーは、電源状態を遷移させるために、関連する要求をデバイスに送信する責任もあります。 既定では、UMDF ベースのクライアント ドライバーは電源ポリシーの所有者ではありません。 フレームワークはすべての電源状態の遷移を処理します。 フレームワークは、システムがスリープ状態になったときにデバイスを D3 に自動的に送信し、逆にシステムが S0 の動作状態になったときにデバイスを D0 に戻します。 詳細については、「UMDF の電源ポリシーの所有権」を参照してください。

もう 1 つの構成オプションは、クライアント ドライバーがデバイスのフィルター ドライバーであるかファンクション ドライバーであるかを指定することです。 コード例では、クライアント ドライバーがその設定を明示的に指定していないことに注意してください。 つまり、クライアント ドライバーはファンクション ドライバーであり、フレームワークはデバイス スタックに FDO を作成する必要があります。 クライアント ドライバーをフィルター ドライバーにする場合、ドライバーは IWDFDeviceInitialize::SetFilter メソッドを呼び出す必要があります。 その場合、フレームワークはデバイス スタックに FiDO を作成します。

クライアント ドライバーは、クライアント ドライバーのコールバックに対するフレームワークの呼び出しが同期されないことも指定します。 クライアント ドライバーは、すべての同期タスクを処理します。 この基本設定を指定するために、クライアント ドライバーは IWDFDeviceInitialize::SetLockingConstraint メソッドを呼び出します。

次に、クライアント ドライバーは、IUnknown::QueryInterface を呼び出して、デバイス コールバック クラスへの IUnknown ポインターを取得します。 その後、クライアント ドライバーは IWDFDriver::CreateDevice を呼び出します。これによってフレームワーク デバイス オブジェクトが作成され、IUnknown ポインターを使用してクライアント ドライバーのデバイス コールバックが登録されます。

クライアント ドライバーは、デバイス コールバック クラスのプライベート データ メンバーにデバイス オブジェクトのアドレス (IWDFDriver::CreateDevice 呼び出しを通じて受信) を格納し、DriverSafeRelease (Internal.h で定義されたインライン関数) を呼び出してその参照を解放します。 これは、デバイス オブジェクトの有効期間がフレームワークによって追跡されるためです。 そのため、クライアント ドライバーは、デバイス オブジェクトの追加の参照カウントを保持する必要はありません。

テンプレート コードは、デバイス インターフェイス GUID を登録し、キューを設定するパブリック メソッド Configure を定義します。 次のコード例は、デバイス コールバック クラス CMyDevice の Configure メソッドの定義を示しています。 構成は、フレームワーク デバイス オブジェクトの作成後に IDriverEntry::OnDeviceAdd によって呼び出されます。

CMyDevice::Configure(
    VOID
    )
{

    HRESULT hr = S_OK;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

     hr = CMyIoQueue::CreateInstanceAndInitialize(m_FxDevice, this, &m_IoQueue);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to create and initialize queue %!hresult!",
                    hr);
        goto Exit;
    }

    hr = m_IoQueue->Configure();
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to configure queue %!hresult!",
                    hr);
        goto Exit;
    } 

    hr = m_FxDevice->CreateDeviceInterface(&GUID_DEVINTERFACE_MyUSBDriver_UMDF_,NULL);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to create device interface %!hresult!",
                    hr);
        goto Exit;
    }

Exit:

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");

    return hr;
}

前述のコード例では、クライアント ドライバーは、I/O フローのキューの初期化とデバイス インターフェイス GUID の登録という 2 つの主要なタスクを実行します。

キューは CMyIoQueue クラスで作成および構成されます。 最初のタスクは、CreateInstanceAndInitialize という名前の静的メソッドを呼び出して、そのクラスをインスタンス化することです。 クライアント ドライバーは、キューを初期化する構成を呼び出します。 CreateInstanceAndInitialize と Configure は CMyIoQueue で宣言されます。これについては、このトピックで後ほど説明します。

また、クライアント ドライバーは IWDFDevice::CreateDeviceInterface を呼び出して、クライアント ドライバーのデバイス インターフェイス GUID を登録します。 アプリケーションでは、GUID を使用してクライアント ドライバーに要求を送信できます。 GUID 定数は Internal.h で宣言されています。

IPnpCallbackHardware の実装と USB 固有のタスク

次に、Device.cpp の IPnpCallbackHardware インターフェイスの実装を見てみましょう。

すべてのデバイス コールバック クラスは、IPnpCallbackHardware インターフェイスを実装する必要があります。 このインターフェイスには、IPnpCallbackHardware::OnPrepareHardware および IPnpCallbackHardware::OnReleaseHardware の 2 つのメソッドがあります。 フレームワークは、PnP マネージャーがデバイスを起動したときとデバイスを削除したときの 2 つのイベントに応答して、これらのメソッドを呼び出します。 デバイスが起動されると、ハードウェアへの通信は確立されますが、デバイスは動作状態 (D0) に入っていません。 そのため、IPnpCallbackHardware::OnPrepareHardware では、クライアント ドライバーは、ハードウェアからデバイス情報を取得し、リソースを割り当て、ドライバーの有効期間中に必要なフレームワーク オブジェクトを初期化できます。 PnP マネージャーがデバイスを削除すると、ドライバーはシステムからアンロードされます。 フレームワークは、クライアント ドライバーの IPnpCallbackHardware::OnReleaseHardware 実装を呼び出します。この実装では、ドライバーはこれらのリソースとフレームワーク オブジェクトを解放できます。

PnP マネージャーは、PnP 状態の変更に起因する他の種類のイベントを生成できます。 フレームワークは、これらのイベントの既定の処理を提供します。 クライアント ドライバーは、これらのイベントの処理に参加することを選択できます。 USB デバイスがホストから切り離されるシナリオを考えてみましょう。 PnP マネージャーは、そのイベントを認識し、フレームワークに通知します。 クライアント ドライバーがイベントに応答して追加のタスクを実行する場合、ドライバーは IPnpCallback インターフェイスと関連する IPnpCallback::OnSurpriseRemoval メソッドをデバイス コールバック クラスに実装する必要があります。 それ以外の場合、フレームワークはイベントの既定の処理を続行します。

USB クライアント ドライバーは、データ転送のための I/O 要求を送信する前に、サポートされているインターフェイス、代替設定、およびエンドポイントに関する情報を取得し、構成する必要があります。 UMDF は、クライアント ドライバーの構成タスクの多くを簡素化する特殊な I/O ターゲット オブジェクトを提供します。 USB デバイスを構成するには、クライアント ドライバーには、PnP マネージャーがデバイスを起動した後にのみ使用できるデバイス情報が必要です。

このテンプレート コードは、IPnpCallbackHardware::OnPrepareHardware メソッドでこれらのオブジェクトを作成します。

通常、クライアント ドライバーは、(デバイスの設計に応じて) 次の構成タスクの 1 つ以上を実行します。

  1. インターフェイスの数など、現在の構成に関する情報を取得します。 フレームワークは、USB デバイスの最初の構成を選択します。 複数構成デバイスの場合、クライアント ドライバーは別の構成を選択できません。
  2. エンドポイントの数など、インターフェイスに関する情報を取得します。
  3. インターフェイスが複数の設定をサポートしている場合は、各インターフェイス内の代替設定を変更します。 既定では、フレームワークは USB デバイスの最初の構成で各インターフェイスの最初の代替設定を選択します。 クライアント ドライバーは、代替設定を選択できます。
  4. 各インターフェイス内のエンドポイントに関する情報を取得します。

これらのタスクを実行するために、クライアント ドライバーは、WDF によって提供されるこれらの種類の特殊な USB I/O ターゲット オブジェクトを使用できます。

USB I/O ターゲット オブジェクト 説明 UMDF インターフェイス
ターゲット デバイス オブジェクト USB デバイスを表し、デバイス記述子を取得し、デバイスに制御要求を送信するためのメソッドを提供します。 IWDFUsbTargetDevice
ターゲット インターフェイス オブジェクト 個別のインターフェイスを表し、クライアント ドライバーが代替設定を選択し、設定に関する情報を取得するために呼び出すことができるメソッドを提供します。 IWDFUsbInterface
ターゲット パイプ オブジェクト インターフェイスの現在の代替設定で構成されているエンドポイントの個別のパイプを表します。 USB バス ドライバーは、選択された構成内の各インターフェイスを選択し、インターフェイス内の各エンドポイントへのコミュニケーション チャネルを設定します。 USB の用語では、その通信チャネルはパイプと呼ばれます。 IWDFUsbTargetPipe

次のコード例は、IPnpCallbackHardware::OnPrepareHardware の実装を示しています。

HRESULT
CMyDevice::OnPrepareHardware(
    __in IWDFDevice * /* FxDevice */
    )
{
    HRESULT hr;
    IWDFUsbTargetFactory *usbFactory = NULL;
    IWDFUsbTargetDevice *usbDevice = NULL;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

    hr = m_FxDevice->QueryInterface(IID_PPV_ARGS(&usbFactory));

    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to get USB target factory %!hresult!",
                    hr);
        goto Exit;
    }

    hr = usbFactory->CreateUsbTargetDevice(&usbDevice);

    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to create USB target device %!hresult!",
                    hr);

        goto Exit;
    }

     m_FxUsbDevice = usbDevice;

Exit:

    DriverSafeRelease(usbDevice);

    DriverSafeRelease(usbFactory);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");

    return hr;
}

フレームワークの USB I/O ターゲット オブジェクトを使用するには、クライアント ドライバーは最初に USB ターゲット デバイス オブジェクトを作成する必要があります。 フレームワーク オブジェクト モデルでは、USB ターゲット デバイス オブジェクトは、USB デバイスを表すデバイス オブジェクトの子です。 USB ターゲット デバイス オブジェクトはフレームワークによって実装され、構成の選択など、USB デバイスのすべてのデバイス レベルのタスクを実行します。

前のコード例では、クライアント ドライバーはフレームワーク デバイス オブジェクトを照会し、USB ターゲット デバイス オブジェクトを作成するクラス ファクトリへの IWDFUsbTargetFactory ポインターを取得します。 このポインターを使用して、クライアント ドライバーは IWDFUsbTargetDevice::CreateUsbTargetDevice メソッドを呼び出します。 このメソッドは、USB ターゲット デバイス オブジェクトを作成し、IWDFUsbTargetDevice インターフェイスへのポインターを返します。 このメソッドでは、既定の (最初の) 構成と、その構成内の各インターフェイスの代替設定 0 も選択します。

テンプレート コードは、USB ターゲット デバイス オブジェクト (IWDFDriver::CreateDevice 呼び出しを通じて受信) のアドレスをデバイス コールバック クラスのプライベート データ メンバーに格納し、DriverSafeRelease を呼び出してその参照を解放します。 USB ターゲット デバイス オブジェクトの参照カウントは、フレームワークによってメインされます。 デバイス オブジェクトが有効である限り、オブジェクトは有効です。 クライアント ドライバーは、IPnpCallbackHardware::OnReleaseHardware で参照を解放する必要があります。

クライアント ドライバーが USB ターゲット デバイス オブジェクトを作成した後、ドライバーは IWDFUsbTargetDevice メソッドを呼び出して次のタスクを実行します。

  • デバイス、構成、インターフェイス記述子、およびその他の情報 (デバイス速度など) を取得します。
  • I/O 制御要求を書式設定して既定のエンドポイントに送信します。
  • USB デバイス全体の電源ポリシーを設定します。

詳細については、「UMDF での USB デバイスの操作」を参照してください。 次のコード例は、IPnpCallbackHardware::OnReleaseHardware の実装を示しています。

HRESULT
CMyDevice::OnReleaseHardware(
    __in IWDFDevice * /* FxDevice */
    )
{
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

    if (m_FxUsbDevice != NULL) {

        m_FxUsbDevice->DeleteWdfObject();
        m_FxUsbDevice = NULL;
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");

    return S_OK;
}

キューのソース コード

フレームワーク キュー オブジェクトは、特定のフレームワーク デバイス オブジェクトの I/O キューを表します。 キュー オブジェクトの完全なソース コードは、IoQueue.h と IoQueue.c にあります。

IoQueue.h

ヘッダー ファイル IoQueue.h は、キュー コールバック クラスを宣言します。

class CMyIoQueue :
    public CComObjectRootEx<CComMultiThreadModel>,
    public IQueueCallbackDeviceIoControl
{

public:

    DECLARE_NOT_AGGREGATABLE(CMyIoQueue)

    BEGIN_COM_MAP(CMyIoQueue)
        COM_INTERFACE_ENTRY(IQueueCallbackDeviceIoControl)
    END_COM_MAP()

    CMyIoQueue() : 
        m_FxQueue(NULL),
        m_Device(NULL)
    {
    }

    ~CMyIoQueue()
    {
        // empty
    }

    HRESULT
    Initialize(
        __in IWDFDevice *FxDevice,
        __in CMyDevice *MyDevice
        );

    static 
    HRESULT 
    CreateInstanceAndInitialize( 
        __in IWDFDevice *FxDevice,
        __in CMyDevice *MyDevice,
        __out CMyIoQueue**    Queue
        );

    HRESULT
    Configure(
        VOID
        )
    {
        return S_OK;
    }


    // IQueueCallbackDeviceIoControl

    virtual
    VOID
    STDMETHODCALLTYPE
    OnDeviceIoControl( 
        __in IWDFIoQueue *pWdfQueue,
        __in IWDFIoRequest *pWdfRequest,
        __in ULONG ControlCode,
        __in SIZE_T InputBufferSizeInBytes,
        __in SIZE_T OutputBufferSizeInBytes
        );

private:

    IWDFIoQueue *               m_FxQueue;

    CMyDevice *                 m_Device;

};

前のコード例では、クライアント ドライバーはキュー コールバック クラスを宣言しています。 インスタンス化されると、オブジェクトは、要求がクライアント ドライバーにディスパッチされる方法を処理するフレームワーク キュー オブジェクトと連携します。 このクラスは、フレームワーク キュー オブジェクトを作成および初期化する 2 つのメソッドを定義します。 静的メソッド CreateInstanceAndInitialize はキュー コールバック クラスをインスタンス化し、フレームワーク キュー オブジェクトを作成して初期化する Initialize メソッドを呼び出します。 キュー オブジェクトのディスパッチ オプションも指定します。

HRESULT 
CMyIoQueue::CreateInstanceAndInitialize(
    __in IWDFDevice *FxDevice,
    __in CMyDevice *MyDevice,
    __out CMyIoQueue** Queue
    )
{

    CComObject<CMyIoQueue> *pMyQueue = NULL;
    HRESULT hr = S_OK;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Entry");

    hr = CComObject<CMyIoQueue>::CreateInstance( &pMyQueue );
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_QUEUE,
                    "%!FUNC! Failed to create instance %!hresult!",
                    hr);
        goto Exit;
    }

    hr = pMyQueue->Initialize(FxDevice, MyDevice);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_QUEUE,
                    "%!FUNC! Failed to initialize %!hresult!",
                    hr);
        goto Exit;
    }

    *Queue = pMyQueue;

Exit:

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Exit");

    return hr;
}

次のコード例は、Initialize メソッドの実装を示しています。

HRESULT
CMyIoQueue::Initialize(
    __in IWDFDevice *FxDevice,
    __in CMyDevice *MyDevice
    )
{
    IWDFIoQueue *fxQueue = NULL;
    HRESULT hr = S_OK;
    IUnknown *unknown = NULL;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Entry");

    assert(FxDevice != NULL);
    assert(MyDevice != NULL);

    hr = this->QueryInterface(__uuidof(IUnknown), (void **)&unknown);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_QUEUE,
                    "%!FUNC! Failed to query IUnknown interface %!hresult!",
                    hr);
        goto Exit;
    }

    hr = FxDevice->CreateIoQueue(unknown,
                                 FALSE,     // Default Queue?
                                 WdfIoQueueDispatchParallel,  // Dispatch type
                                 TRUE,     // Power managed?
                                 FALSE,     // Allow zero-length requests?
                                 &fxQueue); // I/O queue
    DriverSafeRelease(unknown);

    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR, 
                   TRACE_QUEUE, 
                   "%!FUNC! Failed to create framework queue.");
        goto Exit;
    }

    hr = FxDevice->ConfigureRequestDispatching(fxQueue,
                                               WdfRequestDeviceIoControl,
                                               TRUE);

    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR, 
                   TRACE_QUEUE, 
                   "%!FUNC! Failed to configure request dispatching %!hresult!.",
                   hr);
        goto Exit;
    }

    m_FxQueue = fxQueue;
    m_Device= MyDevice;

Exit:

    DriverSafeRelease(fxQueue);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Exit");

    return hr;
}

前述のコード例では、クライアント ドライバーがフレームワーク キュー オブジェクトを作成します。 フレームワークは、クライアント ドライバーへの要求フローを処理するためのキュー オブジェクトを提供します。

オブジェクトを作成するために、クライアント ドライバーは IWDFDriver::CreateDevice への以前の呼び出しで取得した IWDFDevice 参照で IWDFDevice::CreateIoQueue を呼び出します。

IWDFDevice::CreateIoQueue 呼び出しでは、フレームワークがキューを作成する前に、クライアント ドライバーによって特定の構成オプションが指定されます。 これらのオプションは、キューが電源管理されるかどうか、長さ 0 の要求を許可するかどうか、およびドライバーの既定のキューとして機能するかどうかを決定します。 クライアント ドライバーは、次の一連の情報を提供します。

  • そのキュー コールバック クラスへの参照

    キュー コールバック クラスへの IUnknown ポインターを指定します。 これにより、フレームワーク キュー オブジェクトとクライアント ドライバーのキュー コールバック オブジェクトの間にパートナーシップが作成されます。 I/O マネージャーは、アプリケーションから新しい要求を受信すると、フレームワークに通知します。 その後、フレームワークは IUnknown ポインターを使用して、キュー コールバック オブジェクトによって公開されるパブリック メソッドを呼び出します。

  • 既定またはセカンダリ キュー

    キューは、既定のキューまたはセカンダリ キューである必要があります。 フレームワーク キュー オブジェクトが既定のキューとして機能する場合、すべての要求がキューに追加されます。 セカンダリ キューは、特定の種類の要求専用です。 クライアント ドライバーがセカンダリ キューを要求する場合、ドライバーは IWDFDevice::ConfigureRequestDispatching メソッドも呼び出して、フレームワークが指定されたキューに配置する必要がある要求の種類を示す必要があります。 テンプレート コードでは、クライアント ドライバーは bDefaultQueue パラメーターに FALSE を渡します。 これにより、既定のキューではなく、セカンダリ キューを作成するようにメソッドに指示します。 後で IWDFDevice::ConfigureRequestDispatching を呼び出して、キューにデバイス I/O 制御要求のみが必要であることを示します (このセクションのコード例を参照)。

  • ディスパッチの種類

    キュー オブジェクトのディスパッチの種類は、フレームワークがクライアント ドライバーに要求を配信する方法を決定します。 配信メカニズムは、順次、並列、またはクライアント ドライバーによって定義されたカスタム メカニズムによって行うことができます。 順次キューの場合、クライアント ドライバーが前の要求を完了するまで、要求は配信されません。 並列ディスパッチ モードでは、フレームワークは I/O マネージャーから要求が到着すると、すぐに要求を転送します。 これは、クライアント ドライバーが別の要求を処理している間に要求を受信できることを意味します。 カスタム メカニズムでは、ドライバーが次の要求を処理する準備ができたら、クライアントはフレームワーク キュー オブジェクトから次の要求を手動で引き出します。 テンプレート コードでは、クライアント ドライバーは並列ディスパッチ モードを要求します。

  • 電源管理キュー

    フレームワーク キュー オブジェクトは、PnP およびデバイスの電源状態と同期する必要があります。 デバイスが作業状態でない場合、フレームワーク キュー オブジェクトは、すべての要求のディスパッチを停止します。 デバイスが作業状態になると、キュー オブジェクトはディスパッチを再開します。 電源管理キューでは、同期はフレームワークによって実行されます。それ以外の場合は、クライアント ドライブがそのタスクを処理する必要があります。 テンプレート コードでは、クライアントは電源管理キューを要求します。

  • 許可される長さ 0 の要求

    クライアント ドライバーは、I/O 要求をキューに入れるのではなく、長さ 0 のバッファーで完了するようにフレームワークに指示できます。 テンプレート コードでは、クライアントはそのような要求を完了するようにフレームワークに要求します。

1 つのフレームワーク キュー オブジェクトは、読み取り、書き込み、デバイス I/O 制御など、いくつかの種類の要求を処理できます。 テンプレート コードに基づくクライアント ドライバーは、デバイス I/O 制御要求のみを処理できます。 そのため、クライアント ドライバーのキュー コールバック クラスは、IQueueCallbackDeviceIoControl インターフェイスとその IQueueCallbackDeviceIoControl::OnDeviceIoControl メソッドを実装します。 これにより、フレームワークがデバイス I/O コントロール要求を処理するときに、フレームワークがクライアント ドライバーの IQueueCallbackDeviceIoControl::OnDeviceIoControl の実装を呼び出すことができます。

その他の種類の要求の場合、クライアント ドライバーは対応する IQueueCallbackXxx インターフェイスを実装する必要があります。 たとえば、クライアント ドライバーが読み取り要求を処理する場合、キュー コールバック クラスは IQueueCallbackRead インターフェイスとその IQueueCallbackRead::OnRead メソッドを実装する必要があります。 要求とコールバック インターフェイスの種類については、「I/O キュー イベント コールバック関数」を参照してください。

次のコード例は、IQueueCallbackDeviceIoControl::OnDeviceIoControl の実装を示しています。

VOID
STDMETHODCALLTYPE
CMyIoQueue::OnDeviceIoControl(
    __in IWDFIoQueue *FxQueue,
    __in IWDFIoRequest *FxRequest,
    __in ULONG ControlCode,
    __in SIZE_T InputBufferSizeInBytes,
    __in SIZE_T OutputBufferSizeInBytes
    )
{
    UNREFERENCED_PARAMETER(FxQueue);
    UNREFERENCED_PARAMETER(ControlCode);
    UNREFERENCED_PARAMETER(InputBufferSizeInBytes);
    UNREFERENCED_PARAMETER(OutputBufferSizeInBytes);

    HRESULT hr = S_OK;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Entry");

    if (m_Device == NULL) {
        // We don't have pointer to device object
        TraceEvents(TRACE_LEVEL_ERROR, 
                   TRACE_QUEUE, 
                   "%!FUNC!NULL pointer to device object.");
        hr = E_POINTER;
        goto Exit;
    }

    //
    // Process the IOCTLs
    //

Exit:

    FxRequest->Complete(hr);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Exit");

    return;

}

キュー メカニズムのしくみを見てみましょう。 USB デバイスと通信するために、アプリケーションは最初にデバイスにハンドルを開き、特定のコントロール コードで DeviceIoControl 関数を呼び出して、デバイス I/O 制御要求を送信します。 制御コードのタイプに応じて、アプリケーションはその呼び出しで入力バッファーと出力バッファーを指定できます。 呼び出しは最終的に I/O マネージャーによって受信され、フレームワークに通知されます。 フレームワークはフレームワーク要求オブジェクトを作成し、フレームワーク キュー オブジェクトに追加します。 テンプレート コードでは、キュー オブジェクトが WdfIoQueueDispatchParallel フラグを使用して作成されているため、要求がキューに追加されるとすぐにコールバックが呼び出されます。

フレームワークは、クライアント ドライバーのイベント コールバックを呼び出すと、アプリケーションによって送信された要求 (およびその入力バッファーと出力バッファー) を保持するフレームワーク要求オブジェクトにハンドルを渡します。 さらに、その要求を含むフレームワーク キュー オブジェクトにハンドルを送信します。 イベント コールバックでは、クライアント ドライバーは必要に応じて要求を処理します。 テンプレート コードは、要求を完了するだけです。 クライアント ドライバーは、より複雑なタスクを実行できます。 たとえば、アプリケーションがイベント コールバックで特定のデバイス情報を要求した場合、クライアント ドライバーは USB 制御要求を作成し、それを USB ドライバー スタックに送信して、要求されたデバイス情報を取得できます。 USB コントロールの要求については、「USB コントロール転送」で説明します。

ドライバー エントリのソース コード

テンプレート コードでは、ドライバー エントリが Dllsup.cpp に実装されています。

Dllsup.cpp

include セクションの後に、クライアント ドライバーの GUID 定数が宣言されます。 その GUID は、ドライバーのインストール ファイル (INF) の GUID と一致する必要があります。

const CLSID CLSID_Driver =
{0x079e211c,0x8a82,0x4c16,{0x96,0xe2,0x2d,0x28,0xcf,0x23,0xb7,0xff}};

次のコード ブロックは、クライアント ドライバーのクラス ファクトリを宣言します。

class CMyDriverModule :
    public CAtlDllModuleT< CMyDriverModule >
{
};

CMyDriverModule _AtlModule;

テンプレート コードでは、ATL サポートを使用して複雑な COM コードをカプセル化します。 クラス ファクトリは、クライアント ドライバーの作成に必要なすべてのコードを含むテンプレート クラス CAtlDllModuleT を継承します。

次のコード スニペットは、DllMain の実装を示しています

extern "C"
BOOL
WINAPI
DllMain(
    HINSTANCE hInstance,
    DWORD dwReason,
    LPVOID lpReserved
    )
{
    if (dwReason == DLL_PROCESS_ATTACH) {
        WPP_INIT_TRACING(MYDRIVER_TRACING_ID);

        g_hInstance = hInstance;
        DisableThreadLibraryCalls(hInstance);

    } else if (dwReason == DLL_PROCESS_DETACH) {
        WPP_CLEANUP();
    }

    return _AtlModule.DllMain(dwReason, lpReserved);
}

クライアント ドライバーが DllMain 関数を実装している場合、Windows は DllMain をクライアント ドライバー モジュールのエントリ ポイントと見なします。 Windows は、WUDFHost.exe にクライアント ドライバー モジュールを読み込んだ後で、DllMain を呼び出します。 Windows は、Windows がメモリ内のクライアント ドライバーをアンロードする直前に、DllMain を再度呼び出します。 DllMain は、ドライバー レベルでグローバル変数を割り当てて解放できます。 テンプレート コードでは、クライアント ドライバーは WPP トレースに必要なリソースを初期化して解放し、ATL クラスの DllMain 実装を呼び出します。

次のコード スニペットは、DllGetClassObject の実装を示しています

STDAPI
DllGetClassObject(
    __in REFCLSID rclsid,
    __in REFIID riid,
    __deref_out LPVOID FAR* ppv
    )
{
    return _AtlModule.DllGetClassObject(rclsid, riid, ppv);
}

テンプレート コードでは、クラス ファクトリと DllGetClassObject が ATL に実装されます。 上記のコード スニペットは、ATL DllGetClassObject 実装を呼び出すだけです。 一般に、DllGetClassObject は次のタスクを実行する必要があります。

  1. フレームワークによって渡される CLSID がクライアント ドライバーの GUID であることを確認します。 フレームワークは、ドライバーの INF ファイルからクライアント ドライバーの CLSID を取得します。 検証中に、指定した GUID が INF で指定した GUID と一致することを確認してください。
  2. クライアント ドライバーによって実装されるクラス ファクトリをインスタンス化します。 テンプレート コードでは、これは ATL クラスによってカプセル化されます。
  3. クラス ファクトリの IClassFactory インターフェイスへのポインターを取得し、取得したポインターをフレームワークに返します。

クライアント ドライバー モジュールがメモリに読み込まれた後、フレームワークはドライバーが提供する DllGetClassObject 関数を呼び出します。 DllGetClassObject へのフレームワークの呼び出しでは、フレームワークは、クライアント ドライバーを識別し、クラス ファクトリの IClassFactory インターフェイスへのポインターを要求する CLSID を渡します。 クライアント ドライバーは、ドライバー コールバックの作成を容易にするクラス ファクトリを実装します。 したがって、クライアント ドライバーには少なくとも 1 つのクラス ファクトリが含まれている必要があります。 その後、フレームワークは IClassFactory::CreateInstance を呼び出し、ドライバー コールバック クラスへの IDriverEntry ポインターを要求します。

Exports.def

フレームワークが DllGetClassObject を呼び出すには、クライアント ドライバーが .def ファイルから関数をエクスポートする必要があります。 ファイルは Visual Studio プロジェクトに既に含まれています。

; Exports.def : Declares the module parameters.

LIBRARY     "MyUSBDriver_UMDF_.DLL"

EXPORTS
        DllGetClassObject   PRIVATE

ドライバー プロジェクトに含まれる Export.def の上記のコード スニペットでは、クライアントはドライバー モジュールの名前を LIBRARY として提供し、DllGetClassObject を EXPORTS の下に指定します。 詳細については、「DEFファイルを使用した DLL からのエクスポート」を参照してください。