Hello, I have been using JNA for years, but only one function to read Windows registry.
Now that JDK 22 is released, I though of implementing that function with java.lang.foreign.
This is it:
Advapi32Util.java
public static TreeMap<String, Object> registryGetValues(HKEY root,
String keyPath, int samDesiredExtra) {
try (Arena arena = Arena.ofConfined()) {
SymbolLookup advapi32 = SymbolLookup.libraryLookup("Advapi32", Arena.global());
Linker linker = Linker.nativeLinker();
MemorySegment hKeyRef = arena.allocate(JAVA_LONG);
Object o;
try {
o = linker.downcallHandle(
advapi32.find("RegOpenKeyExW").orElseThrow(),
FunctionDescriptor.of(
JAVA_INT, // return 0 = ERROR_SUCCESS
JAVA_LONG, // HKey
ADDRESS, // sub key
JAVA_INT, // options
JAVA_INT, // samDesired
ADDRESS // address of returned handle
))
.invoke(
Pointer.nativeValue(root.getPointer()),
arena.allocateFrom(keyPath, StandardCharsets.UTF_16LE),
0, // options
WinNT.KEY_READ | samDesiredExtra,
hKeyRef
);
} catch (Throwable e) {
throw new RuntimeException(e);
}
int rc = (int)(Integer)o;
if (rc != W32Errors.ERROR_SUCCESS) {
throw new RuntimeException("RegOpenKeyExA " + rc);
// throw new Win32Exception(rc);
}
long hKey = hKeyRef.get(JAVA_LONG, 0);
try {
return registryGetValues(advapi32, arena, hKey);
} finally {
try {
o = linker.downcallHandle(
advapi32.find("RegCloseKey").orElseThrow(),
FunctionDescriptor.of(
JAVA_INT, // return 0 = ERROR_SUCCESS
JAVA_LONG // HKey
))
.invoke(hKey);
rc = (int)(Integer)o;
if (rc != W32Errors.ERROR_SUCCESS) {
log.log(System.Logger.Level.ERROR, "RegCloseKey", rc);
// throw new Win32Exception(rc);
}
} catch (Throwable e) {
log.log(System.Logger.Level.ERROR, "RegCloseKey", e);
}
}
}
}
public static TreeMap<String, Object> registryGetValues(SymbolLookup advapi32, Arena arena,
long hKey) {
TreeMap<String, Object> keyValues = new TreeMap<>();
Linker linker = Linker.nativeLinker();
MemorySegment lpcValues = arena.allocate(JAVA_INT);
MemorySegment lpcMaxValueNameLen = arena.allocate(JAVA_INT);
MemorySegment lpcMaxValueLen = arena.allocate(JAVA_INT);
Object o;
try {
o = linker.downcallHandle(
advapi32.find("RegQueryInfoKeyA").orElseThrow(),
FunctionDescriptor.of(
JAVA_INT, // return 0 = ERROR_SUCCESS
JAVA_LONG, // HKey
ADDRESS, // lpClass
ADDRESS, // lpClass
ADDRESS, // lpReserved
ADDRESS, // lpcSubkeys
ADDRESS, // lpcMaxSubKeyLen
ADDRESS, // lpcMaxClassLen
ADDRESS, // lpcValues
ADDRESS, // lpcMaxValueNameLen
ADDRESS, // lpcMaxValueLen
ADDRESS, // lpcbSecurityDescriptor
ADDRESS // lpftLastWriteTime
))
.invoke(
hKey,
MemorySegment.NULL, // lpClass
MemorySegment.NULL, // lpClass
MemorySegment.NULL, // lpReserved
MemorySegment.NULL, // lpcSubkeys
MemorySegment.NULL, // lpcMaxSubKeyLen
MemorySegment.NULL, // lpcMaxClassLen
lpcValues,
lpcMaxValueNameLen,
lpcMaxValueLen,
MemorySegment.NULL, // lpcbSecurityDescriptor
MemorySegment.NULL // lpftLastWriteTime
);
} catch (Throwable e) {
throw new RuntimeException(e);
}
int rc = (int) (Integer) o;
if (rc != W32Errors.ERROR_SUCCESS) {
throw new RuntimeException("RegQueryInfoKeyW " + rc);
// throw new Win32Exception(rc);
}
int maxValueNameLen = lpcMaxValueNameLen.get(JAVA_INT, 0);
MemorySegment name = arena.allocate(JAVA_CHAR, maxValueNameLen + 1);
// Allocate enough memory to hold largest value and two
// terminating WCHARs -- the memory is zeroed so after
// value request we should not overread when reading strings
int maxValueLen = lpcMaxValueLen.get(JAVA_INT, 0);
MemorySegment byteData = arena.allocate(JAVA_CHAR, maxValueLen + 2);
int valueCount = lpcValues.get(JAVA_INT, 0);
MemorySegment lpcchValueName = arena.allocate(JAVA_INT);
MemorySegment lpcbData = arena.allocate(JAVA_INT);
MemorySegment lpType = arena.allocate(JAVA_INT);
for (int i = 0; i < valueCount; i++) {
byteData.asByteBuffer().clear(); // TODO: Arrays.fill(b.array(), (byte)0) ???
lpcchValueName.set(JAVA_INT, 0, maxValueLen + 1);
lpcbData.set(JAVA_INT, 0, maxValueLen);
try {
o = linker.downcallHandle(
advapi32.find("RegEnumValueW").orElseThrow(),
FunctionDescriptor.of(
JAVA_INT, // return 0 = ERROR_SUCCESS
JAVA_LONG, // HKey
JAVA_INT, // dwIndex
ADDRESS, // lpValueName
ADDRESS, // lpcchValueName
ADDRESS, // reserved
ADDRESS, // lpType
ADDRESS, // lpData
ADDRESS // lpcbData
))
.invoke(hKey, i, name, lpcchValueName, MemorySegment.NULL, lpType, byteData, lpcbData);
} catch (Throwable e) {
throw new RuntimeException(e);
}
rc = (int) (Integer) o;
if (rc != W32Errors.ERROR_SUCCESS) {
throw new RuntimeException("RegEnumValueW " + rc);
// throw new Win32Exception(rc);
}
String nameString = name.getString(0, StandardCharsets.UTF_16LE);
int dataSize = lpcbData.get(JAVA_INT, 0);
int type = lpType.get(JAVA_INT, 0);
if (dataSize == 0) {
switch (type) {
case WinNT.REG_BINARY: {
keyValues.put(nameString, new byte[0]);
break;
}
case WinNT.REG_SZ:
case WinNT.REG_EXPAND_SZ: {
keyValues.put(nameString, new char[0]);
break;
}
case WinNT.REG_MULTI_SZ: {
keyValues.put(nameString, new String[0]);
break;
}
case WinNT.REG_NONE: {
keyValues.put(nameString, null);
break;
}
default:
throw new RuntimeException("Unsupported empty type: " + type);
}
continue;
}
switch (type) {
case WinNT.REG_QWORD: {
keyValues.put(nameString, byteData.get(JAVA_LONG, 0));
break;
}
case WinNT.REG_DWORD: {
keyValues.put(nameString, byteData.get(JAVA_INT, 0));
break;
}
case WinNT.REG_SZ:
case WinNT.REG_EXPAND_SZ: {
keyValues.put(nameString, byteData.getString(0, StandardCharsets.UTF_16LE));
break;
}
case WinNT.REG_BINARY: {
byte[] buf = new byte[dataSize];
byteData.asByteBuffer().limit(dataSize).get(buf);
keyValues.put(nameString, buf);
break;
}
case WinNT.REG_MULTI_SZ: {
ArrayList<String> result = new ArrayList<>();
int offset = 0;
while (offset < dataSize) {
String s = byteData.getString(offset, StandardCharsets.UTF_16LE);
offset += s.length() * 2; // TODO: ???
offset += 2;
if (s.isEmpty()) {
// A sequence of null-terminated strings,
// terminated by an empty string (\0).
// => The first empty string terminates the
break;
} else {
result.add(s);
}
}
keyValues.put(nameString, result.toArray(new String[0]));
break;
}
default:
throw new RuntimeException("Unsupported type: " + type);
}
}
return keyValues;
}
It works fine, but unfortunately I can't use it yet because we are still targeting Java 11.
Posting this if this is useful for someone.
Converting JNA to java.lang.foreign is quite straightforward, but quite a lot of work.
This took me one day.
Wondering if we get JNA for JDK 22 later?
-Harri