아시다시피 Java 에서 Win32 API 를 호출하려면, JNI 를 사용해야 합니다.
Java Native Interface 이던가요? 약어는 가물가물 하네요. 귀차니즘...

아무튼 이번에는 Java 의 JNI 를 이용하여, 폴더 모니터를 만들어 보도록 하겠습니다.
폴더 모니터가 무언가 하니, 특정 폴더에 파일이 추가되거나, 삭제되거나 이름이 변경되거나 하는 등...
변화가 있을 때, Notify 해 주는 녀석을 만들어 보자.. 이 말입니다.


JNI 를 Java 에서 사용하는 방법(?)은 다음과 같습니다.

1. Java 파일을 열심히 만든다.
  - 코딩하란 소리입니다.

2. 앞서 작성한 Java 소스파일을 컴파일 한다.

3. javah 를 이용하여, *.h 파일을 생성한다.
 - c 에서 사용할 헤더파일을 만들라는 것입니다.

4. 앞에서 만든 header 파일을 구현한, dll 파일을 생성한다.
 - *.h 에 정의된 함수들을 구현한 dll 파일을 생성한다는 말입니다.

5. 끗!


간단한가요?
직접 한번 만들어 보도록 해보죠.


1. Java 파일을 열심히 만든다.


package com;


public class FolderWatcher implements Runnable {
	// win32
	private final static int WAIT_OBJECT_0 = 0x00000000;
	private final static int WAIT_TIMEOUT = 258;
	
	// Filter
	public final static int FILTER_CHANGE_FILE_NAME    = 0x00000001;
	public final static int FILTER_CHANGE_DIR_NAME	   = 0x00000002;
	public final static int FILTER_CHANGE_ATTRIBUTES   = 0x00000004;
	public final static int FILTER_CHANGE_SIZE         = 0x00000008;
	public final static int FILTER_CHANGE_LAST_WRITE   = 0x00000010;
	public final static int FILTER_CHANGE_SECURITY     = 0x00000100;
	
	// win32 api
	private native int FindFirstChangeNotification(String folder, boolean watchSubtree, int filter);
	private native boolean FindNextChangeNotification(int hChangeHandle);
	private native boolean FindCloseChangeNotification(int hChangeHandle);
	private native int WaitForMultipleObjects(int nCount, int handle, boolean waitAll, int milliSeconds);
	
	// jni
	static { System.loadLibrary("FolderWatcher"); }
	
	// listener
	private IFolderChangeListener listener = null;
	
	private boolean isWatching = false;
	private int handle = 0;
	private volatile Object lockObj = new Object();
	
	private String folderPath = "";
	private boolean watchSubfolder = false;
	private int filter;
	
	private Thread watchThread = null;
	
	
	//============
	// 생성자
	//============
	public FolderWatcher(IFolderChangeListener listener) {
		this.listener = listener;
	}
	
	
	
	public boolean start(String folderPath, boolean watchSubfolder, int filter) {
		synchronized (this.lockObj) {
			// 이미 실행중인 경우
			if(isWatching == true) { return true; }
			
			// setting
			this.folderPath = folderPath;
			this.watchSubfolder = watchSubfolder;
			this.filter = filter;
			
			// Thread 실행
			this.watchThread = new Thread(this);
			this.watchThread.setDaemon(true);
			this.watchThread.start();
			
			return true;
		}
	}
	
	
	@Override
	public void run(){
		this.isWatching = true;
		
		handle = FindFirstChangeNotification(this.folderPath, this.watchSubfolder, this.filter);
		if(handle == -1) {
			System.out.println("FindFirstChangeNotification function failed.");
			return;
		}
		if(handle == 0) {
			System.out.println("ERROR: FindFirstChangeNotification function failed.");
			return;
		}
		
		while(this.isWatching == true) {
			System.out.println("Waiting for notification...");
			int waitStatus = WaitForMultipleObjects(1, handle, false, 0xFFFFFFFF);
			
			switch(waitStatus)
			{
			case WAIT_OBJECT_0:
				if(FindNextChangeNotification(handle) == true) {
					callback();
				}
				break;
			case WAIT_TIMEOUT:
				System.out.println("No changes in the timeout period.");
				break;
			default:
				System.out.println("ERROR: Unhandled dwWaitStatus.");
				break;
			}
		}
	}
	
	
	
	public boolean stop() {
		synchronized (this.lockObj) {
			if(this.isWatching == false) { return true; }
			//stopWatch();
			FindCloseChangeNotification(this.handle);
			this.isWatching = false;
			this.watchThread.interrupt();
			
			return true;
		}
	}
	
	
	private void callback() {
		if(this.listener != null) {
			this.listener.folderChanged(this, this.folderPath);
		}
	}
} 



여기서 중요한 부분(JNI 관련된 부분)은
// win32 api
 private native int FindFirstChangeNotification(String folder, boolean watchSubtree, int filter);
 private native boolean FindNextChangeNotification(int hChangeHandle);
 private native boolean FindCloseChangeNotification(int hChangeHandle);
 private native int WaitForMultipleObjects(int nCount, int handle, boolean waitAll, int milliSeconds);

// jni
 static { System.loadLibrary("FolderWatcher"); }


입니다. 나머진 늘 보는 Java 코드입니다.

저기서 native 란, 눈치 빠른 분들은 아셨겠지만 외부 dll 에 정의된 함수라고 알려주는 것입니다.
즉, 이 소스코드에서는 함수 원형만 제공할 뿐이죠.

뒤에 따라 나오는 static { System.loadLibrary("FolderWatcher"); } 는 dll 을 불러오겠다는 말입니다.
이때 주의해야 하는 것은, dll 파일 명이 xxxx.dll 일 경우 xxxx 만 적어야 한다는 것입니다.
다시 말하지만, dll 의 파일명(확장자를 제외한)입니다.


2. 앞서 작성한 Java 소스파일을 컴파일 한다.
다들 잘 하실거라고 믿습니다.


3. javah 를 이용하여, *.h 파일을 생성한다.
잘 보셔야 합니다.
javah -classpath ./ com.FolderWatcher
이렇게 하면, 현재 위치에 FolderWatcher.h 비슷한 이름의 파일이 생깁니다.

만약 패키지명이 com.win32.api 였다면,,,
javah -classpath ./ com.win32.api.FolderWatcher

아시겠나요?

뒤에 오는 FolderWatcher 는 *.class 파일명이 아닌, 바로 Java 의 Class 명이라는 것입니다.
다시 말하지만, 파일명이 아니라 Class 명 입니다.


4. 앞에서 만든 header 파일을 구현한, dll 파일을 생성한다.
저는 Visual Studio 2008 로 만들었습니다.
C++ 에서


#include "FolderWatcher.h"
#include <windows.h>

/*
 * Class:     pCloud_win32_FolderWatcher
 * Method:    FindFirstChangeNotification
 * Signature: (Ljava/lang/String;ZI)I
 */
JNIEXPORT jint JNICALL Java_pCloud_win32_FolderWatcher_FindFirstChangeNotification
  (JNIEnv * env, jobject obj, jstring path, jboolean watchSubtree, jint filter)
{
	jchar* new_path = (*env)->GetStringChars(env, path, NULL);
	OutputDebugString((LPCWSTR)new_path);

	return (jint)FindFirstChangeNotification((LPCWSTR)new_path, (BOOL)watchSubtree, filter);
}

/*
 * Class:     pCloud_win32_FolderWatcher
 * Method:    FindNextChangeNotification
 * Signature: (I)I
 */
JNIEXPORT jboolean JNICALL Java_pCloud_win32_FolderWatcher_FindNextChangeNotification
  (JNIEnv * env, jobject obj, jint changeHandle)
{
	return FindNextChangeNotification((HANDLE)changeHandle);
}

/*
 * Class:     pCloud_win32_FolderWatcher
 * Method:    FindCloseChangeNotification
 * Signature: (I)I
 */
JNIEXPORT jboolean JNICALL Java_pCloud_win32_FolderWatcher_FindCloseChangeNotification
  (JNIEnv * env, jobject obj, jint changeHandle)
{
	return FindCloseChangeNotification((HANDLE)changeHandle);
}

/*
 * Class:     pCloud_win32_FolderWatcher
 * Method:    WaitForMultipleObjects
 * Signature: (IIZI)I
 */
JNIEXPORT jint JNICALL Java_pCloud_win32_FolderWatcher_WaitForMultipleObjects
  (JNIEnv * env, jobject obj, jint count, jint changeHandle, jboolean waitAll, jint milliSeconds)
{
	HANDLE handle = (HANDLE)changeHandle;
	return WaitForMultipleObjects(1, &handle, (BOOL)waitAll, (DWORD)milliSeconds);

} 




5. 끗!
이제 dll 을 적당한 위치에 넣고나서 java 를 실행하면, 정상적으로 프로그램이 돌아가는 것을 보실 수 있습니다.


  • 아이오티플 2019.12.18 10:43 신고

    좋은글 감사합니다.
    하나 질문좀 드려도 될까요?
    저는 mac 을 사용하는데 java 폴더에 가보면
    java, javac 등 파일은 다있는데 javah 파일만 없는경우
    어떻게 해야하는지 아시는지요?