Java 네이티브 인터페이스(JNI)를 활용하여 Windows 레지스트리에 접근하고 값을 읽고 쓰는 방법을 설명합니다. 이 예제는 64비트 Windows 환경에서 테스트되었으며, Java와 C 코드를 연동하여 네이티브 API를 호출하는 과정을 다룹니다.
프로젝트 구성
이 프로젝트는 다음과 같은 주요 파일로 구성됩니다:
- Win32RegKey.java: 레지스트리 키 조작의 핵심 로직을 담고 있는 Java 클래스입니다. 내부적으로
Win32RegKeyException및Win32RegKeyNameEnumeration클래스를 사용합니다. - Win32RegKey.c: JNI를 통해 Windows API와 통신하는 C/C++ 소스 코드입니다. Java 클래스의 네이티브 메서드에 대한 구현을 포함합니다.
- Win32RegKeyTest.java: 레지스트리 쓰기 및 읽기 기능을 테스트하는 Java 클래스입니다.
JNI 헤더 파일 생성
JNI 인터페이스를 사용하기 위해서는 Java 네이티브 메서드에 대한 C/C++ 헤더 파일을 생성해야 합니다. 먼저 Java 코드를 컴파일합니다.
javac Win32RegKey.java
컴파일 후 생성된 `.class` 파일들을 사용하여 javah 명령어로 헤더 파일을 생성합니다. 패키지 구조에 따라 명령어가 달라질 수 있습니다.
src> javah Win32RegKey src> javah Win32RegKeyNameEnumeration
만약 패키지가 com.example.jni와 같다면 다음과 같이 실행합니다.
src> javah com.example.jni.Win32RegKey
이 과정을 통해 Win32RegKey.h 및 Win32RegKeyNameEnumeration.h 파일이 생성됩니다.
DLL 컴파일
생성된 헤더 파일과 C 소스 코드를 사용하여 Windows DLL 라이브러리를 컴파일합니다. GCC 컴파일러 환경이 필요하며, MinGW-64 (64비트) 또는 MinGW (32비트)를 사용할 수 있습니다.
다음은 64비트 DLL을 컴파일하는 예시 명령어입니다. JDK 경로(`jdk`)는 실제 설치 경로로 변경해야 합니다.
gcc -D __int64="long long" -Wl,--kill-at -I "C:/Program Files/Java/jdk-11.0.1/bin/../include" -I "C:/Program Files/Java/jdk-11.0.1/bin/../include/win32" -shared Win32RegKey.c -o Win32RegKey.dll
-D __int64="long long": MSVC의__int64타입에 대한 GNU 호환성을 설정합니다.-I: JNI 헤더 파일의 include 경로를 지정합니다.-shared: 공유 라이브러리(DLL) 생성을 지시합니다.
컴파일이 완료되면 생성된 Win32RegKey.dll 파일을 JDK의 jre/bin 디렉토리에 복사합니다. 예를 들어, C:/Program Files/Java/jdk-11.0.1/jre/bin 입니다.
Java 코드 예제
Win32RegKey.java
import java.util.Enumeration;
/**
* Windows 레지스트리 키를 조작하기 위한 클래스입니다.
*/
public class Win32RegKey {
/**
* 레지스트리 키 객체를 생성합니다.
*
* @param rootKey HKEY_CLASSES_ROOT, HKEY_CURRENT_USER 등 루트 키 상수
* @param keyPath 레지스트리 키 경로
*/
public Win32RegKey(int rootKey, String keyPath) {
this.root = rootKey;
this.path = keyPath;
}
/**
* 현재 레지스트리 키 아래의 모든 항목 이름을 열거합니다.
*
* @return 항목 이름 열거자
*/
public Enumeration<String> names() {
return new Win32RegKeyNameEnumeration(root, path);
}
/**
* 지정된 이름의 레지스트리 값을 읽어옵니다.
*
* @param name 읽어올 값의 이름
* @return 해당 값 (String, Integer, byte[] 등)
*/
public native Object getValue(String name);
/**
* 지정된 이름의 레지스트리 값을 설정합니다.
*
* @param name 설정할 값의 이름
* @param value 설정할 값 (String, Integer, byte[] 등)
*/
public native void setValue(String name, Object value);
// 레지스트리 루트 키 상수
public static final int HKEY_CLASSES_ROOT = 0x80000000;
public static final int HKEY_CURRENT_USER = 0x80000001;
public static final int HKEY_LOCAL_MACHINE = 0x80000002;
public static final int HKEY_USERS = 0x80000003;
public static final int HKEY_CURRENT_CONFIG = 0x80000005;
public static final int HKEY_DYN_DATA = 0x80000006;
private int root;
private String path;
// 네이티브 라이브러리 로드
static {
System.loadLibrary("Win32RegKey");
}
}
/**
* 레지스트리 키의 항목 이름을 열거하는 클래스입니다.
*/
class Win32RegKeyNameEnumeration implements Enumeration<String> {
Win32RegKeyNameEnumeration(int rootKey, String keyPath) {
this.root = rootKey;
this.path = keyPath;
}
public native String nextElement();
public native boolean hasMoreElements();
private int root;
private String path;
private int index = -1; // 열거 상태 추적
private int hkey = 0; // 네이티브 핸들
private int maxSize; // 최대 이름 길이
private int count; // 항목 수
}
/**
* 레지스트리 작업 중 발생하는 예외를 위한 커스텀 런타임 예외입니다.
*/
class Win32RegKeyException extends RuntimeException {
public Win32RegKeyException() {}
public Win32RegKeyException(String message) {
super(message);
}
}
Win32RegKeyTest.java
import java.util.Enumeration;
/**
* Win32RegKey 클래스의 기능을 테스트하는 메인 애플리케이션입니다.
*/
public class Win32RegKeyTest {
public static void main(String[] args) {
// HKEY_CURRENT_USER 아래의 특정 경로에 대한 Win32RegKey 객체 생성
Win32RegKey regKey = new Win32RegKey(
Win32RegKey.HKEY_CURRENT_USER, "Software\\JavaSoft\\Java Runtime Environment");
// 레지스트리 값 설정
regKey.setValue("DefaultUser", "TestUser");
regKey.setValue("TestNumber", Integer.valueOf(123));
regKey.setValue("BinaryData", new byte[]{0x01, 0x02, 0x03, 0x04});
System.out.println("--- 레지스트리 항목 ---");
// 모든 항목 이름 열거 및 값 읽기
Enumeration<String> names = regKey.names();
while (names.hasMoreElements()) {
String name = names.nextElement();
System.out.print(name + " = ");
Object value = regKey.getValue(name);
if (value instanceof byte[]) {
// 바이트 배열의 경우 각 바이트를 16진수 형식으로 출력
for (byte b : (byte[]) value) {
System.out.printf("%02X ", b);
}
} else {
System.out.print(value);
}
System.out.println();
}
System.out.println("--------------------");
}
}
C/C++ JNI 구현 (Win32RegKey.c)
이 C 코드는 Java 네이티브 메서드를 Windows 레지스트리 API와 연결합니다. getValue, setValue, 그리고 이름 열거를 위한 헬퍼 함수들을 구현합니다.
#include "Win32RegKey.h" // Java 클래스에 대응하는 헤더
#include "Win32RegKeyNameEnumeration.h" // 열거자 클래스 헤더
#include <string.h>
#include <stdlib.h>
#include <windows.h>
// 레지스트리 값 읽기 함수 구현
JNIEXPORT jobject JNICALL Java_Win32RegKey_getValue(JNIEnv* env, jobject thisObj, jstring valueName) {
const char* cValueName;
jstring keyPathJava;
const char* cKeyPath;
HKEY hKey;
DWORD dataType;
DWORD dataSize;
jclass thisClass;
jfieldID rootFieldID, pathFieldID;
HKEY rootKey;
jobject result;
char* dataBuffer;
// Java 객체의 클래스 및 필드 ID 가져오기
thisClass = (*env)->GetObjectClass(env, thisObj);
rootFieldID = (*env)->GetFieldID(env, thisClass, "root", "I");
pathFieldID = (*env)->GetFieldID(env, thisClass, "path", "Ljava/lang/String;");
// 필드 값 읽기
rootKey = (HKEY)(*env)->GetIntField(env, thisObj, rootFieldID);
keyPathJava = (jstring)(*env)->GetObjectField(env, thisObj, pathFieldID);
cKeyPath = (*env)->GetStringUTFChars(env, keyPathJava, NULL);
// 레지스트리 키 열기 (읽기 전용)
if (RegOpenKeyEx(rootKey, cKeyPath, 0, KEY_READ, &hKey) != ERROR_SUCCESS) {
(*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "레지스트리 키 열기 실패");
(*env)->ReleaseStringUTFChars(env, keyPathJava, cKeyPath);
return NULL;
}
(*env)->ReleaseStringUTFChars(env, keyPathJava, cKeyPath);
// 값 이름 UTF-8 문자열로 변환
cValueName = (*env)->GetStringUTFChars(env, valueName, NULL);
// 값의 타입과 크기 조회
if (RegQueryValueEx(hKey, cValueName, NULL, &dataType, NULL, &dataSize) != ERROR_SUCCESS) {
(*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "레지스트리 값 정보 조회 실패");
RegCloseKey(hKey);
(*env)->ReleaseStringUTFChars(env, valueName, cValueName);
return NULL;
}
// 값 데이터를 저장할 버퍼 할당
dataBuffer = (char*)malloc(dataSize);
if (dataBuffer == NULL) {
(*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "메모리 할당 실패");
RegCloseKey(hKey);
(*env)->ReleaseStringUTFChars(env, valueName, cValueName);
return NULL;
}
// 실제 값 데이터 읽기
if (RegQueryValueEx(hKey, cValueName, NULL, &dataType, (LPBYTE)dataBuffer, &dataSize) != ERROR_SUCCESS) {
(*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "레지스트리 값 읽기 실패");
free(dataBuffer);
RegCloseKey(hKey);
(*env)->ReleaseStringUTFChars(env, valueName, cValueName);
return NULL;
}
// 데이터 타입에 따라 Java 객체 생성
if (dataType == REG_SZ) { // 문자열
result = (*env)->NewStringUTF(env, dataBuffer);
} else if (dataType == REG_DWORD) { // DWORD (정수)
jclass integerClass = (*env)->FindClass(env, "java/lang/Integer");
jmethodID constructorID = (*env)->GetMethodID(env, integerClass, "<init>", "(I)V");
int intValue = *(int*)dataBuffer;
result = (*env)->NewObject(env, integerClass, constructorID, intValue);
} else if (dataType == REG_BINARY) { // 이진 데이터 (바이트 배열)
result = (*env)->NewByteArray(env, dataSize);
(*env)->SetByteArrayRegion(env, (jarray)result, 0, dataSize, (jbyte*)dataBuffer);
} else { // 지원하지 않는 타입
(*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "지원하지 않는 값 타입");
result = NULL;
}
free(dataBuffer);
RegCloseKey(hKey);
(*env)->ReleaseStringUTFChars(env, valueName, cValueName);
return result;
}
// 레지스트리 값 설정 함수 구현
JNIEXPORT void JNICALL Java_Win32RegKey_setValue(JNIEnv* env, jobject thisObj,
jstring valueName, jobject value) {
const char* cValueName;
jstring keyPathJava;
const char* cKeyPath;
HKEY hKey;
DWORD dataType;
DWORD dataSize;
jclass thisClass;
jfieldID rootFieldID, pathFieldID;
HKEY rootKey;
const char* stringData;
int intData;
const char* byteAreaData;
// Java 객체 정보 가져오기
thisClass = (*env)->GetObjectClass(env, thisObj);
rootFieldID = (*env)->GetFieldID(env, thisClass, "root", "I");
pathFieldID = (*env)->GetFieldID(env, thisClass, "path", "Ljava/lang/String;");
rootKey = (HKEY)(*env)->GetIntField(env, thisObj, rootFieldID);
keyPathJava = (jstring)(*env)->GetObjectField(env, thisObj, pathFieldID);
cKeyPath = (*env)->GetStringUTFChars(env, keyPathJava, NULL);
// 레지스트리 키 열기 (쓰기 가능)
if (RegOpenKeyEx(rootKey, cKeyPath, 0, KEY_WRITE, &hKey) != ERROR_SUCCESS) {
(*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "레지스트리 키 열기 실패");
(*env)->ReleaseStringUTFChars(env, keyPathJava, cKeyPath);
return;
}
(*env)->ReleaseStringUTFChars(env, keyPathJava, cKeyPath);
// 값 이름 UTF-8 문자열로 변환
cValueName = (*env)->GetStringUTFChars(env, valueName, NULL);
// 입력 값의 타입 판별
jclass valueClass = (*env)->GetObjectClass(env, value);
jclass stringClass = (*env)->FindClass(env, "java/lang/String");
jclass integerClass = (*env)->FindClass(env, "java/lang/Integer");
jclass byteArrayClass = (*env)->FindClass(env, "[B");
if ((*env)->IsAssignableFrom(env, valueClass, stringClass)) { // String 타입
stringData = (*env)->GetStringUTFChars(env, (jstring)value, NULL);
dataType = REG_SZ;
dataSize = (*env)->GetStringLength(env, (jstring)value) + 1; // null 종료 문자 포함
} else if ((*env)->IsAssignableFrom(env, valueClass, integerClass)) { // Integer 타입
jmethodID intValueMethodID = (*env)->GetMethodID(env, integerClass, "intValue", "()I");
intData = (*env)->CallIntMethod(env, value, intValueMethodID);
dataType = REG_DWORD;
byteAreaData = (char*)&intData; // int 포인터를 char*로 캐스팅
dataSize = sizeof(int);
} else if ((*env)->IsAssignableFrom(env, valueClass, byteArrayClass)) { // byte[] 타입
dataType = REG_BINARY;
byteAreaData = (char*)(*env)->GetByteArrayElements(env, (jarray)value, NULL);
dataSize = (*env)->GetArrayLength(env, (jarray)value);
} else { // 지원하지 않는 타입
(*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "지원하지 않는 값 타입");
RegCloseKey(hKey);
(*env)->ReleaseStringUTFChars(env, valueName, cValueName);
return;
}
// 레지스트리 값 설정
if (RegSetValueEx(hKey, cValueName, 0, dataType, (const BYTE*)byteAreaData, dataSize) != ERROR_SUCCESS) {
(*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "레지스트리 값 설정 실패");
}
RegCloseKey(hKey);
(*env)->ReleaseStringUTFChars(env, valueName, cValueName);
// String 또는 byte[] 타입의 경우, GetByteArrayElements/GetStringUTFChars로 얻은 포인터 해제
if (dataType == REG_SZ) {
(*env)->ReleaseStringUTFChars(env, (jstring)value, stringData);
} else if (dataType == REG_BINARY) {
(*env)->ReleaseByteArrayElements(env, (jarray)value, (jbyte*)byteAreaData, 0);
}
}
// 이름 열거를 위한 초기화 헬퍼 함수
static int initializeNameEnumeration(JNIEnv* env, jobject thisObj, jclass thisClass) {
jfieldID indexFieldID, countFieldID, rootFieldID, pathFieldID, hkeyFieldID, maxSizeFieldID;
HKEY rootKey, hKey;
jstring keyPathJava;
const char* cKeyPath;
DWORD maxNameLen = 0;
DWORD subKeyCount = 0;
// 필드 ID 가져오기
rootFieldID = (*env)->GetFieldID(env, thisClass, "root", "I");
pathFieldID = (*env)->GetFieldID(env, thisClass, "path", "Ljava/lang/String;");
hkeyFieldID = (*env)->GetFieldID(env, thisClass, "hkey", "I");
maxSizeFieldID = (*env)->GetFieldID(env, thisClass, "maxSize", "I");
indexFieldID = (*env)->GetFieldID(env, thisClass, "index", "I");
countFieldID = (*env)->GetFieldID(env, thisClass, "count", "I");
// 필드 값 가져오기
rootKey = (HKEY)(*env)->GetIntField(env, thisObj, rootFieldID);
keyPathJava = (jstring)(*env)->GetObjectField(env, thisObj, pathFieldID);
cKeyPath = (*env)->GetStringUTFChars(env, keyPathJava, NULL);
// 레지스트리 키 열기
if (RegOpenKeyEx(rootKey, cKeyPath, 0, KEY_READ, &hKey) != ERROR_SUCCESS) {
(*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "레지스트리 키 열기 실패");
(*env)->ReleaseStringUTFChars(env, keyPathJava, cKeyPath);
return -1;
}
(*env)->ReleaseStringUTFChars(env, keyPathJava, cKeyPath);
// 키 정보 조회 (하위 키 수, 최대 이름 길이 등)
if (RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, &subKeyCount, &maxNameLen,
NULL, NULL, NULL) != ERROR_SUCCESS) {
(*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "레지스트리 키 정보 조회 실패");
RegCloseKey(hKey);
return -1;
}
// 열거자 객체의 필드 설정
(*env)->SetIntField(env, thisObj, hkeyFieldID, (DWORD)hKey);
(*env)->SetIntField(env, thisObj, maxSizeFieldID, maxNameLen + 1); // null 종료 문자 공간 포함
(*env)->SetIntField(env, thisObj, indexFieldID, 0); // 인덱스 초기화
(*env)->SetIntField(env, thisObj, countFieldID, subKeyCount); // 항목 수 설정
return subKeyCount; // 총 항목 수 반환
}
// Win32RegKeyNameEnumeration.hasMoreElements 네이티브 메서드 구현
JNIEXPORT jboolean JNICALL Java_Win32RegKeyNameEnumeration_hasMoreElements(JNIEnv* env,
jobject thisObj) {
jclass thisClass = (*env)->GetObjectClass(env, thisObj);
jfieldID indexFieldID = (*env)->GetFieldID(env, thisClass, "index", "I");
jfieldID countFieldID = (*env)->GetFieldID(env, thisClass, "count", "I");
int currentIndex = (*env)->GetIntField(env, thisObj, indexFieldID);
if (currentIndex == -1) { // 첫 호출 시 초기화
int totalCount = initializeNameEnumeration(env, thisObj, thisClass);
currentIndex = 0; // 초기화 후 인덱스 0으로 설정
if (totalCount == -1) return JNI_FALSE; // 초기화 실패 시
} else {
// 이미 초기화된 경우, count 필드 값 가져오기
currentIndex = (*env)->GetIntField(env, thisObj, indexFieldID);
}
int totalCount = (*env)->GetIntField(env, thisObj, countFieldID);
return currentIndex < totalCount;
}
// Win32RegKeyNameEnumeration.nextElement 네이티브 메서드 구현
JNIEXPORT jobject JNICALL Java_Win32RegKeyNameEnumeration_nextElement(JNIEnv* env,
jobject thisObj) {
jclass thisClass = (*env)->GetObjectClass(env, thisObj);
jfieldID indexFieldID = (*env)->GetFieldID(env, thisClass, "index", "I");
jfieldID countFieldID = (*env)->GetFieldID(env, thisClass, "count", "I");
jfieldID hkeyFieldID = (*env)->GetFieldID(env, thisClass, "hkey", "I");
jfieldID maxSizeFieldID = (*env)->GetFieldID(env, thisClass, "maxSize", "I");
int currentIndex = (*env)->GetIntField(env, thisObj, indexFieldID);
int totalCount = (*env)->GetIntField(env, thisObj, countFieldID);
// 첫 호출 시 초기화 (hasMoreElements 에서 이미 처리되었을 수 있음)
if (currentIndex == -1) {
totalCount = initializeNameEnumeration(env, thisObj, thisClass);
if (totalCount == -1) return NULL; // 초기화 실패
currentIndex = 0;
}
// 열거 범위 초과 시 예외 발생
if (currentIndex >= totalCount) {
(*env)->ThrowNew(env, (*env)->FindClass(env, "java/util/NoSuchElementException"), "열거 범위를 벗어났습니다");
return NULL;
}
DWORD currentMaxSize = (*env)->GetIntField(env, thisObj, maxSizeFieldID);
HKEY hKey = (HKEY)(*env)->GetIntField(env, thisObj, hkeyFieldID);
char* nameBuffer = (char*)malloc(currentMaxSize);
if (nameBuffer == NULL) {
(*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "메모리 할당 실패");
RegCloseKey(hKey); // 열린 핸들 닫기
return NULL;
}
// 다음 레지스트리 항목 이름 조회
// RegEnumValue는 이름의 실제 길이를 currentMaxSize에 저장함
if (RegEnumValue(hKey, currentIndex, nameBuffer, ¤tMaxSize, NULL, NULL, NULL, NULL) != ERROR_SUCCESS) {
(*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "레지스트리 이름 열거 실패");
free(nameBuffer);
RegCloseKey(hKey);
(*env)->SetIntField(env, thisObj, indexFieldID, totalCount); // 인덱스를 끝으로 이동시켜 hasMoreElements가 false 반환하도록 함
return NULL;
}
jstring resultName = (*env)->NewStringUTF(env, nameBuffer);
free(nameBuffer);
// 인덱스 증가
currentIndex++;
(*env)->SetIntField(env, thisObj, indexFieldID, currentIndex);
// 마지막 항목인 경우, 키 핸들 닫기
if (currentIndex == totalCount) {
RegCloseKey(hKey);
}
return resultName;
}