ファイルとディレクトリの列挙
プロバイダーが最初に仮想化ルートを作成すると、ローカル システムでは空になります。 つまり、バッキング データ ストア内の項目がまだディスクにキャッシュされていません。 各項目が開かれるとキャッシュされますが、項目が開かれるまで、バッキング データ ストアにのみ存在します。
開かれていない項目にはローカルプレゼンスがないため、通常のディレクトリ列挙ではユーザーにその存在は表示されません。 そのため、プロバイダーは、バッキング データ ストア内の項目に関する情報を ProjFS に返すことによって、ディレクトリ列挙に参加する必要があります。 ProjFS は、プロバイダーからの情報をローカル ファイル システム上の情報とマージして、ディレクトリの内容の統合リストをユーザーに提示します。
ディレクトリ列挙コールバック
ディレクトリ列挙に参加するには、プロバイダーがPRJ_START_DIRECTORY_ENUMERATION_CB、PRJ_GET_DIRECTORY_ENUMERATION_CB、およびPRJ_END_DIRECTORY_ENUMERATION_CBコールバックを実装する必要があります。 プロバイダーの仮想化ルートの下のディレクトリが列挙されると、ProjFS は次のアクションを実行します。
ProjFS は、プロバイダーの PRJ_START_DIRECTORY_ENUMERATION_CB コールバックを呼び出して、列挙セッションを開始します。
このコールバックの処理の一環として、プロバイダーは列挙型の実行を準備する必要があります。 たとえば、バッキング データ ストア内の列挙の進行状況を追跡するために必要なメモリを割り当てる必要があります。
ProjFS は、コールバックの callbackData パラメーターの FilePathName メンバーで列挙されているディレクトリを識別します。 これは、仮想化ルートに対する相対パスとして指定されます。 たとえば、仮想化ルートが にあり
C:\virtRoot
、列挙されるディレクトリが の場合、C:\virtRoot\dir1\dir2
FilePathName メンバーには が含まれますdir1\dir2
。 プロバイダーは、バッキング データ ストアでそのパスを列挙する準備をします。 プロバイダーのバッキング ストアの機能によっては、 PRJ_START_DIRECTORY_ENUMERATION_CB コールバックでバッキング ストア列挙を実行するのが理にかなっている場合があります。同じディレクトリ内で複数の列挙が並列に発生する可能性があるため、各列挙コールバックには enumerationId 引数があり、プロバイダーがコールバック呼び出しを 1 つの列挙セッションに関連付けることができます。
プロバイダーがPRJ_START_DIRECTORY_ENUMERATION_CB コールバックからS_OKを返す場合は、ProjFS が enumerationId の同じ値を使用してPRJ_END_DIRECTORY_ENUMERATION_CB コールバックを呼び出すまで、そのプロバイダーが持つ列挙セッション情報を保持する必要があります。 プロバイダーがPRJ_START_DIRECTORY_ENUMERATION_CBからエラーを返した場合、ProjFS はその enumerationId のPRJ_END_DIRECTORY_ENUMERATION_CB コールバックを呼び出しません。
ProjFS は、プロバイダーの PRJ_GET_DIRECTORY_ENUMERATION_CB コールバックを 1 回以上呼び出して、プロバイダーからディレクトリの内容を取得します。
このコールバックに応答して、プロバイダーはバッキング データ ストアからアイテムの並べ替えられたリストを返します。
コールバックは、プロバイダーが列挙の結果の範囲を指定するために使用する searchExpression パラメーターに検索式を提供できます。 searchExpression が NULL の場合、プロバイダーは列挙するディレクトリ内のすべてのエントリを返します。 NULL 以外の場合、プロバイダーは PrjDoesNameContainWildCards を使用して、式にワイルドカードがあるかどうかを判断できます。 存在する場合、プロバイダーは PrjFileNameMatch を使用して、ディレクトリ内のエントリが検索式と一致するかどうかを判断します。
プロバイダーは、このコールバックの最初の呼び出しで searchExpression の値をキャプチャする必要があります。 コールバックの callbackData パラメーターの Flags フィールドにPRJ_CB_DATA_FLAG_ENUM_RESTART_SCANが設定されていない限り、同じ列挙セッションでのコールバックの後続の呼び出しでキャプチャされた値が使用されます。 その場合は、 searchExpression の値を再キャプチャする必要があります。
バッキング ストアに一致するエントリがない場合、プロバイダーはコールバックからS_OKを返すだけです。 それ以外の場合、プロバイダーはバッキング ストアから一致する各エントリを PRJ_FILE_BASIC_INFO 構造体に書式設定し、 PrjFillDirEntryBuffer を使用して、コールバックの dirEntryBufferHandle パラメーターによって提供されるバッファーを埋めます。 プロバイダーは、エントリをすべて追加するまで、または PrjFillDirEntryBuffer が HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) を返すまで、エントリを追加し続けます。 その後、プロバイダーはコールバックからS_OKを返します。 プロバイダーは、適切な並べ替え順序で dirEntryBufferHandle バッファーにエントリを追加する必要があることに注意してください。 プロバイダーは PrjFileNameCompare を使用して、正しい並べ替え順序を決定する必要があります。
プロバイダーは、PRJ_FILE_BASIC_INFO の IsDirectory メンバーの値 を指定する必要があります。 IsDirectory が FALSE の場合、プロバイダーは FileSize メンバーの値も指定する必要があります。
プロバイダーが CreationTime、LastAccessTime、LastWriteTime、ChangeTime の各メンバーの値を指定しない場合、ProjFS は現在のシステム時刻を使用します。
ProjFS は、FILE_ATTRIBUTE_DIRECTORYを除き、FileAttributes メンバーのプロバイダー セットにフラグを設定するFILE_ATTRIBUTEを使用します。IsDirectory メンバーの指定された値に従って、FileAttributes メンバーのFILE_ATTRIBUTE_DIRECTORYの正しい値が設定されます。
PrjFillDirEntryBuffer が HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) を返した場合、プロバイダーは追加するエントリを使い切る前に、エラーが発生したときに追加しようとしたエントリを追跡する必要があります。 ProjFS が同じ列挙セッションに対して PRJ_GET_DIRECTORY_ENUMERATION_CB コールバックを次回呼び出す場合、プロバイダーは追加しようとしているエントリを含む dirEntryBufferHandle バッファーへのエントリの追加を再開する必要があります。
バッキング ストアでシンボリック リンクがサポートされている場合、プロバイダーは PrjFillDirEntryBuffer2 を使用して、コールバックの dirEntryBufferHandle パラメーターによって提供されるバッファーを埋める必要があります。 PrjFillDirEntryBuffer2 では、列挙型エントリがシンボリック リンクであり、そのターゲットが何であるかをプロバイダーが指定できるようにする追加のバッファー入力がサポートされています。 それ以外の場合は、 PrjFillDirEntryBuffer に対して上記のように動作します。 次の例では 、PrjFillDirEntryBuffer2 を使用して、シンボリック リンクのサポートを示しています。
PrjFillDirEntryBuffer2 は、Windows 10 バージョン 2004 の時点でサポートされていることに注意してください。 プロバイダーは、たとえば GetProcAddress を使用して、PrjFillDirEntryBuffer2 の存在をプローブする必要があります。
typedef struct MY_ENUM_ENTRY MY_ENUM_ENTRY; typedef struct MY_ENUM_ENTRY { MY_ENUM_ENTRY* Next; PWSTR Name; BOOLEAN IsDirectory; INT64 FileSize; BOOLEAN IsSymlink; PWSTR SymlinkTarget; } MY_ENUM_ENTRY; typedef struct MY_ENUM_SESSION { GUID EnumerationId; PWSTR SearchExpression; USHORT SearchExpressionMaxLen; BOOLEAN SearchExpressionCaptured; MY_ENUM_ENTRY* EnumHead; MY_ENUM_ENTRY* LastEnumEntry; BOOLEAN EnumCompleted; } MY_ENUM_SESSION; HRESULT MyGetEnumCallback( _In_ const PRJ_CALLBACK_DATA* callbackData, _In_ const GUID* enumerationId, _In_opt_z_ PCWSTR searchExpression, _In_ PRJ_DIR_ENTRY_BUFFER_HANDLE dirEntryBufferHandle ) { // MyGetEnumSession is a routine the provider might implement to find // information about the enumeration session that it first stored // when processing its PRJ_START_DIRECTORY_ENUMERATION_CB callback. // // In this example the PRJ_START_DIRECTORY_ENUMERATION_CB callback has // already retrieved a list of the items in the backing store located at // callbackData->FilePathName, sorted them using PrjFileNameCompare() // to determine collation order, and stored the list in session->EnumHead. MY_ENUM_SESSION* session = NULL; session = MyGetEnumSession(enumerationId); if (session == NULL) { return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); } if (!session->SearchExpressionCaptured || (callbackData->Flags & PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN)) { if (searchExpression != NULL) { if (wcsncpy_s(session->SearchExpression, session->SearchExpressionMaxLen, searchExpression, wcslen(searchExpression))) { // Failed to copy the search expression; perhaps the provider // could try reallocating session->SearchExpression. } } else { if (wcsncpy_s(session->SearchExpression, session->SearchExpressionMaxLen, "*", 1)) { // Failed to copy the search expression; perhaps the provider // could try reallocating session->SearchExpression. } } session->SearchExpressionCaptured = TRUE; } MY_ENUM_ENTRY* enumHead = NULL; // We have to start the enumeration from the beginning if we aren't // continuing an existing session or if the caller is requesting a restart. if (((session->LastEnumEntry == NULL) && !session->EnumCompleted) || (callbackData->Flags & PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN)) { // Ensure that if the caller is requesting a restart we reset our // bookmark of how far we got in the enumeration. session->LastEnumEntry = NULL; // In case we're restarting ensure we don't think we're done. session->EnumCompleted = FALSE; // We need to start considering items from the beginning of the list // retrieved from the backing store. enumHead = session->EnumHead; } else { // We are resuming an existing enumeration session. Note that // session->LastEnumEntry may be NULL. That is okay; it means // we got all the entries the last time this callback was invoked. // Returning S_OK without adding any new entries to the // dirEntryBufferHandle buffer signals ProjFS that the enumeration // has returned everything it can. enumHead = session->LastEnumEntry; } if (enumHead == NULL) { // There are no items to return. Remember that we've returned everything // we can. session->EnumCompleted = TRUE; } else { MY_ENUM_ENTRY* thisEntry = enumHead; int entriesAdded = 0; while (thisEntry != NULL) { // We'll insert the entry into the return buffer if it matches // the search expression captured for this enumeration session. if (PrjFileNameMatch(thisEntry->Name, session->SearchExpression)) { PRJ_FILE_BASIC_INFO fileBasicInfo = {}; fileBasicInfo.IsDirectory = thisEntry->IsDirectory; fileBasicInfo.FileSize = thisEntry->FileSize; // If our backing store says this is really a symbolic link, // set up to tell ProjFS that it is a symlink and what its // target is. PRJ_EXTENDED_INFO extraInfo = {}; if (thisEntry->IsSymlink) { extraInfo.InfoType = PRJ_EXT_INFO_SYMLINK; extraInfo.Symlink.TargetName = thisEntry->SymlinkTarget; } // Format the entry for return to ProjFS. HRESULT fillResult = PrjFillDirEntryBuffer2(dirEntryBufferHandle, thisEntry->Name, &fileBasicInfo, thisEntry->IsSymlink ? &extraInfo : NULL); if (fillResult == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) { if (entriesAdded > 0) { // We couldn't add this entry to the buffer; remember where we left // off for the next time we're called for this enumeration session. session->LastEnumEntry = thisEntry; return S_OK; } else { // The buffer should always have enough space for at least one entry, but // we could not add even one entry. Return the error. return fillResult; } } else { // Any other error is unexpected. NOTE: Be sure to check for E_INVALIDARG to // ensure you are calling PrjFillDirEntryBuffer2 correctly. return fillResult; } ++entriesAdded; } thisEntry = thisEntry->Next; } // We reached the end of the list of entries; remember that we've returned // everything we can. session->EnumCompleted = TRUE; } return S_OK; }
ProjFS は、プロバイダーの PRJ_END_DIRECTORY_ENUMERATION_CB コールバックを呼び出して、列挙セッションを終了します。
プロバイダーは、PRJ_START_DIRECTORY_ENUMERATION_CBの処理の一部として割り当てられたメモリの割り当てを解除するなど、列挙セッションを終了するために必要なクリーンアップを実行 する必要があります。