diff --git a/CHANGES.md b/CHANGES.md index 8f808525bc..7e7bb25ae9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ Features * [#101](https://github.com/twall/jna/issues/101): Modify `com.sun.jna.platform.win32.Advapi32Util.registryGet*` API to support `KEY_WOW64` option - [@falldog] (https://github.com/falldog). * [#271](https://github.com/twall/jna/pull/271): Added `com.sun.jna.platform.win32.Gdi32.ChoosePixelFormat` and `SetPixelFormat` - [@kc7bfi](https://github.com/kc7bfi). * [#271](https://github.com/twall/jna/pull/271): Added `com.sun.jna.platform.win32.OpenGL32`, `OpenGL32Util` and `WinOpenGL` - [@kc7bfi](https://github.com/kc7bfi). +* [#250](https://github.com/twall/jna/pull/250): Added `com.sun.jna.platform.win32.Kernel32.GetPrivateProfileSection`, `GetPrivateProfileSectionNames` and `WritePrivateProfileSection` and corresponding `Kernel32Util` helpers - [@quipsy-karg](https://github.com/quipsy-karg). Bug Fixes --------- diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java index 156544e782..755d90ca48 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java @@ -1981,4 +1981,65 @@ DWORD GetPrivateProfileString(String lpAppName, String lpKeyName, */ boolean WritePrivateProfileString(String lpAppName, String lpKeyName, String lpString, String lpFileName); + + /** + * Retrieves all the keys and values for the specified section of an initialization file. + * + *

+ * Each string has the following format: {@code key=string}. + *

+ *

+ * This operation is atomic; no updates to the specified initialization file are allowed while the key name and value pairs for the section are being copied + * to the buffer pointed to by the {@code lpReturnedString} parameter. + *

+ * + * @param lpAppName + * The name of the section in the initialization file. + * @param lpReturnedString + * A buffer that receives the key name and value pairs associated with the named section. The buffer is filled with one or more {@code null} + * -terminated strings; the last string is followed by a second {@code null} character. + * @param nSize + * The size of the buffer pointed to by the {@code lpReturnedString} parameter, in characters. The maximum profile section size is 32,767 + * characters. + * @param lpFileName + * The name of the initialization file. If this parameter does not contain a full path to the file, the system searches for the file in the + * Windows directory. + * @return The number of characters copied to the buffer, not including the terminating null character. If the buffer is not large enough to contain all the + * key name and value pairs associated with the named section, the return value is equal to {@code nSize} minus two. + */ + DWORD GetPrivateProfileSection(String lpAppName, char[] lpReturnedString, DWORD nSize, String lpFileName); + + /** + * Retrieves the names of all sections in an initialization file. + *

+ * This operation is atomic; no updates to the initialization file are allowed while the section names are being copied to the buffer. + *

+ * + * @param lpszReturnBuffer + * A pointer to a buffer that receives the section names associated with the named file. The buffer is filled with one or more {@code null} + * -terminated strings; the last string is followed by a second {@code null} character. + * @param nSize + * size of the buffer pointed to by the {@code lpszReturnBuffer} parameter, in characters. + * @param lpFileName + * The name of the initialization file. If this parameter is {@code NULL}, the function searches the Win.ini file. If this parameter does not + * contain a full path to the file, the system searches for the file in the Windows directory. + * @return The return value specifies the number of characters copied to the specified buffer, not including the terminating {@code null} character. If the + * buffer is not large enough to contain all the section names associated with the specified initialization file, the return value is equal to the + * size specified by {@code nSize} minus two. + */ + DWORD GetPrivateProfileSectionNames(char[] lpszReturnBuffer, DWORD nSize, String lpFileName); + + /** + * @param lpAppName + * The name of the section in which data is written. This section name is typically the name of the calling application. + * @param lpString + * The new key names and associated values that are to be written to the named section. This string is limited to 65,535 bytes. Must be filled + * with zero or many {@code null}-terminated strings of the form {@code key=value}, appended by an additional {@code null} byte to terminate the + * list. + * @param lpFileName + * The name of the initialization file. If this parameter does not contain a full path for the file, the function searches the Windows directory + * for the file. If the file does not exist and lpFileName does not contain a full path, the function creates the file in the Windows directory. + * @return If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. + */ + boolean WritePrivateProfileSection(String lpAppName, String lpString, String lpFileName); } \ No newline at end of file diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java index 29ca05069b..aa0f889f69 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java @@ -362,4 +362,67 @@ public static final WinNT.SYSTEM_LOGICAL_PROCESSOR_INFORMATION[] getLogicalProce return (WinNT.SYSTEM_LOGICAL_PROCESSOR_INFORMATION[]) firstInformation .toArray(new WinNT.SYSTEM_LOGICAL_PROCESSOR_INFORMATION[returnedStructCount]); } + + /** + * Retrieves all the keys and values for the specified section of an initialization file. + * + *

+ * Each string has the following format: {@code key=string}. + *

+ *

+ * This operation is atomic; no updates to the specified initialization file are allowed while this method is executed. + *

+ * + * @param appName + * The name of the section in the initialization file. + * @param fileName + * The name of the initialization file. If this parameter does not contain a full path to the file, the system searches for the file in the + * Windows directory. + * @return The key name and value pairs associated with the named section. + */ + public static final String[] getPrivateProfileSection(final String appName, final String fileName) { + final char buffer[] = new char[32768]; // Maximum section size according to MSDN (http://msdn.microsoft.com/en-us/library/windows/desktop/ms724348(v=vs.85).aspx) + if (Kernel32.INSTANCE.GetPrivateProfileSection(appName, buffer, new DWORD(buffer.length), fileName).intValue() == 0) { + throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); + } + return new String(buffer).split("\0"); + } + + /** + * Retrieves the names of all sections in an initialization file. + *

+ * This operation is atomic; no updates to the initialization file are allowed while this method is executed. + *

+ * + * @param fileName + * The name of the initialization file. If this parameter is {@code NULL}, the function searches the Win.ini file. If this parameter does not + * contain a full path to the file, the system searches for the file in the Windows directory. + * @return the section names associated with the named file. + */ + public static final String[] getPrivateProfileSectionNames(final String fileName) { + final char buffer[] = new char[65536]; // Maximum INI file size according to MSDN (http://support.microsoft.com/kb/78346) + if (Kernel32.INSTANCE.GetPrivateProfileSectionNames(buffer, new DWORD(buffer.length), fileName).intValue() == 0) { + throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); + } + return new String(buffer).split("\0"); + } + + /** + * @param appName + * The name of the section in which data is written. This section name is typically the name of the calling application. + * @param strings + * The new key names and associated values that are to be written to the named section. Each entry must be of the form {@code key=value}. + * @param fileName + * The name of the initialization file. If this parameter does not contain a full path for the file, the function searches the Windows directory + * for the file. If the file does not exist and lpFileName does not contain a full path, the function creates the file in the Windows directory. + */ + public static final void writePrivateProfileSection(final String appName, final String[] strings, final String fileName) { + final StringBuilder buffer = new StringBuilder(); + for (final String string : strings) + buffer.append(string).append('\0'); + buffer.append('\0'); + if (! Kernel32.INSTANCE.WritePrivateProfileSection(appName, buffer.toString(), fileName)) { + throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); + } + } } diff --git a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java index 91d83de72d..81e913be86 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java @@ -531,4 +531,66 @@ public final void testWritePrivateProfileString() throws IOException { assertEquals(reader.readLine(), null); reader.close(); } + + public final void testGetPrivateProfileSection() throws IOException { + final File tmp = File.createTempFile("testGetPrivateProfileSection", ".ini"); + tmp.deleteOnExit(); + + final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(tmp))); + try { + writer.println("[X]"); + writer.println("A=1"); + writer.println("B=X"); + } finally { + writer.close(); + } + + final char[] buffer = new char[9]; + final DWORD len = Kernel32.INSTANCE.GetPrivateProfileSection("X", buffer, new DWORD(buffer.length), tmp.getCanonicalPath()); + + assertEquals(len.intValue(), 7); + assertEquals(new String(buffer), "A=1\0B=X\0\0"); + } + + public final void testGetPrivateProfileSectionNames() throws IOException { + final File tmp = File.createTempFile("testGetPrivateProfileSectionNames", ".ini"); + tmp.deleteOnExit(); + + final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(tmp))); + try { + writer.println("[S1]"); + writer.println("[S2]"); + } finally { + writer.close(); + } + + final char[] buffer = new char[7]; + final DWORD len = Kernel32.INSTANCE.GetPrivateProfileSectionNames(buffer, new DWORD(buffer.length), tmp.getCanonicalPath()); + assertEquals(len.intValue(), 5); + assertEquals(new String(buffer), "S1\0S2\0\0"); + } + + public final void testWritePrivateProfileSection() throws IOException { + final File tmp = File.createTempFile("testWritePrivateProfileSection", ".ini"); + tmp.deleteOnExit(); + + final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(tmp))); + try { + writer.println("[S1]"); + writer.println("A=1"); + writer.println("B=X"); + } finally { + writer.close(); + } + + final boolean result = Kernel32.INSTANCE.WritePrivateProfileSection("S1", "A=3\0E=Z\0\0", tmp.getCanonicalPath()); + assertTrue(result); + + final BufferedReader reader = new BufferedReader(new FileReader(tmp)); + assertEquals(reader.readLine(), "[S1]"); + assertTrue(reader.readLine().matches("A\\s*=\\s*3")); + assertTrue(reader.readLine().matches("E\\s*=\\s*Z")); + reader.close(); + } + } diff --git a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java index a31a947409..3bf889db81 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java @@ -180,4 +180,76 @@ public final void testWritePrivateProfileString() throws IOException { assertEquals(reader.readLine(), null); reader.close(); } + + public final void testGetPrivateProfileSection() throws IOException { + final File tmp = File.createTempFile("testGetPrivateProfileSection", ".ini"); + tmp.deleteOnExit(); + + final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(tmp))); + try { + writer.println("[X]"); + writer.println("A=1"); + writer.println("foo=bar"); + } finally { + writer.close(); + } + + final String[] lines = Kernel32Util.getPrivateProfileSection("X", tmp.getCanonicalPath()); + assertEquals(lines.length, 2); + assertEquals(lines[0], "A=1"); + assertEquals(lines[1], "foo=bar"); + } + + public final void testGetPrivateProfileSectionNames() throws IOException { + final File tmp = File.createTempFile("testGetPrivateProfileSectionNames", "ini"); + tmp.deleteOnExit(); + + final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(tmp))); + try { + writer.println("[S1]"); + writer.println("A=1"); + writer.println("B=X"); + writer.println("[S2]"); + writer.println("C=2"); + writer.println("D=Y"); + } finally { + writer.close(); + } + + String[] sectionNames = Kernel32Util.getPrivateProfileSectionNames(tmp.getCanonicalPath()); + assertEquals(sectionNames.length, 2); + assertEquals(sectionNames[0], "S1"); + assertEquals(sectionNames[1], "S2"); + } + + public final void testWritePrivateProfileSection() throws IOException { + final File tmp = File.createTempFile("testWritePrivateProfileSecion", "ini"); + tmp.deleteOnExit(); + + final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(tmp))); + try { + writer.println("[S1]"); + writer.println("A=1"); + writer.println("B=X"); + writer.println("[S2]"); + writer.println("C=2"); + writer.println("foo=bar"); + } finally { + writer.close(); + } + + Kernel32Util.writePrivateProfileSection("S1", new String[] { "A=3", "E=Z" }, tmp.getCanonicalPath()); + + final BufferedReader reader = new BufferedReader(new FileReader(tmp)); + try { + assertEquals(reader.readLine(), "[S1]"); + assertEquals(reader.readLine(), "A=3"); + assertEquals(reader.readLine(), "E=Z"); + assertEquals(reader.readLine(), "[S2]"); + assertEquals(reader.readLine(), "C=2"); + assertEquals(reader.readLine(), "foo=bar"); + } finally { + reader.close(); + } + } }