Joshua Schroijen
Joshua Schroijen

Reputation: 436

Can't get simplest JNI eample to run on Windows 10

For a project I'm doing I will soon have to use the Java Native interface. I tried to get started with it by reading this tutorial on it over on the IBM DeveloperWorks site.

Basically, it told me to write this class:

public class Sample1 {
  public native int intMethod( int n );
  public native boolean booleanMethod( boolean bool );
  public native String stringMethod( String text );
  public native int intArrayMethod( int[] intArray );

  public static void main( String[] args ){
    try{
      System.loadLibrary( "Sample132" );
    } catch( UnsatisfiedLinkError e ){
        System.out.println( "Linking failed with message\n" + e.getMessage() );
    }

    Sample1 sample = new Sample1();
    int square = 0, sum = 0;
    boolean bool = false;
    String text = "";

    try{
      square = sample.intMethod( 5 );
      bool   = sample.booleanMethod( true );
      text   = sample.stringMethod( "JAVA" );
      sum    = 
        sample.intArrayMethod(
          new int[]{ 1, 1, 2, 3, 5, 8, 13 } );
    } catch( UnsatisfiedLinkError e ){
      System.out.println( "Calling native method failed with\n" + e.getMessage() );
      System.exit( 1 );
    }

    System.out.println( "intMethod: " + square );
    System.out.println( "booleanMethod: " + bool );
    System.out.println( "stringMethod: " + text );
    System.out.println( "intArrayMethod: " + sum );
  }
}

This class just contains some simple native methods. In its main method, it tries to load the DLL containing the implementations of these. I generated the appropriate header file (Sample1.h) for the class with javah. My C++ file containing the implementation looks as follows:

#include "Sample1.h"

#include <string.h>

extern "C" {
JNIEXPORT jint JNICALL Java_Sample1_intMethod
  ( JNIEnv * env, 
    jobject obj, 
    jint num ){
      return( num * num );
    }

JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
  ( JNIEnv * env, 
    jobject obj, 
    jboolean boolean ){
    return( ! boolean );
  }

JNIEXPORT jstring JNICALL Java_Sample1_stringMethod
  ( JNIEnv * env, 
    jobject obj,
    jstring string ){
    const char * str = env->GetStringUTFChars( string, 0 );
    char cap[ 128 ];
    strcpy( cap, str );
    env->ReleaseStringUTFChars( string, 0 );
    return( env->NewStringUTF( strupr( cap ) ) );
  }

JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
  ( JNIEnv * env,
    jobject obj,
    jintArray array ){
    int i, sum = 0;
    jsize len = env->GetArrayLength( array );
    jint * body = env->GetIntArrayElements( array, 0 );
    for( i=0; i < len; i++ ){
      sum += body[ i ];
    }
    env->ReleaseIntArrayElements(array, body, 0);
    return( sum );
  }
}

The problem I have now is that as soon as the Sample1 class' main method hits the call to the native intMethod on its instance, it crashes with a UnsatisfiedLinkError runtime exception. Now, the DLL is just residing in the same directory as the class and I have verified that the main function actually loads the library successfully: it doesn't throw an UnsatisfiedLinkError exception in the System.loadLibrary() call. But for completeness sake, I was wondering whether the way I compile the DLL with MinGW's G++ has anything to do with the call failing:

g++ -I"C:\Program Files (x86)\Java\jdk1.8.0_172\include" -I"C:\Program Files (x86)\Java\jdk1.8.0_172\include\win32" -fPIC --shared -m32 -o Sample132.dll Sample1.cpp

I should also note that I'm running the class in the 32-bit JVM and it's a 32 bit DLL!

Finally, with MS VS's dumpbin tool, I got this output:

Dump of file Sample132.dll

File Type: DLL

  Section contains the following exports for Sample132.dll

    00000000 characteristics
    5B4F79B4 time date stamp Wed Jul 18 19:32:36 2018
        0.00 version
           1 ordinal base
           4 number of functions
           4 number of names

    ordinal hint RVA      name

          1    0 000012DE Java_Sample1_booleanMethod@12
          2    1 00001368 Java_Sample1_intArrayMethod@12
          3    2 000012D0 Java_Sample1_intMethod@12
          4    3 000012F5 Java_Sample1_stringMethod@12

  Summary

        1000 .CRT
        1000 .bss
        1000 .data
        1000 .debug_abbrev
        1000 .debug_aranges
        2000 .debug_info
        1000 .debug_line
        1000 .edata
        1000 .eh_frame
        1000 .idata
        1000 .rdata
        1000 .reloc
        1000 .text
        1000 .tls

So it doesn't seem like the symbols were mangled and thus the JVM wouldn't be able to find the correct symbols inside the loaded DLL.

Is there anyone who knows why my program fails with a UnsatisfieLinkError when it calls the native methods? Some help would be greatly appreciated!

Thanks in advance, Joshua

EDIT: this is the C++ file that still won't compile

#include "Sample1.h"

#include <string.h>

JNIEXPORT jint JNICALL Java_Sample1_intMethod
  ( JNIEnv * env, 
    jobject obj, 
    jint num ){
      return( num * num );
    }

JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
  ( JNIEnv * env, 
    jobject obj, 
    jboolean boolean ){
    return( ! boolean );
  }

JNIEXPORT jstring JNICALL Java_Sample1_stringMethod
  ( JNIEnv * env, 
    jobject obj,
    jstring string ){
    const char * str = env->GetStringUTFChars( string, 0 );
    char cap[ 128 ];
    strcpy( cap, str );
    env->ReleaseStringUTFChars( string, 0 );
    return( env->NewStringUTF( strupr( cap ) ) );
  }

JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
  ( JNIEnv * env,
    jobject obj,
    jintArray array ){
    int i, sum = 0;
    jsize len = env->GetArrayLength( array );
    jint * body = env->GetIntArrayElements( array, 0 );
    for( i=0; i < len; i++ ){
      sum += body[ i ];
    }
    env->ReleaseIntArrayElements(array, body, 0);
    return( sum );
  }

void main(){}

Upvotes: 3

Views: 920

Answers (1)

Joshua Schroijen
Joshua Schroijen

Reputation: 436

OK, I solved my issue. The issue was the way I built my DLL.

First, you should pass the option kill-at to the MinGW linker (ld). When you don't pass this option, your symbols will still get mangled in a subtle way despite the extern "C" declaration: at the end of the function name the compiler will append @<number of bytes the function's parameters take up>. This looks like a minor detail, but you DON'T want this. Java ( or at least the standard Oracle JVM ) DOES NOT expect this suffix!

Secondly, g++ actually allows you to define _JNI_IMPLEMENTATION. This built-in identifier will signal to the compiler that the shared library it's going to build is meant to be used for the JNI and to configure itself accordingly.

So, finally, to compile the DLL property you DON'T have to change anything to the original javah-generated header file. Just execute this command:

g++ -I"C:\Program Files (x86)\Java\jdk1.8.0_172\include" -I"C:\Program Files (x86)\Java\jdk1.8.0_172\include\win32" -D_JNI_IMPLEMENTATION -Wl,--kill-at --shared -o Sample132.dll Sample1.cpp

( Of course, change the include paths to the include paths of your Java SDK installation )

This page contains lots of interesting information about building JNI compatible DLL's with MinGW

I hope this will also help someone in the future. Thanks a lot for your help, Alerra! :)

Upvotes: 3

Related Questions