應用層vc實現三種檔案監視方法 下面介紹三種非驅動實現檔案監視的方法。 =============================================================
阿新 • • 發佈:2019-01-04
下面介紹三種非驅動實現檔案監視的方法。
=================================================================
通過 未公開API SHChangeNotifyRegister 實現
=================================================================
一、原理
Windows 內部有兩個未公開的函式(注:在最新的MSDN中,已經公開了這兩個函式),分別叫做SHChangeNotifyRegister和 SHChangeNotifyDeregister,可以實現以上的功能。這兩個函式位於Shell32.dll中,是用序號方式匯出的。這就是為什麼我們用VC自帶的Depends工具察看Shell32.dll時,找不到這兩個函式的原因。SHChangeNotifyRegister的匯出序號是 2;而SHChangeNotifyDeregister的匯出序號是4。
SHChangeNotifyRegister可以把指定的視窗新增到系統的訊息監視鏈中,這樣視窗就能接收到來自檔案系統或者Shell的通知了。而對應的另一個函式,SHChangeNotifyDeregister,則用來取消監視鉤掛。SHChangeNotifyRegister的原型和相關引數如下:
ULONG SHChangeNotifyRegister
(
HWND hwnd,
int fSources,
LONG fEvents,
UINT wMsg,
Int cEntries,
SHChangeNotifyEntry *pfsne
);
其中:
hwnd
將要接收改變或通知訊息的視窗的控制代碼。
fSource
指示接收訊息的事件型別,將是下列值的一個或多個(注:這些標誌沒有被包括在任何標頭檔案中,使用者須在自己的程式中加以定義或者直接使用其對應的數值)
SHCNRF_InterruptLevel
0x0001。接收來自檔案系統的中斷級別通知訊息。
SHCNRF_ShellLevel
0x0002。接收來自Shell的Shell級別通知訊息。
SHCNRF_RecursiveInterrupt
0x1000。接收目錄下所有子目錄的中斷事件。此標誌必須和SHCNRF_InterruptLevel 標誌合在一起使用。當使用該標誌時,必須同時設定對應的SHChangeNotifyEntry結構體中的fRecursive成員為TRUE(此結構體由函式的最後一個引數pfsne指向),這樣通知訊息在目錄樹上是遞迴的。
SHCNRF_NewDelivery
0x8000。接收到的訊息使用共享記憶體。必須先呼叫SHChangeNotification_Lock,然後才能存取實際的資料,完成後呼叫SHChangeNotification_Unlock函式釋放記憶體。
fEvents
要捕捉的事件,其所有可能的值請參見MSDN中關於SHChangeNotify函式的註解。
wMsg
產生對應的事件後,發往視窗的訊息。
cEntries
pfsne指向的陣列的成員的個數。
pfsne
SHChangeNotifyEntry 結構體陣列的起始指標。此結構體承載通知訊息,其成員個數必須設定成1,否則SHChangeNotifyRegister或者 SHChangeNotifyDeregister將不能正常工作(但是據我試驗,如果cEntries設為大於1的值,依然可以註冊成功,不知何故)。
如果函式呼叫成功,則返回一個整型註冊標誌號,否則將返回0。同時系統就會將hwnd指定的視窗加入到操作監視鏈中,當有檔案操作發生時,系統會向hwnd標識的視窗傳送wMsg指定的訊息,我們只要在程式中加入對該訊息的處理函式就可以實現對系統操作的監視了。
如果要退出程式監視,就要呼叫另外一個未公開得函式SHChangeNotifyDeregister來取消程式監視。該函式的原型如下:
BOOL SHChangeNotifyDeregister(ULONG ulID);
其中ulID指定了要登出的監視註冊標誌號,如果解除安裝成功,返回TRUE,否則返回FALSE。
二、例項
在使用這兩個函式之前,必須要先宣告它們的原型,同時還要新增一些巨集和結構定義。我們在原工程中新增一個ShellDef.h標頭檔案,然後加入如下宣告:
#define SHCNRF_InterruptLevel 0x0001 //Interrupt level notifications from the file system
#define SHCNRF_ShellLevel 0x0002 //Shell-level notifications from the shell
#define SHCNRF_RecursiveInterrupt 0x1000 //Interrupt events on the whole subtree
#define SHCNRF_NewDelivery 0x8000 //Messages received use shared memory
typedef struct
{
LPCITEMIDLIST pidl; //Pointer to an item identifier list (PIDL) for which to receive notifications
BOOL fRecursive; //Flag indicating whether to post notifications for children of this PIDL
}SHChangeNotifyEntry;
typedef struct
{
DWORD dwItem1; // dwItem1 contains the previous PIDL or name of the folder.
DWORD dwItem2; // dwItem2 contains the new PIDL or name of the folder.
}SHNotifyInfo;
typedef ULONG
(WINAPI* pfnSHChangeNotifyRegister)
(
HWND hWnd,
int fSource,
LONG fEvents,
UINT wMsg,
int cEntries,
SHChangeNotifyEntry* pfsne
);
typedef BOOL (WINAPI* pfnSHChangeNotifyDeregister)(ULONG ulID);
這些巨集和函式的宣告,以及引數含義,如前所述。下面我們要在CListCtrlEx體內新增兩個函式指標和一個ULONG型的成員變數,以儲存函式地址和返回的註冊號。
接下來實現一個函式Initialize,在其中,我們首先進行載入Shell32.dll以及初始化函式指標動作,接著呼叫註冊函式向Shell註冊。
BOOL Initialize()
{
…………
//載入Shell32.dll
m_hShell32 = LoadLibrary("Shell32.dll");
if(m_hShell32 == NULL)
{
return FALSE;
}
//取函式地址
m_pfnDeregister = NULL;
m_pfnRegister = NULL;
m_pfnRegister = (pfnSHChangeNotifyRegister)GetProcAddress(m_hShell32,MAKEINTRESOURCE(2));
m_pfnDeregister = (pfnSHChangeNotifyDeregister)GetProcAddress(m_hShell32,MAKEINTRESOURCE(4));
if(m_pfnRegister==NULL || m_pfnDeregister==NULL)
{
return FALSE;
}
SHChangeNotifyEntry shEntry = {0};
shEntry.fRecursive = TRUE;
shEntry.pidl = 0;
m_ulNotifyId = 0;
//註冊Shell監視函式
m_ulNotifyId = m_pfnRegister(
GetSafeHwnd(),
SHCNRF_InterruptLevel|SHCNRF_ShellLevel,
SHCNE_ALLEVENTS,
WM_USERDEF_FILECHANGED, //自定義訊息
1,
&shEntry
);
if(m_ulNotifyId == 0)
{
MessageBox("Register failed!","ERROR",MB_OK|MB_ICONERROR);
return FALSE;
}
return TRUE;
}
=================================================================
通過 FindFirstChangeNotification 實現
=================================================================
FindFirstChangeNotification函式建立一個更改通知控制代碼並設定初始更改通知過濾條件.
當一個在指定目錄或子目錄下發生的更改符合過濾條件時,等待通知控制代碼則成功。
該函式原型為:
HANDLE FindFirstChangeNotification(
LPCTSTR lpPathName, //目錄名
BOOL bWatchSubtree, // 監視選項
DWORD dwNotifyFilter // 過濾條件
);
當下列情況之一發生時,WaitForMultipleObjects函式返回
1.一個或者全部指定的物件在訊號狀態(signaled state)
2.到達超時間隔
例程如下:
DWORD dwWaitStatus;
HANDLE dwChangeHandles[2];
//監視C:\Windows目錄下的檔案建立和刪除
dwChangeHandles[0] = FindFirstChangeNotification(
"C:\\WINDOWS", // directory to watch
FALSE, // do not watch the subtree
FILE_NOTIFY_CHANGE_FILE_NAME); // watch file name changes
if (dwChangeHandles[0] == INVALID_HANDLE_VALUE)
ExitProcess(GetLastError());
//監視C:\下子目錄樹的檔案建立和刪除
dwChangeHandles[1] = FindFirstChangeNotification(
"C:\\", // directory to watch
TRUE, // watch the subtree
FILE_NOTIFY_CHANGE_DIR_NAME); // watch dir. name changes
if (dwChangeHandles[1] == INVALID_HANDLE_VALUE)
ExitProcess(GetLastError());
// Change notification is set. Now wait on both notification
// handles and refresh accordingly.
while (TRUE)
{
// Wait for notification.
dwWaitStatus = WaitForMultipleObjects(2, dwChangeHandles,FALSE, INFINITE);
switch (dwWaitStatus)
{
case WAIT_OBJECT_0:
//在C:\WINDOWS目錄中建立或刪除檔案 。
//重新整理該目錄及重啟更改通知(change notification).
AfxMessageBox("RefreshDirectory");
if ( FindNextChangeNotification(dwChangeHandles[0]) == FALSE )
ExitProcess(GetLastError());
break;
case WAIT_OBJECT_0 1:
//在C:\WINDOWS目錄中建立或刪除檔案 。
//重新整理該目錄樹及重啟更改通知(change notification).
AfxMessageBox("RefreshTree");
if (FindNextChangeNotification(dwChangeHandles[1]) == FALSE)
ExitProcess(GetLastError());
break;
default:
ExitProcess(GetLastError());
}
}
=================================================================
通過 ReadDirectoryChangesW 實現
=================================================================
bool Monitor()
{
HANDLE hFile = CreateFile(
"c:\\",
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL
);
if( INVALID_HANDLE_VALUE == hFile ) return false;
char buf[ 2*(sizeof(FILE_NOTIFY_INFORMATION)+MAX_PATH) ];
FILE_NOTIFY_INFORMATION* pNotify=(FILE_NOTIFY_INFORMATION *)buf;
DWORD BytesReturned;
while(true)
{
if( ReadDirectoryChangesW( hFile,
pNotify,
sizeof(buf),
true,
FILE_NOTIFY_CHANGE_FILE_NAME|
FILE_NOTIFY_CHANGE_DIR_NAME|
FILE_NOTIFY_CHANGE_ATTRIBUTES|
FILE_NOTIFY_CHANGE_SIZE|
FILE_NOTIFY_CHANGE_LAST_WRITE|
FILE_NOTIFY_CHANGE_LAST_ACCESS|
FILE_NOTIFY_CHANGE_CREATION|
FILE_NOTIFY_CHANGE_SECURITY,
&BytesReturned,
NULL,
NULL ) )
{
char tmp[MAX_PATH], str1[MAX_PATH], str2[MAX_PATH];
memset( tmp, 0, sizeof(tmp) );
WideCharToMultiByte( CP_ACP,0,pNotify->FileName,pNotify->FileNameLength/2,tmp,99,NULL,NULL );
strcpy( str1, tmp );
if( 0 != pNotify->NextEntryOffset )
{
PFILE_NOTIFY_INFORMATION p = (PFILE_NOTIFY_INFORMATION)((char*)pNotify+pNotify->NextEntryOffset);
memset( tmp, 0, sizeof(tmp) );
WideCharToMultiByte( CP_ACP,0,p->FileName,p->FileNameLength/2,tmp,99,NULL,NULL );
strcpy( str2, tmp );
}
// your process
}
else
{
break;
}
}
return true;
}
=================================================================
通過 未公開API SHChangeNotifyRegister 實現
=================================================================
一、原理
Windows 內部有兩個未公開的函式(注:在最新的MSDN中,已經公開了這兩個函式),分別叫做SHChangeNotifyRegister和 SHChangeNotifyDeregister,可以實現以上的功能。這兩個函式位於Shell32.dll中,是用序號方式匯出的。這就是為什麼我們用VC自帶的Depends工具察看Shell32.dll時,找不到這兩個函式的原因。SHChangeNotifyRegister的匯出序號是 2;而SHChangeNotifyDeregister的匯出序號是4。
SHChangeNotifyRegister可以把指定的視窗新增到系統的訊息監視鏈中,這樣視窗就能接收到來自檔案系統或者Shell的通知了。而對應的另一個函式,SHChangeNotifyDeregister,則用來取消監視鉤掛。SHChangeNotifyRegister的原型和相關引數如下:
ULONG SHChangeNotifyRegister
(
HWND hwnd,
int fSources,
LONG fEvents,
UINT wMsg,
Int cEntries,
SHChangeNotifyEntry *pfsne
);
其中:
hwnd
將要接收改變或通知訊息的視窗的控制代碼。
fSource
指示接收訊息的事件型別,將是下列值的一個或多個(注:這些標誌沒有被包括在任何標頭檔案中,使用者須在自己的程式中加以定義或者直接使用其對應的數值)
SHCNRF_InterruptLevel
0x0001。接收來自檔案系統的中斷級別通知訊息。
SHCNRF_ShellLevel
0x0002。接收來自Shell的Shell級別通知訊息。
SHCNRF_RecursiveInterrupt
0x1000。接收目錄下所有子目錄的中斷事件。此標誌必須和SHCNRF_InterruptLevel 標誌合在一起使用。當使用該標誌時,必須同時設定對應的SHChangeNotifyEntry結構體中的fRecursive成員為TRUE(此結構體由函式的最後一個引數pfsne指向),這樣通知訊息在目錄樹上是遞迴的。
SHCNRF_NewDelivery
0x8000。接收到的訊息使用共享記憶體。必須先呼叫SHChangeNotification_Lock,然後才能存取實際的資料,完成後呼叫SHChangeNotification_Unlock函式釋放記憶體。
fEvents
要捕捉的事件,其所有可能的值請參見MSDN中關於SHChangeNotify函式的註解。
wMsg
產生對應的事件後,發往視窗的訊息。
cEntries
pfsne指向的陣列的成員的個數。
pfsne
SHChangeNotifyEntry 結構體陣列的起始指標。此結構體承載通知訊息,其成員個數必須設定成1,否則SHChangeNotifyRegister或者 SHChangeNotifyDeregister將不能正常工作(但是據我試驗,如果cEntries設為大於1的值,依然可以註冊成功,不知何故)。
如果函式呼叫成功,則返回一個整型註冊標誌號,否則將返回0。同時系統就會將hwnd指定的視窗加入到操作監視鏈中,當有檔案操作發生時,系統會向hwnd標識的視窗傳送wMsg指定的訊息,我們只要在程式中加入對該訊息的處理函式就可以實現對系統操作的監視了。
如果要退出程式監視,就要呼叫另外一個未公開得函式SHChangeNotifyDeregister來取消程式監視。該函式的原型如下:
BOOL SHChangeNotifyDeregister(ULONG ulID);
其中ulID指定了要登出的監視註冊標誌號,如果解除安裝成功,返回TRUE,否則返回FALSE。
二、例項
在使用這兩個函式之前,必須要先宣告它們的原型,同時還要新增一些巨集和結構定義。我們在原工程中新增一個ShellDef.h標頭檔案,然後加入如下宣告:
#define SHCNRF_InterruptLevel 0x0001 //Interrupt level notifications from the file system
#define SHCNRF_ShellLevel 0x0002 //Shell-level notifications from the shell
#define SHCNRF_RecursiveInterrupt 0x1000 //Interrupt events on the whole subtree
#define SHCNRF_NewDelivery 0x8000 //Messages received use shared memory
typedef struct
{
LPCITEMIDLIST pidl; //Pointer to an item identifier list (PIDL) for which to receive notifications
BOOL fRecursive; //Flag indicating whether to post notifications for children of this PIDL
}SHChangeNotifyEntry;
typedef struct
{
DWORD dwItem1; // dwItem1 contains the previous PIDL or name of the folder.
DWORD dwItem2; // dwItem2 contains the new PIDL or name of the folder.
}SHNotifyInfo;
typedef ULONG
(WINAPI* pfnSHChangeNotifyRegister)
(
HWND hWnd,
int fSource,
LONG fEvents,
UINT wMsg,
int cEntries,
SHChangeNotifyEntry* pfsne
);
typedef BOOL (WINAPI* pfnSHChangeNotifyDeregister)(ULONG ulID);
這些巨集和函式的宣告,以及引數含義,如前所述。下面我們要在CListCtrlEx體內新增兩個函式指標和一個ULONG型的成員變數,以儲存函式地址和返回的註冊號。
接下來實現一個函式Initialize,在其中,我們首先進行載入Shell32.dll以及初始化函式指標動作,接著呼叫註冊函式向Shell註冊。
BOOL Initialize()
{
…………
//載入Shell32.dll
m_hShell32 = LoadLibrary("Shell32.dll");
if(m_hShell32 == NULL)
{
return FALSE;
}
//取函式地址
m_pfnDeregister = NULL;
m_pfnRegister = NULL;
m_pfnRegister = (pfnSHChangeNotifyRegister)GetProcAddress(m_hShell32,MAKEINTRESOURCE(2));
m_pfnDeregister = (pfnSHChangeNotifyDeregister)GetProcAddress(m_hShell32,MAKEINTRESOURCE(4));
if(m_pfnRegister==NULL || m_pfnDeregister==NULL)
{
return FALSE;
}
SHChangeNotifyEntry shEntry = {0};
shEntry.fRecursive = TRUE;
shEntry.pidl = 0;
m_ulNotifyId = 0;
//註冊Shell監視函式
m_ulNotifyId = m_pfnRegister(
GetSafeHwnd(),
SHCNRF_InterruptLevel|SHCNRF_ShellLevel,
SHCNE_ALLEVENTS,
WM_USERDEF_FILECHANGED, //自定義訊息
1,
&shEntry
);
if(m_ulNotifyId == 0)
{
MessageBox("Register failed!","ERROR",MB_OK|MB_ICONERROR);
return FALSE;
}
return TRUE;
}
=================================================================
通過 FindFirstChangeNotification 實現
=================================================================
FindFirstChangeNotification函式建立一個更改通知控制代碼並設定初始更改通知過濾條件.
當一個在指定目錄或子目錄下發生的更改符合過濾條件時,等待通知控制代碼則成功。
該函式原型為:
HANDLE FindFirstChangeNotification(
LPCTSTR lpPathName, //目錄名
BOOL bWatchSubtree, // 監視選項
DWORD dwNotifyFilter // 過濾條件
);
當下列情況之一發生時,WaitForMultipleObjects函式返回
1.一個或者全部指定的物件在訊號狀態(signaled state)
2.到達超時間隔
例程如下:
DWORD dwWaitStatus;
HANDLE dwChangeHandles[2];
//監視C:\Windows目錄下的檔案建立和刪除
dwChangeHandles[0] = FindFirstChangeNotification(
"C:\\WINDOWS", // directory to watch
FALSE, // do not watch the subtree
FILE_NOTIFY_CHANGE_FILE_NAME); // watch file name changes
if (dwChangeHandles[0] == INVALID_HANDLE_VALUE)
ExitProcess(GetLastError());
//監視C:\下子目錄樹的檔案建立和刪除
dwChangeHandles[1] = FindFirstChangeNotification(
"C:\\", // directory to watch
TRUE, // watch the subtree
FILE_NOTIFY_CHANGE_DIR_NAME); // watch dir. name changes
if (dwChangeHandles[1] == INVALID_HANDLE_VALUE)
ExitProcess(GetLastError());
// Change notification is set. Now wait on both notification
// handles and refresh accordingly.
while (TRUE)
{
// Wait for notification.
dwWaitStatus = WaitForMultipleObjects(2, dwChangeHandles,FALSE, INFINITE);
switch (dwWaitStatus)
{
case WAIT_OBJECT_0:
//在C:\WINDOWS目錄中建立或刪除檔案 。
//重新整理該目錄及重啟更改通知(change notification).
AfxMessageBox("RefreshDirectory");
if ( FindNextChangeNotification(dwChangeHandles[0]) == FALSE )
ExitProcess(GetLastError());
break;
case WAIT_OBJECT_0 1:
//在C:\WINDOWS目錄中建立或刪除檔案 。
//重新整理該目錄樹及重啟更改通知(change notification).
AfxMessageBox("RefreshTree");
if (FindNextChangeNotification(dwChangeHandles[1]) == FALSE)
ExitProcess(GetLastError());
break;
default:
ExitProcess(GetLastError());
}
}
=================================================================
通過 ReadDirectoryChangesW 實現
=================================================================
bool Monitor()
{
HANDLE hFile = CreateFile(
"c:\\",
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL
);
if( INVALID_HANDLE_VALUE == hFile ) return false;
char buf[ 2*(sizeof(FILE_NOTIFY_INFORMATION)+MAX_PATH) ];
FILE_NOTIFY_INFORMATION* pNotify=(FILE_NOTIFY_INFORMATION *)buf;
DWORD BytesReturned;
while(true)
{
if( ReadDirectoryChangesW( hFile,
pNotify,
sizeof(buf),
true,
FILE_NOTIFY_CHANGE_FILE_NAME|
FILE_NOTIFY_CHANGE_DIR_NAME|
FILE_NOTIFY_CHANGE_ATTRIBUTES|
FILE_NOTIFY_CHANGE_SIZE|
FILE_NOTIFY_CHANGE_LAST_WRITE|
FILE_NOTIFY_CHANGE_LAST_ACCESS|
FILE_NOTIFY_CHANGE_CREATION|
FILE_NOTIFY_CHANGE_SECURITY,
&BytesReturned,
NULL,
NULL ) )
{
char tmp[MAX_PATH], str1[MAX_PATH], str2[MAX_PATH];
memset( tmp, 0, sizeof(tmp) );
WideCharToMultiByte( CP_ACP,0,pNotify->FileName,pNotify->FileNameLength/2,tmp,99,NULL,NULL );
strcpy( str1, tmp );
if( 0 != pNotify->NextEntryOffset )
{
PFILE_NOTIFY_INFORMATION p = (PFILE_NOTIFY_INFORMATION)((char*)pNotify+pNotify->NextEntryOffset);
memset( tmp, 0, sizeof(tmp) );
WideCharToMultiByte( CP_ACP,0,p->FileName,p->FileNameLength/2,tmp,99,NULL,NULL );
strcpy( str2, tmp );
}
// your process
}
else
{
break;
}
}
return true;
}