diff --git a/CHANGES.md b/CHANGES.md index da360d68d1..c7f497d7bb 100755 --- a/CHANGES.md +++ b/CHANGES.md @@ -44,6 +44,7 @@ Features * [#428](https://github.com/twall/jna/pull/428): Added Wincon.h related functions and definitions to `com.sun.jna.platform.win32.Kernel32` - [@lgoldstein](https://github.com/lgoldstein). * [#431](https://github.com/twall/jna/pull/431): Added named pipe API support to `com.sun.jna.platform.win32.Kernel32` - [@lgoldstein](https://github.com/lgoldstein). * [#432](https://github.com/twall/jna/pull/432): Added SetLocalTime definition to 'com.sun.jna.platform.win32.Kernel32' - [@lgoldstein](https://github.com/lgoldstein). +* [#434](https://github.com/twall/jna/pull/434): Added GetEnvironmentStrings to 'com.sun.jna.platform.win32.Kernel32' - [@lgoldstein](https://github.com/lgoldstein). Bug Fixes --------- diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Advapi32Util.java b/contrib/platform/src/com/sun/jna/platform/win32/Advapi32Util.java index 6b08e38f54..a8c45526ef 100755 --- a/contrib/platform/src/com/sun/jna/platform/win32/Advapi32Util.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Advapi32Util.java @@ -1758,13 +1758,14 @@ public EnumKey(HKEY hKey, int dwIndex) { * @return A environment block */ public static String getEnvironmentBlock(Map environment) { - StringBuilder out = new StringBuilder(); + StringBuilder out = new StringBuilder(environment.size() * 32 /* some guess about average name=value length*/); for (Entry entry : environment.entrySet()) { - if (entry.getValue() != null) { - out.append(entry.getKey() + "=" + entry.getValue() + "\0"); + String key=entry.getKey(), value=entry.getValue(); + if (value != null) { + out.append(key).append("=").append(value).append('\0'); } } - return out.toString() + "\0"; + return out.append('\0').toString(); } /** 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 5a304c8bfa..3b72d6fbe3 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java @@ -2210,6 +2210,34 @@ boolean GetDiskFreeSpaceEx(String lpDirectoryName, */ int GetEnvironmentVariable(String lpName, char[] lpBuffer, int nSize); + /** + *

Retrieves the environment variables for the current process. The + * block of variables format is as follows:


+ * + * Var1=Value1\0 + * Var2=Value2\0 + * Var3=Value3\0 + * ... + * VarN=ValueN\0\0 + * + * @return If the function succeeds, the return value is a {@link Pointer}. + * to the environment block of the current process. If fails, then + * {@code null} is returned. When the data is no longer needed the memory + * block must be released using {@link #FreeEnvironmentStrings(Pointer)} + * @see GetEnvironmentStrings documentation + */ + Pointer GetEnvironmentStrings(); + + /** + * @param lpszEnvironmentBlock A pointer to a block of environment strings + * obtained by calling the {@link #GetEnvironmentStrings()} function + * @return {@code true} if successful, {@code false} otherwise. + * To get extended error information, call {@link #GetLastError()}. + * @see FreeEnvironmentStrings + * documentation + */ + boolean FreeEnvironmentStrings(Pointer lpszEnvironmentBlock); + /** * Returns the locale identifier for the system locale. * 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 2e396152d4..576a61f309 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java @@ -14,12 +14,16 @@ import java.io.File; import java.io.FileNotFoundException; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.TreeMap; import com.sun.jna.LastErrorException; import com.sun.jna.Memory; import com.sun.jna.Native; +import com.sun.jna.Pointer; import com.sun.jna.platform.win32.WinNT.HANDLE; import com.sun.jna.platform.win32.WinNT.HANDLEByReference; import com.sun.jna.platform.win32.WinNT.HRESULT; @@ -252,6 +256,176 @@ public static String getEnvironmentVariable(String name) { return Native.toString(buffer); } + /** + * Uses the {@link Kernel32#GetEnvironmentStrings()} to retrieve and + * parse the current process environment + * @return The current process environment as a {@link Map}. + * @throws LastErrorException if failed to get or free the environment + * data block + * @see #getEnvironmentVariables(Pointer, long) + */ + public static Map getEnvironmentVariables() { + Pointer lpszEnvironmentBlock=Kernel32.INSTANCE.GetEnvironmentStrings(); + if (lpszEnvironmentBlock == null) { + throw new LastErrorException(Kernel32.INSTANCE.GetLastError()); + } + + try { + return getEnvironmentVariables(lpszEnvironmentBlock, 0L); + } finally { + if (!Kernel32.INSTANCE.FreeEnvironmentStrings(lpszEnvironmentBlock)) { + throw new LastErrorException(Kernel32.INSTANCE.GetLastError()); + } + } + } + + /** + * @param lpszEnvironmentBlock The environment block as received from the + * GetEnvironmentStrings + * function + * @param offset Offset within the block to parse the data + * @return A {@link Map} of the parsed name=value pairs. + * Note: if the environment block is {@code null} then {@code null} + * is returned instead of an empty map since we want to distinguish + * between the case that the data block is {@code null} and when there are + * no environment variables (as unlikely as it may be) + */ + public static Map getEnvironmentVariables(Pointer lpszEnvironmentBlock, long offset) { + if (lpszEnvironmentBlock == null) { + return null; + } + + Map vars=new TreeMap(); + boolean asWideChars=isWideCharEnvironmentStringBlock(lpszEnvironmentBlock, offset); + long stepFactor=asWideChars ? 2L : 1L; + for (long curOffset=offset; ; ) { + String nvp=readEnvironmentStringBlockEntry(lpszEnvironmentBlock, curOffset, asWideChars); + int len=nvp.length(); + if (len == 0) { // found the ending '\0' + break; + } + + int pos=nvp.indexOf('='); + if (pos < 0) { + throw new IllegalArgumentException("Missing variable value separator in " + nvp); + } + + String name=nvp.substring(0, pos), value=nvp.substring(pos + 1); + vars.put(name, value); + + curOffset += (len + 1 /* skip the ending '\0' */) * stepFactor; + } + + return vars; + } + + /** + * @param lpszEnvironmentBlock The environment block as received from the + * GetEnvironmentStrings + * function + * @param offset Offset within the block to look for the entry + * @param asWideChars If {@code true} then the block contains {@code wchar_t} + * instead of "plain old" {@code char}s + * @return A {@link String} containing the name=value pair or + * empty if reached end of block + * @see #isWideCharEnvironmentStringBlock(Pointer) + * @see #findEnvironmentStringBlockEntryEnd(Pointer, long, boolean) + */ + public static String readEnvironmentStringBlockEntry(Pointer lpszEnvironmentBlock, long offset, boolean asWideChars) { + long endOffset=findEnvironmentStringBlockEntryEnd(lpszEnvironmentBlock, offset, asWideChars); + int dataLen=(int) (endOffset - offset); + if (dataLen == 0) { + return ""; + } + + int charsLen=asWideChars ? (dataLen / 2) : dataLen; + char[] chars=new char[charsLen]; + long curOffset=offset, stepSize=asWideChars ? 2L : 1L; + ByteOrder byteOrder=ByteOrder.nativeOrder(); + for (int index=0; index < chars.length; index++, curOffset += stepSize) { + byte b=lpszEnvironmentBlock.getByte(curOffset); + if (asWideChars) { + byte x=lpszEnvironmentBlock.getByte(curOffset + 1L); + if (ByteOrder.LITTLE_ENDIAN.equals(byteOrder)) { + chars[index] = (char) (((x << Byte.SIZE) & 0xFF00) | (b & 0x00FF)); + } else { // unlikely, but handle it + chars[index] = (char) (((b << Byte.SIZE) & 0xFF00) | (x & 0x00FF)); + } + } else { + chars[index] = (char) (b & 0x00FF); + } + } + + return new String(chars); + } + + /** + * @param lpszEnvironmentBlock The environment block as received from the + * GetEnvironmentStrings + * function + * @param offset Offset within the block to look for the entry + * @param asWideChars If {@code true} then the block contains {@code wchar_t} + * instead of "plain old" {@code char}s + * @return The offset of the first {@code '\0'} in the data block + * starting at the specified offset - can be the start offset itself if empty + * string. + * @see #isWideCharEnvironmentStringBlock(Pointer) + */ + public static long findEnvironmentStringBlockEntryEnd(Pointer lpszEnvironmentBlock, long offset, boolean asWideChars) { + for (long curOffset=offset, stepSize=asWideChars ? 2L : 1L; ; curOffset += stepSize) { + byte b=lpszEnvironmentBlock.getByte(curOffset); + if (b == 0) { + return curOffset; + } + } + } + + /** + *

Attempts to determine whether the data block uses {@code wchar_t} + * instead of "plain old" {@code char}s. It does that by reading + * 2 bytes from the specified offset - the character value and its charset + * indicator - and examining them as follows:


+ *
    + *
  • + * If the charset indicator is non-zero then it is assumed to be + * a "plain old" {@code char}s data block. Note: + * the assumption is that the environment variable name (at + * least) is ASCII. + *
  • + * + *
  • + * Otherwise (i.e., zero charset indicator), it is assumed to be + * a {@code wchar_t} + *
  • + *
+ * Note: the code takes into account the {@link ByteOrder} even though + * only {@link ByteOrder#LITTLE_ENDIAN} is the likely one + * @param lpszEnvironmentBlock The environment block as received from the + * GetEnvironmentStrings + * function + * @return {@code true} if the block contains {@code wchar_t} instead of + * "plain old" {@code char}s + */ + public static boolean isWideCharEnvironmentStringBlock(Pointer lpszEnvironmentBlock, long offset) { + byte b0=lpszEnvironmentBlock.getByte(offset); + byte b1=lpszEnvironmentBlock.getByte(offset + 1L); + ByteOrder byteOrder=ByteOrder.nativeOrder(); + if (ByteOrder.LITTLE_ENDIAN.equals(byteOrder)) { + return isWideCharEnvironmentStringBlock(b1); + } else { + return isWideCharEnvironmentStringBlock(b0); + } + } + + private static boolean isWideCharEnvironmentStringBlock(byte charsetIndicator) { + // assume wchar_t for environment variables represents ASCII letters + if (charsetIndicator != 0) { + return false; + } else { + return true; + } + } + /** * Retrieves an integer associated with a key in the specified section of an * initialization file. diff --git a/contrib/platform/test/com/sun/jna/platform/win32/WinconTest.java b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32ConsoleTest.java similarity index 98% rename from contrib/platform/test/com/sun/jna/platform/win32/WinconTest.java rename to contrib/platform/test/com/sun/jna/platform/win32/Kernel32ConsoleTest.java index be81b77bce..5a84224b63 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/WinconTest.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32ConsoleTest.java @@ -24,7 +24,7 @@ /** * @author lgoldstein */ -public class WinconTest extends AbstractWin32TestSupport { +public class Kernel32ConsoleTest extends AbstractWin32TestSupport { private static final Wincon INSTANCE=Kernel32.INSTANCE; @Test diff --git a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32EnvironmentVarsTest.java b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32EnvironmentVarsTest.java new file mode 100644 index 0000000000..8decae0f24 --- /dev/null +++ b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32EnvironmentVarsTest.java @@ -0,0 +1,73 @@ +/* Copyright (c) 2007 Timothy Wall, All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +package com.sun.jna.platform.win32; + +import java.util.Map; +import java.util.Random; + +import org.junit.Test; + +import com.sun.jna.Native; + +/** + * @author lgoldstein + */ +public class Kernel32EnvironmentVarsTest extends AbstractWin32TestSupport { + public Kernel32EnvironmentVarsTest() { + super(); + } + + @Test + public void testKernelUtilGetEnvironmentStrings() { + Map vars=Kernel32Util.getEnvironmentVariables(); + for (Map.Entry entry : vars.entrySet()) { + String name=entry.getKey(), expected=entry.getValue(); + char[] data=new char[expected.length() + 1]; + int size=Kernel32.INSTANCE.GetEnvironmentVariable(name, data, data.length); + assertEquals("Mismatched retrieved length for " + name, data.length - 1 /* w/o the '\0' */, size); + + String actual=Native.toString(data); + assertEquals("Mismatched retrieved value for " + name, expected, actual); + } + } + + @Test + public void testKernelSetAndGetEnvironmentVariable() { + String name=getCurrentTestName(), expected="42"; + assertCallSucceeded("SetEnvironmentVariable", Kernel32.INSTANCE.SetEnvironmentVariable(name, expected)); + + try { + int size = Kernel32.INSTANCE.GetEnvironmentVariable(name, null, 0); + assertEquals("Mismatched required buffer size", expected.length() + 1, size); + + char[] data = new char[size]; + assertEquals("Mismatched retrieved variable data length", size - 1, Kernel32.INSTANCE.GetEnvironmentVariable(name, data, size)); + + String actual=Native.toString(data); + assertEquals("Mismatched retrieved variable value", expected, actual); + } finally { + assertCallSucceeded("Clean up variable", Kernel32.INSTANCE.SetEnvironmentVariable(name, null)); + } + } + + @Test + public void testKernelUtilGetEnvironmentVariable() { + String name=getCurrentTestName(), expected=Integer.toString(new Random().nextInt()); + assertCallSucceeded("SetEnvironmentVariable", Kernel32.INSTANCE.SetEnvironmentVariable(name, expected)); + try { + assertEquals("Mismatched retrieved value", expected, Kernel32Util.getEnvironmentVariable(name)); + } finally { + assertCallSucceeded("Clean up variable", Kernel32.INSTANCE.SetEnvironmentVariable(name, null)); + } + } +} diff --git a/contrib/platform/test/com/sun/jna/platform/win32/NamedPipeTest.java b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32NamedPipeTest.java similarity index 98% rename from contrib/platform/test/com/sun/jna/platform/win32/NamedPipeTest.java rename to contrib/platform/test/com/sun/jna/platform/win32/Kernel32NamedPipeTest.java index 227991d759..8b3c51194f 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/NamedPipeTest.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32NamedPipeTest.java @@ -29,8 +29,8 @@ /** * @author lgoldstein */ -public class NamedPipeTest extends AbstractWin32TestSupport { - public NamedPipeTest() { +public class Kernel32NamedPipeTest extends AbstractWin32TestSupport { + public Kernel32NamedPipeTest() { super(); } 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 d881bf7d7f..61562de9cf 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java @@ -539,21 +539,6 @@ public void testCreateProcess() { assertTrue(processInformation.dwProcessId.longValue() > 0); } - public void testGetEnvironmentVariable() { - assertTrue(Kernel32.INSTANCE.SetEnvironmentVariable("jna-getenvironment-test", "42")); - int size = Kernel32.INSTANCE.GetEnvironmentVariable("jna-getenvironment-test", null, 0); - assertTrue(size == 3); - char[] data = new char[size]; - assertEquals(size - 1, Kernel32.INSTANCE.GetEnvironmentVariable("jna-getenvironment-test", data, size)); - assertEquals(size - 1, Native.toString(data).length()); - } - - public void testSetEnvironmentVariable() { - int value = new Random().nextInt(); - Kernel32.INSTANCE.SetEnvironmentVariable("jna-setenvironment-test", Integer.toString(value)); - assertEquals(Integer.toString(value), Kernel32Util.getEnvironmentVariable("jna-setenvironment-test")); - } - public void testGetSetFileTime() throws IOException { File tmp = File.createTempFile("testGetSetFileTime", "jna"); tmp.deleteOnExit();