共用方式為


檔系統作業通知

除了處理列舉、傳回檔案元數據和檔案內容所需的回呼之外,提供者也可以選擇性地在其對 PrjStartVirtualizing 的呼叫中註冊PRJ_NOTIFICATION_CB回呼 此回呼會接收在實例虛擬化根目錄下對檔案和目錄執行的文件系統作業通知。 有些通知是「作業後」通知,告知提供者作業已完成。 其他通知是「作業前」通知,這表示提供者會在作業發生之前收到通知。 在作業前通知中,提供者可以從回呼傳回錯誤碼來否決作業。 這會導致作業失敗,並傳回的錯誤碼。

如何指定要接收的通知

如果提供者在啟動虛擬化實例時提供PRJ_NOTIFICATION_CB作,則也應該指定想要接收的通知。 提供者可以註冊的通知定義於PRJ_NOTIFICATION列舉中

當提供者指定它想要接收的通知時,它會使用一或多個 PRJ_NOTIFICATION_MAPPING 結構的陣列。 這些定義「通知對應」。 通知對應是目錄之間的配對,稱為「通知根目錄」,以及一組以位掩碼表示的通知,ProjFS 應該針對該目錄及其子系傳送。 您也可以針對單一檔案建立通知對應。 在通知對應中指定的檔案或目錄,在提供者呼叫 PrjStartVirtualizing 時,不必已經存在,提供者可以指定任何路徑,而且如果曾經建立對應,對應就會套用至它。

如果提供者指定多個通知對應,有些是其他通知對應,則必須以遞減深度指定對應。 更深層層級的通知對應會覆寫其子系的較高層級對應。

如果提供者未指定一組通知對應,ProjFS 預設會將它傳送至虛擬化根目錄底下的所有檔案和目錄PRJ_NOTIFY_FILE_OPENED、PRJ_NOTIFY_NEW_FILE_CREATED和PRJ_NOTIFY_FILE_OVERWRITTEN。

下列代碼段說明如何在啟動虛擬化實例時註冊一組通知。

PRJ_CALLBACKS callbackTable;

// Supply required callbacks.
callbackTable.StartDirectoryEnumerationCallback = MyStartEnumCallback;
callbackTable.EndDirectoryEnumerationCallback = MyEndEnumCallback;
callbackTable.GetDirectoryEnumerationCallback = MyGetEnumCallback;
callbackTable.GetPlaceholderInfoCallback = MyGetPlaceholderInfoCallback;
callbackTable.GetFileDataCallback = MyGetFileDataCallback;

// The rest of the callbacks are optional.  We want notifications, so specify
// our notification callback.
callbackTable.QueryFileNameCallback = nullptr;
callbackTable.NotificationCallback = MyNotificationCallback;
callbackTable.CancelCommandCallback = nullptr;

// Assume the provider has created a new virtualization root at C:\VirtRoot and
// initialized it with this layout:
//
//     C:\VirtRoot
//     +--- baz
//     \--- foo
//          +--- subdir1
//          \--- subdir2
//
// The provider wants these notifications:
// * Notification of new file/directory creates for all locations within the
//   virtualization root that are not subject to one of the following more
//   specific notification mappings.
// * Notification of new file/directory creates, file opens, post-renames, and
//   pre- and post-deletes for C:\VirtRoot\foo
// * No notifications for C:\VirtRoot\foo\subdir1
PRJ_STARTVIRTUALIZING_OPTIONS startOpts = {};
PRJ_NOTIFICATION_MAPPING notificationMappings[3];

// Configure default notifications - notify of new files for most of the tree.
notificationMappings[0].NotificationRoot = L"";
notificationMappings[0].NotificationBitMask = PRJ_NOTIFY_NEW_FILE_CREATED;

// Override default notifications - notify of new files, opened files, and file
// deletes for C:\VirtRoot\foo and its descendants.
notificationMappings[1].NotificationRoot = L"foo";
notificationMappings[1].NotificationBitMask = PRJ_NOTIFY_NEW_FILE_CREATED |
                                              PRJ_NOTIFY_FILE_OPENED |
                                              PRJ_NOTIFY_PRE_DELETE |
                                              PRJ_NOTIFY_FILE_HANDLE_CLOSED_FILE_DELETED |
                                              PRJ_NOTIFY_FILE_RENAMED;

// Override all notifications for C:\VirtRoot\foo\subdir1 and its descendants.
notificationMappings[2].NotificationRoot = L"foo\\subdir1";
notificationMappings[2].NotificationBitMask = PRJ_NOTIFY_SUPPRESS_NOTIFICATIONS;

startOpts.NotificationMappings = notificationMappings;
startOpts.NotificationMappingsCount = ARRAYSIZE(notificationMapping);

// Start the instance and provide our notification mappings.
HRESULT hr;
PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT instanceHandle;
hr = PrjStartVirtualizing(rootName,
                          &callbackTable,
                          nullptr,
                          &startOpts,
                          &instanceHandle);
if (FAILED(hr))
{
    wprintf(L"Failed to start the virtualization instance (0x%08x)\n", hr);
    return;
}

接收告知

ProjFS 會呼叫提供者的PRJ_NOTIFICATION_CB回呼,以傳送文件系統作業的通知。 它會針對提供者已註冊的每個作業執行此動作。 如果,如上述範例所示,為 PRJ_NOTIFY_NEW_FILE_CREATED 註冊的提供者 |PRJ_NOTIFY_FILE_OPENED |PRJ_NOTIFY_PRE_DELETE |PRJ_NOTIFY_FILE_HANDLE_CLOSED_FILE_DELETED |PRJ_NOTIFY_FILE_RENAMED,而且應用程式在通知根目錄中建立了新的檔案,ProjFS 會使用 新檔案的路徑名稱呼叫PRJ_NOTIFICATION_CB 回呼,並將 通知 參數設定為 PRJ_NOTIFICATION_NEW_FILE_CREATED。

ProjFS 會在建立關聯的文件系統作業之前,先在下列清單中傳送通知。 如果提供者從 PRJ_NOTIFICATION_CB 回呼傳回失敗碼,檔案系統作業將會失敗,並傳回提供者指定的失敗碼。

  • PRJ_NOTIFICATION_PRE_DELETE - 檔案即將刪除。
  • PRJ_NOTIFICATION_PRE_RENAME - 檔案即將重新命名。
  • PRJ_NOTIFICATION_PRE_SET_HARDLINK - 即將為檔案建立硬連結。
  • PRJ_NOTIFICATION_FILE_PRE_CONVERT_TO_FULL - 檔案即將從佔位元展開至完整檔案,這表示其內容可能會遭到修改。

ProjFS 會在相關聯的文件系統作業完成之後,傳送下列清單中的通知。

  • PRJ_NOTIFICATION_FILE_OPENED - 已開啟現有的檔案。
    • 即使此通知會在檔案開啟之後傳送,提供者仍可傳回失敗碼。 回應中,ProjFS 會取消開啟,並將失敗傳回給呼叫端。
  • PRJ_NOTIFICATION_NEW_FILE_CREATED - 已建立新的檔案。
  • PRJ_NOTIFICATION_FILE_OVERWRITTEN - 已覆寫現有的檔案,例如使用 CREATE_ALWAYS dwCreationDisposition 旗標呼叫 CreateFileW
  • PRJ_NOTIFICATION_FILE_RENAMED - 檔案已重新命名。
  • PRJ_NOTIFICATION_HARDLINK_CREATED - 已為檔案建立硬式連結
  • PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_NO_MODIFICATION - 已關閉檔句柄,而且檔案的內容未修改,也不會刪除檔案。
  • PRJ_NOTIFICATION_FILE_HANLDE_CLOSED_FILE_MODIFIED - 檔案句柄已關閉,且檔案的內容已修改。
  • PRJ_NOTIFICATION_FILE_HANDLE_CLOSED_FILE_DELETED - 已關閉檔句柄,並在關閉句柄時刪除檔案。

如需每個通知的詳細資訊,請參閱PRJ_NOTIFICATION的檔

PRJ_NOTIFICATION_CB回呼的 notificationParameters 參數會指定特定通知的額外參數。 如果提供者收到PRJ_NOTIFICATION_FILE_OPENED、PRJ_NOTIFICATION_NEW_FILE_CREATED或PRJ_NOTIFICATION_FILE_OVERWRITTEN或PRJ_NOTIFICATION_FILE_RENAMED通知,提供者可以指定要為檔案接收的新通知集。 如需進一步的詳細數據,請參閱PRJ_NOTIFICATION_CB的檔

下列範例顯示提供者如何處理在上述代碼段中註冊的通知的簡單範例。

#include <windows.h>

HRESULT
MyNotificationCallback(
    _In_ const PRJ_CALLBACK_DATA* callbackData,
    _In_ BOOLEAN isDirectory,
    _In_ PRJ_NOTIFICATION notification,
    _In_opt_z_ PCWSTR destinationFileName,
    _Inout_ PRJ_NOTIFICATION_PARAMETERS* operationParameters
    )
{
    HRESULT hr = S_OK;

    switch (notification)
    {
    case PRJ_NOTIFY_NEW_FILE_CREATED:

        wprintf(L"A new %ls was created at [%ls].\n",
                isDirectory ? L"directory" : L"file",
                callbackData->FilePathName);

        // Let's stop getting notifications inside newly-created directories.
        if (isDirectory)
        {
            operationParameters->PostCreate.NotificationMask = PRJ_NOTIFY_SUPPRESS_NOTIFICATIONS;
        }
        break;

    case PRJ_NOTIFY_FILE_OPENED:

        wprintf(L"Handle opened to %ls [%ls].\n",
                isDirectory ? L"directory": L"file",
                callbackData->FilePathName);
        break;

    case PRJ_NOTIFY_PRE_DELETE:

        wprintf(L"Attempt to delete %ls [%ls]: ",
                isDirectory ? L"directory": L"file",
                callbackData->FilePathName);

        // MyAllowDelete is a routine the provider might implement to decide
        // whether to allow a given file/directory to be deleted.
        if (!MyAllowDelete(callbackData->FilePathName, isDirectory))
        {
            wprintf(L"DENIED\n");
            hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
        }
        else
        {
            wprintf(L"ALLOWED\n");
        }
        break;

    case PRJ_NOTIFY_FILE_HANDLE_CLOSED_FILE_DELETED:

        wprintf(L"The %ls [%ls] was deleted.\n",
                isDirectory ? L"directory": L"file",
                callbackData->FilePathName);
        break;

    case PRJ_NOTIFY_FILE_RENAMED:

        if (wcslen(callbackData->FilePathName) == 0)
        {
            // If callbackData->FilePathName is an empty string, then the item
            // that was renamed was originally in a location not under the
            // virtualization root.
            wprintf(L"A %ls was renamed into the virtualization root,\n",
                    isDirectory ? L"directory": L"file");
            wprintf(L"Its new location is [%ls].\n",
                    destinationFileName);
        }
        else if (wcslen(destinationFileName) == 0)
        {
            // If destinationFileName is an empty string, then the item that was
            // renamed is now in a location not under the virtualization root.
            wprintf(L"A %ls was renamed out of the virtualization root.\n",
                    isDirectory ? L"directory": L"file");
            wprintf(L"Its original location was [%ls].\n",
                    callbackData->FilePathName);
        }
        else
        {
            // If both names are specified, both the new and old names are under
            // the virtualization root (it is never the case that nether name is
            // specified).
            wprintf(L"A %ls [%ls] was renamed.\n",
                    isDirectory ? L"directory": L"file",
                    callbackData->FilePathName);
            wprintf(L"Its new name is [%ls].\n", destinationFileName);
        }
        break;

    default:
        wprintf(L"Unrecognized notification: 0x%08x");
    }

    // Note that this may be a failure code from MyAllowDelete().
    return hr;
}