개발중 디렉토리 변경 감시 모듈이 필요했다.
우선적으로 생각한 방법은 단순히 일정 시간(T) 마다 해당디렉토리의 전체 파일목록을 불러와서 변경된 것이 있는지 확인하는 것 이었다. 하지만 이 방법으로 처리를 하게되면 T가 작아지면 자원을 더 많이 사용할 것이고 T가 커지면 변경된 순간으로 부터 인식하는 순간까지의 시간이 커질 것이다. 그래서 좀 더 나은 방법을 찾아보던 중 FindFirstChangeNotification, WaitForMultiObjects, FindNextChangeNotification, FindCloseChangeNotification 이란 WinAPI들을 사용하면 가능하다는 것을 알았다. 그리고는 구글을 통해 여러 소스를 찾아보았지만 내 입맛에 맞는 것이 없어 직접 만들기로 하였다.
1. FindFirstChangeNotification
HANDLE WINAPI FindFirstChangeNotification( _In_ LPCTSTR lpPathName, _In_ BOOL bWatchSubtree, _In_ DWORD dwNotifyFilter );
DWORD WINAPI WaitForMultipleObjects(
_In_ DWORD nCount,
_In_ const HANDLE *lpHandles,
_In_ BOOL bWaitAll,
_In_ DWORD dwMilliseconds
);
nCount : 감시 할 오브젝트의 갯수
*lpHandles : 오브젝트들의 핸들
bWaitAll : 모든 오브젝트의 신호를 감시할 것인가 여부
dwMilliseconds : 신호를 감지할때까지 기다릴 시간오브젝트에 신호가 있을때 까지 기다린 후 몇번째 핸들에서 신호를 수신했는지 리턴한다.
기다리는 동안 해당 스레드는 어떠한 작업도 할 수 없다.3. FindNextChangeNotificationBOOL WINAPI FindNextChangeNotification( _In_ HANDLE hChangeHandle );hChangeHandle : FindFirstChangeNotification 을 통해 리턴받은 오브젝트의 핸들
오브젝트에서 신호가 수신된 후 다음 신호를 감시하도록 설정한다. 성공여부를 리턴한다.
4. FindCloseChangeNotification
BOOL WINAPI FindCloseChangeNotification( _In_ HANDLE hChangeHandle );
hChangeHandle : FindFirstChageNotification 을 통해 리턴받은 오브젝트의 핸들
변경을 감시하는 것을 중지 한다.
위 API 들을 이용해서 다음처럼 동작한다.
감시시작(FIndFirstChangeNotification) -> 신호기다림(WaitForMultiObjects) -> 신호발생
-> 계속 감시(FindNextChangeNotification) or 감시 중단(FIndCloseChangeNotification)
다음은 해당 로직을 파워빌더로 구현한 것이다.
우선 사용할 WinAPI 함수를 지역 함수로 선언해 주었다.
FUNCTION ULONG FindFirstChangeNotification(&
REF STRING lpPathName,&
BOOLEAN bWatchSubtree,&
ULONG dwNotifyFilter &
)LIBRARY "Kernel32.dll" ALIAS FOR "FindFirstChangeNotificationA;Ansi"
FUNCTION ULONG WaitForSingleObject(&
ULONG hHandle,&
ULONG dwMilliseconds &
)LIBRARY "Kernel32.dll"
FUNCTION BOOLEAN FindNextChangeNotification(&
ULONG hChangeHandle &
)LIBRARY "Kernel32.dll"
FUNCTION ULONG WaitForMultipleObjects(&
ULONG nCount, &
ULONG lpHandle[], &
BOOLEAN bWaitALL, &
ULONG dwMilliseconds &
)LIBRARY "Kernel32.dll"
FUNCTION BOOLEAN FindCloseChangeNotification(&
ULONG hChangeHandle &
)LIBRARY "Kernel32.dll"
FUNCTION BOOLEAN ResetEvent(&
ULONG hEvent &
)LIBRARY "Kernel32.dll"
다음으로 감시 모듈을 시작시키는 사용자 함수를 만들었다.
public subroutine of_startmonitor (string directory, unsignedlong hwhd_receiver, unsignedlong hevent_exit, boolean watchsubtree);
/* WinAPI 들의 인자로 사용되는 상수들 */
CONSTANT BOOLEAN WATCH_SUBTREE=true
CONSTANT BOOLEAN DONT_WATCH_SUBTREE=false
CONSTANT BOOLEAN FILE_NOTIFY_CHANGE_FILE_NAME=1
CONSTANT ULONG INFINITE = 4294967295
ULONG lula_handle[] //감시할 오브젝트의 핸들을 저장할 변수
ULONG lul_nCount //감시할 오브젝트의 갯수를 저장할 변수
INTEGER li_i //FOR문에 사용할 변수
lula_handle[1] = hevent_exit //감시종료 이벤트 핸들로 함수 인자로 넘겨 받는다.
lula_handle[2] = FindFirstChangeNotification(directory, DONT_WATCH_SUBTREE, FILE_NOTIFY_CHANGE_FILE_NAME)
// 해당디렉토리(서버트리제외)를 감시 설정
IF watchsubtree THEN
lula_handle[3] = FindFirstChangeNotification(directory, WATCH_SUBTREE, FILE_NOTIFY_CHANGE_FILE_NAME)
// 넘겨받은 인자중 watchsubtree가 TRUE 일 경우 서브트리도 감시 하도록 설정
END IF
lul_nCount = UpperBound(lula_handle) //핸들이 저장된 배열의 갯수를 저장해준다
FOR li_i = 2 TO INTEGER(lul_nCount) //FindFirstChangeNotification 이 정상 처리되었는지 확인
IF isNull(lula_handle[li_i]) THEN
MessageBox("ERROR", "Unexpected NULL from FindFirstChangeNotification")
RETURN
ELSEIF lula_handle[li_i] = -1 THEN
MessageBox("ERROR", "FindFirstChangeNotification function failed.")
RETURN
END IF
NEXT
/*
총 3개의 오브젝트 신호를 기다리도록 설정한다.
첫번째 핸들은 이 모듈을 중지시킬 유일한 방법은 종료 이벤트
두번째 핸들은 해당 디렉토리 변경을 감시하는 오브젝트
세번째 핸들은 해당 디렉토리의 서브트리 변경을 감시하는 오브젝트
첫번째 핸들에서 신호가 오면 변경감시하는 오브젝트를 닫아주고 본 객체를 종료시킨다.
두번째 핸들이나 세번째 핸들에서 신호가오면 다음변경을 감시하도록 설정을 하고
인자로 넘겨받은 변경을 보고 받을 윈도우에 메세지를 보낸다.
현재 설정되있는 것은 두번째 핸들은 WM_USER를 세번째 핸들은 WM_USER+1을 보낸다.
*/
DO WHILE TRUE
ULONG lu_dwWaitStatus
lu_dwWaitStatus = WaitForMultipleObjects(lul_nCount,lula_handle,FALSE,INFINITE)
IF lu_dwWaitStatus = 0 THEN
IF FindCloseChangeNotification(lula_handle[2]) = FALSE THEN
MessageBox("ERROR", "FindCloseChangeNotification function failed")
END IF
IF watchsubtree THEN
IF FindCloseChangeNotification(lula_handle[3]) = FALSE THEN
MessageBox("ERROR", "FindCloseChangeNotification function failed")
END IF
END IF
IF ResetEvent(lula_handle[1]) = FALSE THEN
MessageBox("ERROR", "ResetEvent function failed")
END IF
HALT CLOSE
ELSEIF lu_dwWaitStatus = 1 THEN
Send(hWhd_receiver,1024,0,0)
IF FindNextChangeNotification(lula_handle[2]) = FALSE THEN
MessageBox("ERROR", "FindNextChangeNotification function failed")
END IF
ELSEIF lu_dwWaitStatus = 2 THEN
Send(hWhd_receiver,1025,0,0)
IF FindNextChangeNotification(lula_handle[3]) = FALSE THEN
MessageBox("ERROR", "FindNextChangeNotification function failed")
END IF
END IF
LOOP
end subroutine
작성된 함수를 보면 인자로 directory(감시할폴더), hwhd_receiver(보고받을윈도우핸들), hevent_exit(종료이벤트핸들), watchsubtree(서버트리 감시여부)를 넘겨받는다.
이 모듈은 감시를 할때 다른 작업을 동시에 할수 없기때문에 멀티스레드 프로세스에 이용할 목적으로 만들었다.
메인스레드에서는 CreateEvent, SetEvent WinAPI를 통해 종료 이벤트를 생성해서 인자에 포함시켜 호출을 한다.
또한 변경이 감시 되었을때 hwhd_receiver로 메세지를 보내도록 되어있는데 Sned(hWhd_receiver,1024,0,0) 이 부분을 수정해서 다른 작업을 하도록 하거나 다른 메시지를 전송하도록 할 수 있다.
개발환경
파워빌더 11.5
윈도우 7 32bit