Skip to content

Commit

Permalink
Added GetEnvironmentStrings to 'com.sun.jna.platform.win32.Kernel32' - [
Browse files Browse the repository at this point in the history
  • Loading branch information
Lyor Goldstein committed Apr 28, 2015
1 parent 68db993 commit 04bfbc1
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 19 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1758,13 +1758,14 @@ public EnumKey(HKEY hKey, int dwIndex) {
* @return A environment block
*/
public static String getEnvironmentBlock(Map<String, String> environment) {
StringBuilder out = new StringBuilder();
StringBuilder out = new StringBuilder(environment.size() * 32 /* some guess about average name=value length*/);
for (Entry<String, String> 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();
}

/**
Expand Down
28 changes: 28 additions & 0 deletions contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java
Original file line number Diff line number Diff line change
Expand Up @@ -2210,6 +2210,34 @@ boolean GetDiskFreeSpaceEx(String lpDirectoryName,
*/
int GetEnvironmentVariable(String lpName, char[] lpBuffer, int nSize);

/**
* <P>Retrieves the environment variables for the current process. The
* block of variables format is as follows:</P></BR>
* <code>
* Var1=Value1\0
* Var2=Value2\0
* Var3=Value3\0
* ...
* VarN=ValueN\0\0
* </code>
* @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 <A HREF="https://msdn.microsoft.com/en-us/library/windows/desktop/ms683187(v=vs.85).aspx">GetEnvironmentStrings</A> 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 <A HREF="https://msdn.microsoft.com/en-us/library/windows/desktop/ms683151(v=vs.85).aspx">FreeEnvironmentStrings</A>
* documentation
*/
boolean FreeEnvironmentStrings(Pointer lpszEnvironmentBlock);

/**
* Returns the locale identifier for the system locale.
*
Expand Down
174 changes: 174 additions & 0 deletions contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String,String> 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
* <A HREF="https://msdn.microsoft.com/en-us/library/windows/desktop/ms683187(v=vs.85).aspx">GetEnvironmentStrings</A>
* function
* @param offset Offset within the block to parse the data
* @return A {@link Map} of the parsed <code>name=value</code> pairs.
* <B>Note:</B> 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<String,String> getEnvironmentVariables(Pointer lpszEnvironmentBlock, long offset) {
if (lpszEnvironmentBlock == null) {
return null;
}

Map<String,String> vars=new TreeMap<String,String>();
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
* <A HREF="https://msdn.microsoft.com/en-us/library/windows/desktop/ms683187(v=vs.85).aspx">GetEnvironmentStrings</A>
* 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 &quot;plain old&quot; {@code char}s
* @return A {@link String} containing the <code>name=value</code> 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
* <A HREF="https://msdn.microsoft.com/en-us/library/windows/desktop/ms683187(v=vs.85).aspx">GetEnvironmentStrings</A>
* 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 &quot;plain old&quot; {@code char}s
* @return The offset of the <U>first</U> {@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;
}
}
}

/**
* <P>Attempts to determine whether the data block uses {@code wchar_t}
* instead of &quot;plain old&quot; {@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:</P></BR>
* <UL>
* <LI>
* If the charset indicator is non-zero then it is assumed to be
* a &quot;plain old&quot; {@code char}s data block. <B>Note:</B>
* the assumption is that the environment variable <U>name</U> (at
* least) is ASCII.
* </LI>
*
* <LI>
* Otherwise (i.e., zero charset indicator), it is assumed to be
* a {@code wchar_t}
* </LI>
* </UL>
* <B>Note:</B> 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
* <A HREF="https://msdn.microsoft.com/en-us/library/windows/desktop/ms683187(v=vs.85).aspx">GetEnvironmentStrings</A>
* function
* @return {@code true} if the block contains {@code wchar_t} instead of
* &quot;plain old&quot; {@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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* 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.nio.ByteOrder;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;

import org.junit.Test;

import com.sun.jna.Native;
import com.sun.jna.Pointer;

/**
* @author lgoldstein
*/
public class EnvironmentVarsTest extends AbstractWin32TestSupport {
public EnvironmentVarsTest() {
super();
}

@Test
public void testKernelUtilGetEnvironmentStrings() {
assertCurrentEnvironmentVariableValues(Kernel32Util.getEnvironmentVariables());
}

@Test
public void testGetEnvironmentStringsFromPointer() {
Pointer lpszEnvironmentBlock=Kernel32.INSTANCE.GetEnvironmentStrings();
assertNotNull("No environment data block returned", lpszEnvironmentBlock);
try {
assertCurrentEnvironmentVariableValues(Kernel32Util.getEnvironmentVariables(lpszEnvironmentBlock, 0L));
} finally {
assertCallSucceeded("FreeEnvironmentStrings", Kernel32.INSTANCE.FreeEnvironmentStrings(lpszEnvironmentBlock));
}
}

private static void assertCurrentEnvironmentVariableValues(Map<String,String> vars) {
for (Map.Entry<String,String> 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));
}
}
}
15 changes: 0 additions & 15 deletions contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit 04bfbc1

Please sign in to comment.