diff --git a/CHANGES.md b/CHANGES.md index d8a80ce078..9557593dd6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ Next Release (5.13.0) Features -------- +* [#1454](https://github.com/java-native-access/jna/pull/1454): Add `c.s.j.p.win32.Psapi.QueryWorkingSetEx` and associated Types - [@crain-32](https://github.com/Crain-32). Bug Fixes --------- diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Psapi.java b/contrib/platform/src/com/sun/jna/platform/win32/Psapi.java index dfd13d204d..43cb959e17 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/Psapi.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Psapi.java @@ -26,8 +26,10 @@ import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.Structure; +import com.sun.jna.Structure.ByReference; import com.sun.jna.Structure.FieldOrder; import com.sun.jna.platform.win32.BaseTSD.SIZE_T; +import com.sun.jna.platform.win32.BaseTSD.ULONG_PTR; import com.sun.jna.platform.win32.WinDef.DWORD; import com.sun.jna.platform.win32.WinDef.HMODULE; import com.sun.jna.platform.win32.WinNT.HANDLE; @@ -292,6 +294,18 @@ public interface Psapi extends StdCallLibrary { */ boolean EnumProcesses(int[] lpidProcess, int cb, IntByReference lpcbNeeded); + /** + * Retrieves extended information about the pages at specific + * virtual addresses in the address space of the specified process. + * + * @param hProcess A Handle to the Process + * @param pv A pointer to an array of PSAPI_WORKING_SET_EX_INFORMATION structures + * @param cb The size of the pv buffer, in bytes. + * @return If the function succeeds, the return value is nonzero. + * @see MSDN + */ + boolean QueryWorkingSetEx(HANDLE hProcess, Pointer pv, int cb); + @FieldOrder({"lpBaseOfDll", "SizeOfImage", "EntryPoint"}) class MODULEINFO extends Structure { public Pointer EntryPoint; @@ -320,4 +334,17 @@ class PERFORMANCE_INFORMATION extends Structure { public DWORD ProcessCount; public DWORD ThreadCount; } + + @FieldOrder({"Flags", "Data"}) + class PSAPI_WORKING_SET_EX_BLOCK extends Structure implements ByReference { + public ULONG_PTR Flags; + public ULONG_PTR[] Data = new ULONG_PTR[Native.POINTER_SIZE == 8 ? 1 : 2]; + } + + + @FieldOrder({"VirtualAddress", "VirtualAttributes"}) + class PSAPI_WORKING_SET_EX_INFORMATION extends Structure { + public Pointer VirtualAddress; + public PSAPI_WORKING_SET_EX_BLOCK VirtualAttributes; + } } diff --git a/contrib/platform/src/com/sun/jna/platform/win32/PsapiUtil.java b/contrib/platform/src/com/sun/jna/platform/win32/PsapiUtil.java index efa5bacf45..4d6a87f62f 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/PsapiUtil.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/PsapiUtil.java @@ -28,6 +28,7 @@ import com.sun.jna.platform.win32.WinDef.DWORD; import com.sun.jna.platform.win32.WinNT.HANDLE; +import com.sun.jna.platform.win32.Psapi.PSAPI_WORKING_SET_EX_BLOCK; import com.sun.jna.ptr.IntByReference; /** @@ -87,4 +88,73 @@ public static String GetProcessImageFileName(HANDLE hProcess) { } } } + + /** + * If this bit is 1, the other values in the PSAPI_WORKING_SET_EX_BLOCK are valid. + */ + public static boolean isPsapiWorkingSetExBlockValid(PSAPI_WORKING_SET_EX_BLOCK psapiWorkingSetExBlock) { + return PsapiUtil.getBitFieldValue(psapiWorkingSetExBlock.Data[0].longValue(), 1, 0) == 1; + } + + /** + * The number of processes that share the page addressed by the PSAPI_WORKING_SET_EX_BLOCK. The maximum value of this member is 7. + */ + public static int getPsapiWorkingSetExBlockShareCount(PSAPI_WORKING_SET_EX_BLOCK psapiWorkingSetExBlock) { + return PsapiUtil.getBitFieldValue(psapiWorkingSetExBlock.Data[0].longValue(), 3, 1); + } + + /** + * The memory protection attributes of the page addressed by the PSAPI_WORKING_SET_EX_BLOCK. For a list of values see below. + * @see Memory Protection Constants. + */ + public static int getPsapiWorkingSetExBlockWin32Protection(PSAPI_WORKING_SET_EX_BLOCK psapiWorkingSetExBlock) { + return PsapiUtil.getBitFieldValue(psapiWorkingSetExBlock.Data[0].longValue(),11, 3 + 1); + } + + /** + * If this bit is 1, the page addressed by the PSAPI_WORKING_SET_EX_BLOCK can be shared. + */ + public static boolean isPsapiWorkingSetExBlockShared(PSAPI_WORKING_SET_EX_BLOCK psapiWorkingSetExBlock) { + return PsapiUtil.getBitFieldValue(psapiWorkingSetExBlock.Data[0].longValue(), 1, 11 + 3 + 1) == 1; + } + + /** + * The NUMA node of the page addressed by the PSAPI_WORKING_SET_EX_BLOCK. The maximum value of this member is 63. + */ + public static int getPsapiWorkingSetExBlockNode(PSAPI_WORKING_SET_EX_BLOCK psapiWorkingSetExBlock) { + return PsapiUtil.getBitFieldValue(psapiWorkingSetExBlock.Data[0].longValue(), 6, 1 + 11 + 3 + 1); + } + + /** + * If this bit is 1, the virtual page addressed by the PSAPI_WORKING_SET_EX_BLOCK is locked in physical memory. + */ + public static boolean isPsapiWorkingSetExBlockLocked(PSAPI_WORKING_SET_EX_BLOCK psapiWorkingSetExBlock) { + return PsapiUtil.getBitFieldValue(psapiWorkingSetExBlock.Data[0].longValue(), 1, 6 + 1 + 11 + 3 + 1) == 1; + } + + /** + * If this bit is 1, the page addressed by the PSAPI_WORKING_SET_EX_BLOCK is a large page. + */ + public static boolean isPsapiWorkingSetExBlockLargePage(PSAPI_WORKING_SET_EX_BLOCK psapiWorkingSetExBlock) { + return PsapiUtil.getBitFieldValue(psapiWorkingSetExBlock.Data[0].longValue(),1, 1 + 6 + 1 + 11 + 3 + 1) == 1; + } + + /** + * If this bit is 1, the page addressed by the PSAPI_WORKING_SET_EX_BLOCK is has been reported as bad. + */ + public static boolean isPsapiWorkingSetExBlockBad(PSAPI_WORKING_SET_EX_BLOCK psapiWorkingSetExBlock) { + return PsapiUtil.getBitFieldValue(psapiWorkingSetExBlock.Data[0].longValue(), 1,1 + 1 + 1 + 6 + 1 + 11 + 3 + 1) == 1; + } + + /** + * Returns a Right Shifted and Masked version of value. + */ + public static int getBitFieldValue(final long value, final int maskLength, final int rightShiftAmount) { + long bitMask = 0; + + for (int l = 0; l < maskLength; l++) { + bitMask |= 1 << l; + } + return (int) ((value >>> rightShiftAmount) & bitMask); + } } diff --git a/contrib/platform/test/com/sun/jna/platform/win32/PsapiTest.java b/contrib/platform/test/com/sun/jna/platform/win32/PsapiTest.java index 7af0c9b275..a617d1db7f 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/PsapiTest.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/PsapiTest.java @@ -24,6 +24,8 @@ package com.sun.jna.platform.win32; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertFalse; import java.util.LinkedList; import java.util.List; @@ -34,12 +36,15 @@ import com.sun.jna.Memory; import com.sun.jna.Native; +import com.sun.jna.Pointer; import com.sun.jna.platform.win32.Psapi.MODULEINFO; import com.sun.jna.platform.win32.Psapi.PERFORMANCE_INFORMATION; import com.sun.jna.platform.win32.WinDef.DWORD; import com.sun.jna.platform.win32.WinDef.HMODULE; import com.sun.jna.platform.win32.WinDef.HWND; import com.sun.jna.platform.win32.WinNT.HANDLE; +import com.sun.jna.platform.win32.WinNT.MEMORY_BASIC_INFORMATION; +import com.sun.jna.platform.win32.BaseTSD.SIZE_T; import com.sun.jna.ptr.IntByReference; /** @@ -273,4 +278,42 @@ public void testEnumProcesses() { } assertTrue("List should contain my pid", foundMyPid); } + + @Test + public void testQueryWorkingSetEx() { + Win32Exception we = null; + HANDLE selfHandle = Kernel32.INSTANCE.GetCurrentProcess(); + MEMORY_BASIC_INFORMATION mbi = new MEMORY_BASIC_INFORMATION(); + try { + SIZE_T bytesRead = Kernel32.INSTANCE.VirtualQueryEx(selfHandle, Pointer.NULL, mbi, new SIZE_T(mbi.size())); + assertNotEquals("Kernel should be able to read this Process' Bytes", bytesRead.intValue(), 0); + Psapi.PSAPI_WORKING_SET_EX_INFORMATION pswsi = new Psapi.PSAPI_WORKING_SET_EX_INFORMATION(); + pswsi.VirtualAddress = mbi.baseAddress; + if (!Psapi.INSTANCE.QueryWorkingSetEx(selfHandle, pswsi.VirtualAddress, pswsi.size())) { + throw new Win32Exception(Native.getLastError()); + } + assertTrue("Virual Attributes should not be null", pswsi.VirtualAttributes != null); + if (Psapi.INSTANCE.QueryWorkingSetEx(new HANDLE(), pswsi.VirtualAddress, pswsi.size())) { + throw new Win32Exception(Native.getLastError()); + } + assertFalse("This line should never be called", true); + } catch (Win32Exception e) { + we = e; + throw we; // re-throw to invoke finally block + } finally { + try { + Kernel32Util.closeHandle(selfHandle); + } catch (Win32Exception e) { + if (we == null) { + we = e; + } else { + we.addSuppressedReflected(e); + } + } + if (we != null) { + throw we; + } + } + } } + diff --git a/contrib/platform/test/com/sun/jna/platform/win32/PsapiUtilTest.java b/contrib/platform/test/com/sun/jna/platform/win32/PsapiUtilTest.java index d8578ad3d5..d4c52fd3a4 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/PsapiUtilTest.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/PsapiUtilTest.java @@ -25,6 +25,19 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotEquals; + + +import com.sun.jna.Pointer; +import com.sun.jna.Native; +import com.sun.jna.platform.win32.Psapi.MODULEINFO; +import com.sun.jna.platform.win32.Psapi.PERFORMANCE_INFORMATION; +import com.sun.jna.platform.win32.WinDef.DWORD; +import com.sun.jna.platform.win32.WinDef.HMODULE; +import com.sun.jna.platform.win32.WinDef.HWND; +import com.sun.jna.platform.win32.WinNT.HANDLE; +import com.sun.jna.platform.win32.WinNT.MEMORY_BASIC_INFORMATION; +import com.sun.jna.platform.win32.BaseTSD.SIZE_T; import org.junit.Test; @@ -50,4 +63,39 @@ public void enumProcesses() { } assertTrue("List should contain my pid", foundMyPid); } + + @Test + public void testPsapiWorkingSetExBlockUtils() { + Win32Exception we = null; + HANDLE selfHandle = Kernel32.INSTANCE.GetCurrentProcess(); + MEMORY_BASIC_INFORMATION mbi = new MEMORY_BASIC_INFORMATION(); + try { + SIZE_T bytesRead = Kernel32.INSTANCE.VirtualQueryEx(selfHandle, Pointer.NULL, mbi, new SIZE_T(mbi.size())); + assertNotEquals("Kernel should be able to read this Process' Bytes", bytesRead.intValue(), 0); + Psapi.PSAPI_WORKING_SET_EX_INFORMATION pswsi = new Psapi.PSAPI_WORKING_SET_EX_INFORMATION(); + pswsi.VirtualAddress = mbi.baseAddress; + if (!Psapi.INSTANCE.QueryWorkingSetEx(selfHandle, pswsi.VirtualAddress, pswsi.size())) { + throw new Win32Exception(Native.getLastError()); + } + assertTrue("Virual Attributes should not be null", pswsi.VirtualAttributes != null); + Psapi.PSAPI_WORKING_SET_EX_BLOCK psapiWorkingSetExBlock = pswsi.VirtualAttributes; + assertTrue("The JVM Page should be valid", PsapiUtil.isPsapiWorkingSetExBlockValid(psapiWorkingSetExBlock)); + } catch (Win32Exception e) { + we = e; + throw we; // re-throw to invoke finally block + } finally { + try { + Kernel32Util.closeHandle(selfHandle); + } catch (Win32Exception e) { + if (we == null) { + we = e; + } else { + we.addSuppressedReflected(e); + } + } + if (we != null) { + throw we; + } + } + } }