Search code examples
javadependenciesjava-native-interface

My JNI app cannot resolve dependencies of my shared app


I want to use 3rd party shared libs from my Java app via JNI.

(I am running on Manjaro Linux, using command line tools at the moment)

I can build and run this all fine if I do not create a dependency in myLib.so on the 3rd party libraries.
As soon as I create a dependency from myLib.so to 3rd party lib the Java invocation fails with

java: symbol lookup error: myLib.so: undefined symbol: acOpenSystem

I have tried setting the library path to the locations of the libraries, but this still fails.

java -Djava.library.path=.:$ARENA_LIBS:$ARENALIBS2 MyClass

I am sure that there is something simple that I am missing, but I cannot seem to divine the answer from the web. Please help. Even a link to a blindingly obvious resource that I failed to find would be welcome ;-)

Thanks

Edit: Here is the source. HelloWorld -> Foo -> Bar Bar.c

#include <stdio.h>
 
void bar(void)
{
    puts("\nHello, I am a shared library called BAR");
}

bar.h

#ifndef bar_h__
#define bar_h__
 
extern void bar(void);
 
#endif  // bar_h_

foo.c:

#include <jni.h>
#include <stdio.h>
#include "bar.h"
#include "HelloWorld.h"
 
// https://www.cprogramming.com/tutorial/shared-libraries-linux-gcc.html
 
void foo(void)
{
    puts("\nHello, I am a shared library");
    bar();
}

JNIEXPORT void JNICALL Java_HelloWorld_foo
  (JNIEnv *, jobject) {
    foo();
 }

foo.h:

#ifndef foo_h__
#define foo_h__
 
extern void foo(void);
 
#endif  // foo_h_

HelloWorld.h:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */

#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloWorld
 * Method:    foo
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloWorld_foo
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

HelloWorld.java:


import java.io.File;

class HelloWorld {

    public static void main(String[] args) {
        try {
            File file = new File("libFoo.so");
            String path = file.getAbsoluteFile().toPath().toString();
            System.out.println("path = "+ path);
            System.load(path);
            System.out.println("Hello from java app"); // Display the string.
            new HelloWorld().foo();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    private native void foo();
}

And my script file:

#!/bin/sh

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

# https://www.cprogramming.com/tutorial/shared-libraries-linux-gcc.html
# compile foo
gcc -c -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -Wall -Werror -fpic foo.c
# link object into shared library
gcc -shared -o libFoo.so foo.o

# compile bar
gcc -c -Wall -Werror -fpic bar.c
# link object into shared library
gcc -shared -o libBar.so bar.o


LD_LIBRARY_PATH=::$LD_LIBRARY_PATH
export LD_LIBRARY_PATH
gcc -L$SCRIPT_DIR -o test hello-world.c -lFoo -lBar

./test


LD_LIBRARY_PATH=$SCRIPT_DIR:$LD_LIBRARY_PATH
echo "LD_LIBRARY_PATH: $LD_LIBRARY_PATH"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH

javac HelloWorld.java
javac -h . HelloWorld.java
java -Djava.library.path=. HelloWorld

And for completeness, the source for the c executable that proves that the problem is not in the c code:

#include <stdio.h>      // C Standard IO Header
#include "foo.h"

int main(int argc, char *argv[]) {
    printf("Hello from executable");
    foo();
}

The executable in c runs fine, but the java application gives an error:

Hello from java app

Hello, I am a shared library
java: symbol lookup error: /mnt/data/singer/c/experiment/libFoo.so: undefined symbol: bar
java -version
openjdk version "17.0.5" 2022-10-18
OpenJDK Runtime Environment Temurin-17.0.5+8 (build 17.0.5+8)
OpenJDK 64-Bit Server VM Temurin-17.0.5+8 (build 17.0.5+8, mixed mode, sharing)

gcc --version
gcc (GCC) 12.2.1 20230201

Running on Manjaro 22.1


Solution

  • I think that I have now got it working. I will post my script for posterity:

    #!/bin/sh
    
    #  Example of building JNI app.  
    # Designed to run on linux using GCC and a suitable java compiler
    #
    # Uses an app 'HelloWorld' which invokes a shared library 'foo'.
    # Foo depends upon 'bar' which is bound to foo either as
    # - an object (bar.o)
    # - a static library (bar.a)
    # - a dynamic library (libBar.so)
    #
    # The java app is run once with each of the three forms of the shared library (libFoo.so) 
    
    # compile java and create jni header file
    javac HelloWorld.java
    javac -h . HelloWorld.java
    
    
    # compile bar
    gcc -c -Wall -Werror -fpic bar.c
    
    # link object into shared library
    gcc -shared -o libBar.so bar.o
    
    # link object into static library
    ar rcs bar.a bar.o
    
    # compile foo
    gcc -c -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -Wall -Werror -fpic foo.c
    
    # Set up the LD_LIBRARY_PATH to resolve dynamic libraries at run time
    # current working directory as a variable
    cwd=$(pwd)
    LD_LIBRARY_PATH=$cwd:$LD_LIBRARY_PATH
    # echo "LD_LIBRARY_PATH: $LD_LIBRARY_PATH"
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH
    
    echo
    echo
    echo
    echo "************** Using Bar as object *************"
    # link object into shared library an object library for Bar
    gcc -shared -o libFoo.so foo.o bar.o
    java -Djava.library.path=. HelloWorld
    
    echo
    echo
    echo
    echo "************** Using Bar as static library *************"
    # link object into shared library a static library for Bar
    gcc -shared -o libFoo.so foo.o -L . bar.a
    java -Djava.library.path=. HelloWorld
    
    echo
    echo
    echo
    echo "************** Using Bar as dynamic library *************"
    # link object into shared library using a dynamic library for Bar
    gcc -shared -o libFoo.so foo.o -L . -lBar
    java -Djava.library.path=. HelloWorld