A native implementation of getenv

Implementing a replacement for the non-functional java.lang.System.getenv using JNI is quite straightforward. The constraints of what we're trying to do and how JNI works pretty much determine what the code will look like. The code here is released into the public domain with no licensing restrictions: do with it what you will.

First you need a class to contain the new method. Because I couldn't think of anything better I've called mine uk.co.tigress.System:

package uk.co.tigress ;

/**
 * The System class provides an implementation of
 * getenv that actually works, unlike the one in
 * java.lang.System.  The class cannot be instantiated.
 *
 * @author   R M Yorston
 * @version  1.0
 */
public class System {
    static {
        java.lang.System.loadLibrary("getenv") ;
    }

    private System() {
    }

    /**
     * Gets an environment variable. An environment variable is a
     * system-dependent external variable that has a string value.
     *
     * @param    name    name of the environment variable
     * @return    the value of the variable, or null if the
     * variable is not defined.
     */
    public static native String getenv(String name) ;
}
Once you've compiled that you can use javah to generate an include file, which basically just has a declaration of the required native function. Then you can write the native function. The desired functionality and the way JNI works means that you'll end up with something not unlike:
#include "uk_co_tigress_System.h"
#include 

JNIEXPORT jstring JNICALL Java_uk_co_tigress_System_getenv
  (JNIEnv *env, jclass c, jstring jname)
{
    const char *name, *value ;

    if ( jname == NULL ) {
        return NULL ;
    }

    name = (*env)->GetStringUTFChars(env, jname, (jboolean *)NULL) ;

    value = getenv(name) ;

    (*env)->ReleaseStringUTFChars(env, jname, name) ;

    return value ? (*env)->NewStringUTF(env, value) : NULL ;
}
If you use a Java 2 development environment you might also need these two functions:
EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
    /*
     * getenv.dll doesn't use any features of JNI beyond 1.1, so
     * returning JNI_VERSION_1_1 is fine.
     */
    return JNI_VERSION_1_1 ;
}

JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved)
{
    return ;
}
Compile that and put it into a shared library, put the Java class into a jar file and install them where the runtime can get at them. In Java 1.2 and above this is simply achieved using the Java Extension Mechanism for optional packages. See the documentation that comes with the J2SDK. In Java 1.1 you'll need to add the jar file to your classpath and put the shared library in the right place. This is left as an exercise for the reader. Java 1.0 has a different native interface. That is even more emphatically left as an exercise for the reader.

Using the method is straightforward. It's probably best to refer to the class using its full name rather than importing it, as this will only lead to confusion with java.lang.System.

public class test {
    public static void main(String args[]) {
        System.out.println(uk.co.tigress.System.getenv("HOME")) ;
    }
}
For your convenience pre-compiled versions of the jar file and native library for certain platforms are available for download from this site. To download, right-click on the link and choose 'Save link as...'. None of the files is bigger than 4 Kbytes.

To install the files go to the top directory of a JRE installation, or the jre directory of an SDK installation and unpack the archive file.

Source

The source code is available in a variety of formats.

Linux/i386

The Linux version of the native library was built using the Sun 1.3.1 SDK on Red Hat 6.2. It has been tested with the Blackdown, Sun and IBM 1.3.1 SDKs, the Sun 1.4.1 SDK and the Blackdown 1.1.8 SDK. The IBM 1.3.1 system seems to require the native library to be in the bin directory, so there's a separate archive for that. The files are the same, they just need to be in a different place.

Solaris/SPARC

The Solaris version of the native library was built using the Sun 1.3.1 SDK on Solaris 8.

AIX

The AIX version of the native library was compiled using the IBM 1.1.8 SDK on AIX 4.6. It hasn't been tested on 1.3, but it should work. Since I don't know where the native library should go I've guessed that it's like the IBM Linux system, bin. I'd appreciate knowing if that's correct.

Microsoft Windows

The Microsoft Windows version of the native library was compiled using the Sun 1.1.8 SDK on Windows 95. It was tested on the Sun 1.3.1 JRE on Windows 2000. The compiler used was LCC-Win32 by Jacob Navia.

Other platforms

It should be possible to build the native library on other platforms. (The jar file is platform-independent.) If anyone does make a native library for another platform I'd appreciate a copy so that I can add it to this page.

As an alternative approach, Pierre-Charles David has pointed out that on some systems it's possible to access the environment variables for a process through the /proc filesystem. This opens the way to a pure Java implementation of getenv on those systems.

If you have enjoyed this code you might also care to read my rant about why I think Sun are wrong to disable java.lang.System.getenv.


Ron Yorston
9th April 2003