Created
March 30, 2024 10:09
-
-
Save brcolow/e6c2e59a3aa29d32d3332bcf10313031 to your computer and use it in GitHub Desktop.
Example for getting known folder ids using Java Foreign Function & Memory API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$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