Date

Media Foundation では、ある処理を非同期的に実行する仕組みが用意されている。呼び出し側のスレッドをブロックせずに実行できるのでパフォーマンス向上が見込める。

非同期処理の呼び出し Begin... / End... パターン

MF において、非同期的に実行する API は Begin... End... という形式の名前となっている。

例) MFBeginCreateFile, MFEndCreatefile

呼び出すには以下のようにする。

  1. 呼び出す側に IMFAsyncCallback インターフェイスを実装したクラスを用意しインスタンスを作成する。
  2. Begin... を呼び出す。以下を渡す。
  3. IMFAsyncCallback のポインタ (1 のポインタ)
  4. state object (オプション)
  5. 処理が完了すると、2 で渡した IMFAsyncCallback::Invoke が呼び出される。
  6. IMFAsyncCallback::Invoke の中から End... を呼び出す。以下を渡す。
  7. Invoke で渡された引数 IMFAsyncResult * pAsyncResult
  8. 実行結果などの受け取り先(オプション)

独自の非同期処理

アプリケーション独自の非同期処理を実装することもできる。

  1. 非同期処理を行うクラスに IMFAsyncCallback を実装する。
  2. Begin... メソッドの中で、 MFPutWorkItemMFInvokeCallback などを使い IMFAsyncCallback::Invoke を呼び出す。
  3. End... メソッドの中で、結果を返す。

例 : 非同期的にビデオデコーダ MFTを列挙する

例として、非同期的にビデオデコーダ MFT を列挙する処理を書いてみる。以下の 2 つのクラスを実装する。両方とも IMFAsyncCallback を継承している。

クラス
AsyncMFTEnum ビデオデコーダ MFT を列挙する。
MyCaller AsyncMFTEnum の処理を呼び出し、結果を受け取る。

MyCaller クラス

アプリケーションが開始されたら、COM と MF を初期化する。その後 MyCaller::Exec を呼び出すことにする。

MyCaller::ExecMyCaller::Invoke のコードを示す。

```c++ MyCaller::Exec using namespace Microsoft::WRL;

HRESULT MyCaller::Exec() { HRESULT hr; _CompletionEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); //…{1} _pOperation = Make(); hr = _pOperation->BeginEnumMFT(nullptr, this, nullptr); //…{2} if (FAILED(hr)) { CloseHandle(_CompletionEvent); return hr; } WaitForSingleObject(_CompletionEvent, INFINITE); //…{3} CloseHandle(_CompletionEvent);

1
2
3
4
5
6
```c++ MyCaller::Invoke
STDMETHODIMP MyCaller::Invoke(IMFAsyncResult *pAsyncResult) {
    _hrStatus = _pOperation->EndEnumMFT(pAsyncResult, &_ppActivate, &_Count); //…{4}
    SetEvent(_CompletionEvent);
    return S_OK;
}
  1. MyCaller::Exec で完了待ち用イベントを作成する。
  2. AsyncMFTEnum::BeginEnumMFT (後述)を呼び出して列挙を開始する。
  3. IMFAsyncCallback のポインタとして this を渡す。列挙後に MyCaller::Invoke が呼び出されるようになる。
  4. WaitForSingleObject でイベントがシグナルになるまで待つ。
  5. MyCaller::Invoke が呼ばれたら、 `AsyncMFTEnum::EndEnumMFT を呼び出して結果を受け取る。イベントをシグナルにする。

  6. Invoke メソッド内で、ブロックする処理は避ける。 イベントやウィンドウメッセージを利用して Invoke の外側で処理を行う。

AsyncMFTEnum クラス

AsyncMFTEnum::BeginEnumMFT のコードを示す。

```c++ AsyncMFTEnum::BeginEnumMFT HRESULT AsyncMFTEnum::BeginEnumMFT(IUnknown * pObj, IMFAsyncCallback * pCallback, IUnknown * pState) { if (pCallback == nullptr) { return E_INVALIDARG; } HRESULT hr; _Count = 0; // …{1} _ppActivate = nullptr; ComPtr async_result; // …{2} hr = MFCreateAsyncResult(pObj, pCallback, pState, &async_result);

1
2
3
4
ComPtr<IMFAsyncResult> async_result_for_invoke;  // …{3}
MFCreateAsyncResult(nullptr, this, async_result.Get(), &async_result_for_invoke);
hr = MFInvokeCallback(async_result_for_invoke.Get()); // …{4}
return hr;

}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
1. 結果を保持するメンバ変数 `_Count`, `_ppActivate` を初期化する。
2. 列挙が終わったことを `MyCaller` 側で知るために
`MFCreateAsyncResult` で asynchronous result object を作成する。
   * `BeginEnumMFT` の引数 `pObj`, `pCallback`, `pState` から IMFAsyncResult インターフェイスへのポインタを得る。ここで `pObj`, `pState` は `nullptr` であり未使用。
3. `AsyncMFTEnum::Invoke` を非同期的に呼び出すために、 `MFCreateAsyncResult` を呼び出して asynchronous result object を作成する。
   * state object として 2 で作成したオブジェクトを渡す。
4. 3 で得たポインタを引数として `MFInvokeCallback` を呼び出す。

`MFCreateAsyncResult` を 2 回呼び出すところがポイント。

* 1 回目は `AsyncMFTEnum::Invoke` から `MyCaller::Invoke` を呼び出すため。これにより、列挙が終わったことを `MyCaller` 側で知ることができる。
* 2 回目は `AsyncMFTEnum::BeginEnumMFT` から `AsyncMFTEnum::Invoke` を呼び出すため。

[`MFInvokeCallback`](http://msdn.microsoft.com/en-us/library/ms695400%28v=vs.85%29.aspx) は、 `IMFAsyncCallback::Invoke` を呼び出すための API である。

`AsyncMFTEnum::Invoke` のコードを示す。

```c++ AsyncMFTEnum::Invoke
STDMETHODIMP AsyncMFTEnum::Invoke(IMFAsyncResult * pAsyncResult) {
    HRESULT hr;
    hr = DoEnum(); // …{1}
    ComPtr<IMFAsyncResult> caller_result;
    pAsyncResult->GetState(&caller_result); // …{2}
    caller_result->SetStatus(hr);
    MFInvokeCallback(caller_result.Get()); // …{3}
    return S_OK;
}
  1. MFT を列挙する処理を呼び出す。(詳細は省略。)
  2. 引数 IMFAsyncResult * pAsyncResult から state object を取得する。 これは AsyncMFTEnum::BeginEnumMFTMFInvokeCallback で渡したポインタとなっている。
  3. 2 で得たポインタを引数として MFInvokeCallback を呼び出す。MyCaller::Invoke が呼び出される。

AsyncMFTEnum::EndEnumMFT のコードを示す。

c++ AsyncMFTEnum::EndEnumMFT HRESULT AsyncMFTEnum::EndEnumMFT(IMFAsyncResult * pAsyncResult, IMFActivate *** pppActivate, UINT32 * pCount) { HRESULT hr = pAsyncResult->GetStatus(); if (FAILED(hr)) { return hr; } *pcount = _Count; *pppActivate = _ppActivate; return hr; } MyCaller::Invoke から呼び出される。自身のインスタンスに保持していたメンバ変数を結果として返している。

コードのダウンロード

GitHub からお願いします。

https://github.com/mahorigahama/IMFAsyncCallbackSample


Comments

comments powered by Disqus