Implement a JNI Method in C++

Overview

  1. Call h-gen to generate the C++ header file for the Java class that contains the native method(s).
  2. Create a corresponding C++ source file and implement the native methods.
  3. Define the necessary global data to register the JNI methods.
  4. Add Process.cpp to the project for the DLL
  5. Implement JNI_OnLoad.

Details

  1. For this example, we will use the class com.moesol.bindings.platform_sdk.base_services.Process. We used h-gen to generate src/com/moesol/bindings/platform_sdk/base_services/Process.h. For more information about calling h-gen see its usage.

  2. Next, we created a new file called: src/com/moesol/bindings/platform_sdk/base_services/Process.cpp. Since we used VC++ with pre-compiled headers we first include "StdAfx.h". Our "StdAfx.h" includes jni.h, some standard C++ headers, j.h, and jcom.h. Note that, j.h is required for h-gen generated classes to work:

    #include <jni.h>
    
    #include <sstream>
    #include <memory>
    using namespace std;
    
    #include "com/moesol/bindings/j.h"
    #include "com/moesol/bindings/jcom.h"
    
    Next we include the generated "Process.h".

    Next, we enclose most of the remaining file in the necessary namespace:

    namespace com { namespace moesol { namespace bindings { namespace platform_sdk { namespace base_services { 
    ...
    } } } } }
    
    Next, copy the declaration for jni_GetEnvironmentVariable_impl.
    ::java::lang::String * Process::jni_GetEnvironmentVariable_impl( IRMIFL::jni_env &e, jclass self, ::java::lang::String * p0 );
    
    Remove the trailing ';' and add the member function implementation
    {
    ...
    }
    
    In this case we want jni_GetEnvironmentVariable_impl to call the PlatformSDK global function ::GetEnvironmentVariable. To do this we will need to convert the incoming Java String into a string usable by C/C++. h-gen generates the string parameter as ::java::lang::String *. Normally, we would need to include "java/lang/String.h" to get the h-gen C++ declaration for ::java::lang::String, but j.h already has this include. String has a helper method str that returns a std::string. So we use that to extract the string.
    std::string name = p0->str(e);
    
    The full listing for jni_GetEnvironmentVariable_impl is below. But, here we focus on getting the resulting char * returned back to java. String has another helper method that will create a new String * from a char *, fromCStr.
  3. return ::java::lang::String::fromCStr(e, value.get());
    
    And here's the full listing:
    ::java::lang::String * Process::jni_GetEnvironmentVariable_impl( IRMIFL::jni_env &e, jclass self, ::java::lang::String * p0 )
    {
        std::string name = p0->str(e);
    
        DWORD len;
        len = ::GetEnvironmentVariable(name.c_str(), NULL, 0);
        if (len == 0) {
            return NULL;
        }
    
        std::auto_ptr value(new char[len]);
        len = ::GetEnvironmentVariable(name.c_str(), value.get(), len);
        _ASSERT(len);
        return ::java::lang::String::fromCStr(e, value.get());
    }
    

  4. We have to make sure the JNI methods get registered with Java. These lines need to be added to Process.cpp outside of the namespace {}.

    #define include_Process_registration
    #include "Process.h"
    
    These lines define the global data needed for the registration. When include_Process_registration is defined Process.h only emits these lines:
    #ifdef include_Process_registration
    
    namespace com { namespace moesol { namespace bindings { namespace platform_sdk { namespace base_services { 
    
    JNINativeMethod Process_reg_methods[] = {
        { "jni_GetEnvironmentVariable",
          "(Ljava/lang/String;)Ljava/lang/String;",
          Process::jni_GetEnvironmentVariable_thunk },
        { "jni_SetEnvironmentVariable",
          "(Ljava/lang/String;Ljava/lang/String;)Z",
          Process::jni_SetEnvironmentVariable_thunk },
    
    };
    IRMIFL::jni_registration Process_registration("com/moesol/bindings/platform_sdk/base_services/Process",
        Process_reg_methods, sizeof(Process_reg_methods)/sizeof(Process_reg_methods[0]));
    
    } } } } } 
    
    #endif
    

  5. Add Process.cpp to the .dsp and .vcproj files via the corresponding IDE's

  6. IRMIFL::jni_registration maintains a global list of methods that need to be registered. This list is registered on the JNI_OnLoad method (see com_moesol_bindings.cpp):

    extern "C"
    JNIEXPORT jint JNICALL
    JNI_OnLoad(JavaVM *vm, void *reserved)
    {
        LOG("JNI_OnLoad %s\n", __FILE__);
        IRMIFL::java_vm::on_load(vm);
        return JNI_VERSION_1_4;
    }
    
    Note: the package name IRMIFL is a legacy package name that we expect to rename to ::com::moesol::bindings in the future (that's why it's listed as beta).

    On the Java side you will still need to load the native library. We've built Process.cpp into com_moesol_bindings.dll, so in Java we added these lines to Process.java.

    	static {
    		NativeLibraryLoader.loadLibrary("com_moesol_bindings");
    	}
    
    NativeLibraryLoader is a Java class that calls System.loadLibrary to load the DLL and if that fails it tries to find the location of the DLL via a system property that is the name of the library plus the suffix ".library.path". For example:
    com_moesol_bindings.library.path=C:/cygwin/home/robert/jSegue/sf/jSegue/src/lib/com_moesol_bindings/Debug
    


$Id: jni-in-c++.html 3769 2007-06-08 19:06:43Z hastings $