Skip to content

Instantly share code, notes, and snippets.

@brcolow
Created March 30, 2024 10:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brcolow/e6c2e59a3aa29d32d3332bcf10313031 to your computer and use it in GitHub Desktop.
Save brcolow/e6c2e59a3aa29d32d3332bcf10313031 to your computer and use it in GitHub Desktop.
Example for getting known folder ids using Java Foreign Function & Memory API
import java.lang.foreign.AddressLayout;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.GroupLayout;
import java.lang.foreign.Linker;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
import static java.lang.foreign.ValueLayout.JAVA_CHAR;
public class JavaDir {
private static final String FOLDERID_LocalAppData = "{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}";
private static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup().or(Linker.nativeLinker().defaultLookup());
public static final ValueLayout.OfBoolean C_BOOL = ValueLayout.JAVA_BOOLEAN;
public static final ValueLayout.OfByte C_CHAR = ValueLayout.JAVA_BYTE;
public static final ValueLayout.OfShort C_SHORT = ValueLayout.JAVA_SHORT;
public static final ValueLayout.OfInt C_INT = ValueLayout.JAVA_INT;
public static final ValueLayout.OfLong C_LONG_LONG = ValueLayout.JAVA_LONG;
public static final ValueLayout.OfFloat C_FLOAT = ValueLayout.JAVA_FLOAT;
public static final ValueLayout.OfDouble C_DOUBLE = ValueLayout.JAVA_DOUBLE;
public static final AddressLayout C_POINTER = ValueLayout.ADDRESS
.withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, JAVA_BYTE));
public static final ValueLayout.OfInt C_LONG = ValueLayout.JAVA_INT;
public static final ValueLayout.OfDouble C_LONG_DOUBLE = ValueLayout.JAVA_DOUBLE;
public static void main(String[] args) {
System.loadLibrary("ole32");
System.loadLibrary("shell32");
try (var arena = Arena.ofConfined()) {
MemorySegment guid = arena.allocate(_GUID_LAYOUT);
if (CLSIDFromString(createSegmentFromString(FOLDERID_LocalAppData, arena), guid) != 0) {
System.exit(-1);
}
MemorySegment path = arena.allocate(C_POINTER);
SHGetKnownFolderPath(guid, 0, MemorySegment.NULL, path);
System.out.println(createStringFromSegment(path.get(C_POINTER, 0)));
}
}
/**
* Creates a memory segment as a copy of a Java string.
* <p>
* The memory segment contains a copy of the string (null-terminated, UTF-16/wide characters).
* </p>
*
* @param str the string to copy
* @param arena the arena for the memory segment
* @return the resulting memory segment
*/
public static MemorySegment createSegmentFromString(String str, Arena arena) {
// allocate segment (including space for terminating null)
var segment = arena.allocate(JAVA_CHAR, str.length() + 1L);
// copy characters
segment.copyFrom(MemorySegment.ofArray(str.toCharArray()));
return segment;
}
/**
* Creates a copy of the string in the memory segment.
* <p>
* The string must be a null-terminated UTF-16 (wide character) string.
* </p>
*
* @param segment the memory segment
* @return copied string
*/
public static String createStringFromSegment(MemorySegment segment) {
var len = 0;
while (segment.get(JAVA_CHAR, len) != 0) {
len += 2;
}
return new String(segment.asSlice(0, len).toArray(JAVA_CHAR));
}
static MemorySegment findOrThrow(String symbol) {
return SYMBOL_LOOKUP.find(symbol)
.orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol: " + symbol));
}
private static final GroupLayout _GUID_LAYOUT = MemoryLayout.structLayout(
C_LONG.withName("Data1"),
C_SHORT.withName("Data2"),
C_SHORT.withName("Data3"),
MemoryLayout.sequenceLayout(8, C_CHAR).withName("Data4")
).withName("_GUID");
/**
* {@snippet lang=c :
* extern HRESULT CLSIDFromString(LPCOLESTR lpsz, LPCLSID pclsid)
* }
*/
public static int CLSIDFromString(MemorySegment lpsz, MemorySegment pclsid) {
var mh$ = CLSIDFromString.HANDLE;
try {
return (int)mh$.invokeExact(lpsz, pclsid);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
}
}
private static class CLSIDFromString {
public static final FunctionDescriptor DESC = FunctionDescriptor.of(
C_LONG,
C_POINTER,
C_POINTER
);
public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(
findOrThrow("CLSIDFromString"), DESC);
}
/**
* {@snippet lang=c :
* extern HRESULT SHGetKnownFolderPath(const KNOWNFOLDERID *const rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath)
* }
*/
public static int SHGetKnownFolderPath(MemorySegment rfid, int dwFlags, MemorySegment hToken, MemorySegment ppszPath) {
var mh$ = SHGetKnownFolderPath.HANDLE;
try {
return (int)mh$.invokeExact(rfid, dwFlags, hToken, ppszPath);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
}
}
private static class SHGetKnownFolderPath {
public static final FunctionDescriptor DESC = FunctionDescriptor.of(
C_LONG,
C_POINTER,
C_LONG,
C_POINTER,
C_POINTER
);
public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(
findOrThrow("SHGetKnownFolderPath"), DESC);
}
}
$jdk = "C:\Program Files\Java\jdk-22"
New-Alias -Name javac -Value "$jdk\bin\javac" -Force
New-Alias -Name java -Value "$jdk\bin\java" -Force
javac --enable-preview --source=22 -d . *.java
java --source=22 --enable-preview --enable-native-access=ALL-UNNAMED JavaDir.java
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment