IBackgroundCopyCallback
COM公式ドキュメント
ジョブの完了、変更、またはエラー発生の通知を受け取るには、IBackgroundCopyCallback インターフェイスを実装します。クライアントはジョブの状態をポーリングする代わりに、このインターフェイスを使用します。
解説(Remarks)
通知を受け取るには、 IBackgroundCopyJob::SetNotifyInterface メソッドを呼び出して、 IBackgroundCopyCallback 実装へのインターフェイスポインターを指定します。受け取る通知の種類を指定するには、 IBackgroundCopyJob::SetNotifyFlags メソッドを呼び出します。
BITS は、インターフェイスポインターが有効である限りコールバックを呼び出します。アプリケーションが終了すると通知インターフェイスは無効になります。BITS は通知インターフェイスを永続化しません。そのため、アプリケーションの初期化処理で、通知を受け取りたい既存のジョブに対して SetNotifyInterface メソッドを呼び出す必要があります。
BITS は、イベントの発生後に登録した場合でも、コールバックを少なくとも 1 回は呼び出すことを保証します。たとえば、ジョブの転送が完了した後に転送の通知を要求した場合でも、ジョブ転送のコールバックを受け取ります。また、あるジョブが通知を受け取った後にポインターが無効になった場合、そのジョブに再度インターフェイスポインターを設定すると、そのジョブは再び通知を受け取ります。
IBackgroundCopyCallback インターフェイスのすべてのメソッドを実装する必要があります。たとえば、ジョブ変更のコールバックを登録していない場合でも、JobModification メソッドは S_OK を返す必要があります。
JobModification コールバックは低優先度のスレッドで起動されるのに対し、JobTransferred および JobError コールバックはより高い優先度のスレッドで起動されます。そのため、JobModification コールバックが保留中であっても、その後に起動された JobTransferred コールバックがクライアントに先に届く場合があります。
BITS はユーザーごとに最大 4 つの同時通知をサポートします。1 つ以上のアプリケーションが、あるユーザーの 4 つの通知すべてを戻らない状態でブロックすると、同じユーザーとして実行されている他のアプリケーションは、ブロックしている通知のいずれかが戻るまで通知を受け取れません。コールバックが他の通知をブロックする可能性を減らすため、実装は短くしてください。
管理者がジョブの所有権を取得した場合、通知コールバックは通知を要求したユーザーのコンテキストで行われます。
アプリケーションが シングルスレッドアパートメント モデルを使用している場合、コールバックメソッド内から COM オブジェクトを呼び出すと、コールバックメソッドが再入する可能性があります。たとえば、JobModification コールバック内から IBackgroundCopyJob::GetProgress を呼び出すと、現在の通知を処理している最中に BITS がジョブ変更コールバックへ別の通知を送信することがあります。すべての JobModification コールバックに応答することがアプリケーションにとって重要でない場合は、次の例のように再入したコールバックを無視できます。
//A member variable is used to determine if the callback
//is already processing another job modification callback.
LONG m_PendingJobModificationCount = 0;
//If you are already processing a callback, ignore this notification.
if (InterlockedCompareExchange(&m_PendingJobModificationCount, 1, 0) == 1)
{
return S_OK;
}
... //processing the current notification
m_PendingJobModificationCount = 0;
return hr;
例
次の例は、 IBackgroundCopyCallback の実装を示しています。この実装を呼び出す例については、 IBackgroundCopyJob::SetNotifyInterface メソッドを参照してください。
#define TWO_GB 2147483648 // 2GB
class CNotifyInterface : public IBackgroundCopyCallback
{
LONG m_lRefCount;
public:
//Constructor, Destructor
CNotifyInterface() {m_lRefCount = 1;};
~CNotifyInterface() {};
//IUnknown
HRESULT __stdcall QueryInterface(REFIID riid, LPVOID *ppvObj);
ULONG __stdcall AddRef();
ULONG __stdcall Release();
//IBackgroundCopyCallback methods
HRESULT __stdcall JobTransferred(IBackgroundCopyJob* pJob);
HRESULT __stdcall JobError(IBackgroundCopyJob* pJob, IBackgroundCopyError* pError);
HRESULT __stdcall JobModification(IBackgroundCopyJob* pJob, DWORD dwReserved);
};
HRESULT CNotifyInterface::QueryInterface(REFIID riid, LPVOID* ppvObj)
{
if (riid == __uuidof(IUnknown) || riid == __uuidof(IBackgroundCopyCallback))
{
*ppvObj = this;
}
else
{
*ppvObj = NULL;
return E_NOINTERFACE;
}
AddRef();
return NOERROR;
}
ULONG CNotifyInterface::AddRef()
{
return InterlockedIncrement(&m_lRefCount);
}
ULONG CNotifyInterface::Release()
{
ULONG ulCount = InterlockedDecrement(&m_lRefCount);
if(0 == ulCount)
{
delete this;
}
return ulCount;
}
HRESULT CNotifyInterface::JobTransferred(IBackgroundCopyJob* pJob)
{
HRESULT hr;
//Add logic that will not block the callback thread. If you need to perform
//extensive logic at this time, consider creating a separate thread to perform
//the work.
hr = pJob->Complete();
if (FAILED(hr))
{
//Handle error. BITS probably was unable to rename one or more of the
//temporary files. See the Remarks section of the IBackgroundCopyJob::Complete
//method for more details.
}
//If you do not return S_OK, BITS continues to call this callback.
return S_OK;
}
HRESULT CNotifyInterface::JobError(IBackgroundCopyJob* pJob, IBackgroundCopyError* pError)
{
HRESULT hr;
BG_FILE_PROGRESS Progress;
BG_ERROR_CONTEXT Context;
HRESULT ErrorCode = S_OK;
WCHAR* pszJobName = NULL;
WCHAR* pszErrorDescription = NULL;
BOOL IsError = TRUE;
//Use pJob and pError to retrieve information of interest. For example,
//if the job is an upload reply, call the IBackgroundCopyError::GetError method
//to determine the context in which the job failed. If the context is
//BG_JOB_CONTEXT_REMOTE_APPLICATION, the server application that received the
//upload file failed.
hr = pError->GetError(&Context, &ErrorCode);
//If the proxy or server does not support the Content-Range header or if
//antivirus software removes the range requests, BITS returns BG_E_INSUFFICIENT_RANGE_SUPPORT.
//This implementation tries to switch the job to foreground priority, so
//the content has a better chance of being successfully downloaded.
if (BG_E_INSUFFICIENT_RANGE_SUPPORT == ErrorCode)
{
hr = pError->GetFile(&pFile);
hr = pFile->GetProgress(&Progress);
if (BG_SIZE_UNKNOWN == Progress.BytesTotal)
{
//The content is dynamic, do not change priority. Handle as an error.
}
else if (Progress.BytesTotal > TWO_GB)
{
// BITS requires range requests support if the content is larger than 2 GB.
// For these scenarios, BITS uses 2 GB ranges to download the file,
// so switching to foreground priority will not help.
}
else
{
hr = pJob->SetPriority(BG_JOB_PRIORITY_FOREGROUND);
hr = pJob->Resume();
IsError = FALSE;
}
pFile->Release();
}
if (TRUE == IsError)
{
hr = pJob->GetDisplayName(&pszJobName);
hr = pError->GetErrorDescription(LANGIDFROMLCID(GetThreadLocale()), &pszErrorDescription);
if (pszJobName && pszErrorDescription)
{
//Do something with the job name and description.
}
CoTaskMemFree(pszJobName);
CoTaskMemFree(pszErrorDescription);
}
//If you do not return S_OK, BITS continues to call this callback.
return S_OK;
}
HRESULT CNotifyInterface::JobModification(IBackgroundCopyJob* pJob, DWORD dwReserved)
{
HRESULT hr;
WCHAR* pszJobName = NULL;
BG_JOB_PROGRESS Progress;
BG_JOB_STATE State;
hr = pJob->GetDisplayName(&pszJobName);
if (SUCCEEDED(hr))
{
hr = pJob->GetProgress(&Progress);
if (SUCCEEDED(hr))
{
hr = pJob->GetState(&State);
if (SUCCEEDED(hr))
{
//Do something with the progress and state information.
//BITS generates a high volume of modification
//callbacks. Use this callback with discretion. Consider creating a timer and
//polling for state and progress information.
}
}
CoTaskMemFree(pszJobName);
}
return S_OK;
}
メソッド 3
vtbl = vtable インデックス(0始まり)。HSP等からCOMメソッドをインデックス指定で呼ぶ際に使用します。0〜2 は IUnknown。
ジョブ内のすべてのファイルが正常に転送されたとき、BITS は JobTransferred メソッドの実装を呼び出します。
| pJob | IBackgroundCopyJob* | in | ジョブが完了した時刻、転送されたバイト数、転送されたファイル数など、ジョブ関連の情報を保持します。pJob を解放しないでください。メソッドが戻るときに BITS がインターフェイスを解放します。 |
戻り値
このメソッドは S_OK を返す必要があります。S_OK 以外を返すと、BITS は S_OK が返されるまでこのメソッドを呼び出し続けます。パフォーマンス上の理由から、S_OK 以外の値を返す回数は数回程度に制限してください。エラーコードを返す代わりに、常に S_OK を返し、エラーは内部で処理することも検討してください。このメソッドが呼び出される間隔は不定です。
このメソッドが失敗し、かつ IBackgroundCopyJob2::SetNotifyCmdLine メソッドを呼び出していた場合は、コマンドラインが実行され、このメソッドは再度呼び出されない点に注意してください。
解説(Remarks)
通常、実装では IBackgroundCopyJob::Complete メソッドを呼び出して、BITS がファイルを正常に転送したことを確認します。ダウンロードしたファイルおよび応答ファイルは、 Complete メソッドを呼び出すまでクライアントで利用できません。
Complete メソッドまたは IBackgroundCopyJob::Cancel メソッドを 90 日以内(既定の JobInactivityTimeout グループポリシー)に呼び出さない場合、BITS はジョブをキャンセルし、ダウンロードしたファイルと応答ファイルを削除します。ジョブのキャンセルは、正常にアップロードされたファイルには影響しません。
コールバック内で応答データを取得したい場合は、pJob に対して IBackgroundCopyJob2 インターフェイスをクエリし、その GetReplyData メソッドを呼び出します。応答データを含むファイルの名前を取得するには、 GetReplyFileName メソッドを呼び出します。
BITS は、第三者による改ざんに対して転送されたファイルの整合性を保証しません。クライアントは、Complete メソッドを呼び出す前に、転送されたファイルを検証する整合性チェックを実装できます。ファイルが転送されたときに通知を受け取るには、IBackgroundCopyCallback2::FileTransferred メソッドを実装します。コールバック内で IBackgroundCopyFile3::GetTemporaryName メソッドを呼び出して、ダウンロードされた内容を含む一時ファイルの名前を取得します。内容を検証した後、IBackgroundCopyFile3::SetValidationState メソッドを呼び出して、内容が有効かどうかを示します。内容が無効で、BITS がオリジンサーバーからファイルをダウンロードしていた場合、ジョブはエラー状態になります。ピアからダウンロードしていた場合、BITS はオリジンサーバーからファイルをダウンロードします。
例
IBackgroundCopyCallback インターフェイスのサンプルコードを参照してください。
ジョブの状態が BG_JOB_STATE_ERROR に変化したとき、BITS は JobError メソッドの実装を呼び出します。
| pJob | IBackgroundCopyJob* | in | エラー発生前に転送されたバイト数やファイル数など、ジョブ関連の情報を保持します。また、ジョブを再開およびキャンセルするメソッドも含みます。pJob を解放しないでください。JobError メソッドが戻るときに BITS がインターフェイスを解放します。 |
| pError | IBackgroundCopyError* | in | 致命的なエラーが発生した時点で処理されていたファイルやエラーの説明など、エラー情報を保持します。pError を解放しないでください。JobError メソッドが戻るときに BITS がインターフェイスを解放します。 |
戻り値
このメソッドは S_OK を返す必要があります。S_OK 以外を返すと、BITS は S_OK が返されるまでこのメソッドを呼び出し続けます。パフォーマンス上の理由から、S_OK 以外の値を返す回数は数回程度に制限してください。エラーコードを返す代わりに、常に S_OK を返し、エラーは内部で処理することも検討してください。このメソッドが呼び出される間隔は不定です。
このメソッドが失敗し、かつ IBackgroundCopyJob2::SetNotifyCmdLine メソッドを呼び出していた場合は、コマンドラインが実行され、このメソッドは再度呼び出されない点に注意してください。
解説(Remarks)
エラーの原因を特定した後、次のいずれかの対応を行います。
- ジョブをキャンセルするには、 IBackgroundCopyJob::Cancel メソッドを呼び出します。ファイルが正常にアップロードされた後にエラーが発生した場合、キャンセル要求はアップロードジョブには影響しません。ただし、ジョブの種類が BG_JOB_TYPE_UPLOAD_REPLY でアップロードが成功していた場合、 Cancel メソッドを呼び出すと応答データの要求がキャンセルされます。
- エラー発生前に正常に転送された部分を受け入れるには、 IBackgroundCopyJob::Complete メソッドを呼び出します。この方法はアップロードジョブには適用できません。アップロードジョブの一部を完了させることはできません。
- ジョブの処理を完了させるには、問題を修正してから IBackgroundCopyJob::Resume メソッドを呼び出します。
一時的なエラーでは JobError メソッドは呼び出されません。
アップロード応答ジョブにおいて、アップロード、応答、サーバーアプリケーションのいずれの部分が失敗したかを判断するには、 IBackgroundCopyError::GetError メソッドを呼び出して、エラーが発生した コンテキスト を取得します。コンテキストが BG_ERROR_CONTEXT_REMOTE_APPLICATION の場合は、サーバーアプリケーションが失敗しています。アップロードと応答のコンテキストは BG_ERROR_CONTEXT_REMOTE_FILE です。 BG_JOB_REPLY_PROGRESS 構造体の BytesTotal メンバーが BG_SIZE_UNKNOWN でない場合は、応答が失敗しています。それ以外の場合はアップロードが失敗しています。
例
IBackgroundCopyCallback インターフェイスのサンプルコードを参照してください。
ジョブが変更されたとき、BITS は JobModification メソッドの実装を呼び出します。
| pJob | IBackgroundCopyJob* | in | ジョブのプロパティ、進行状況、状態の情報にアクセスするためのメソッドを保持します。pJob を解放しないでください。JobModification メソッドが戻るときに BITS がインターフェイスを解放します。 |
| dwReserved | DWORD | in | 将来の使用のために予約されています。 |
戻り値
このメソッドは S_OK を返す必要があります。
解説(Remarks)
リソース負荷が最大の状況では、実装がすべての変更イベントを受け取れない場合があります。
BITS は大量の変更イベントを生成します。タイマーを作成して状態や進行状況の情報をポーリングするか、このコールバックの使用を制限することを検討してください。このコールバックを使用する場合は、実装を短くしてください。
ジョブの状態が BG_JOB_STATE_ERROR または BG_JOB_STATE_TRANSFERRED に変化したとき、BITS は変更イベントを生成しません。
例
IBackgroundCopyCallback インターフェイスのサンプルコードを参照してください。
Microsoft 公式リファレンス: 英語 (en-us) · 日本語 (ja-jp) · 原文ソース (GitHub)
HSP用 COM定義
#usecom / #comfunc によるHSPのCOM呼び出し定義。数字は vtbl インデックス(0始まり)。クラスIDが無い場合 #usecom の末尾は "{}"、ある場合は "{CLSID}"。
#define global IID_IBackgroundCopyCallback "{97EA99C7-0186-4AD4-8DF9-C5B4E0ED6B22}"
#usecom global IBackgroundCopyCallback IID_IBackgroundCopyCallback "{}"
#comfunc global IBackgroundCopyCallback_JobTransferred 3 sptr
#comfunc global IBackgroundCopyCallback_JobError 4 sptr,sptr
#comfunc global IBackgroundCopyCallback_JobModification 5 sptr,int
; ※数字は vtbl インデックス(0始まり)。0/1/2 は IUnknown(QueryInterface/AddRef/Release)。
; ※このインターフェースは直接 CoCreateInstance するクラスIDが無いため "{}"(他メソッド/アクティベーションで取得)。
; ※ハンドル/void*等の不透明ポインタは IronHSP では intptr 指定が可能。