Search code examples
javajava-native-interface

JNI UnsatisfiedLinkError when using native function


I have a method of performing some operation using some external libraries.
To do this, I defined an interface IMethod and an abstract class AMethod:

public interface IMethod {
    /**
     * Get a level by a certain method.
     *
     * @return The level obtained using this method.
     * @throws MethodUndefinedResultException If an error occurred in the method while obtaining the level.
     */
    Level getLevel() throws MethodUndefinedResultException;

    /**
     * Get method type.
     *
     * @return Enum method value.
     */
    Method getMethod();
}
abstract class ALibrary implements IMethod {
    private final Logger log = LoggerWrapper.getLogger();
    private final Library library;

    public ALibrary(Library library) {
        this.library = library;
    }

    @Override
    public Level getLevel() throws MethodUndefinedResultException {
        try {
            linkWithLibrary();
            String nativeCallResult = makeNativeCall();
            return parseNativeCallResult(nativeCallResult);
        } catch (LibraryResolveException | LibraryNativeCallException | LibraryParsingException e) {
            log.error(e.getMessage());
            throw new MethodUndefinedResultException(library.getMethod(), e);
        }
    }

    @Override
    public Method getMethod() {
        return library.getMethod();
    }

    /**
     * Link with library for the native method.
     *
     * @throws LibraryResolveException If library is not available.
     */
    private void linkWithLibrary() throws LibraryResolveException {
        try {
            System.loadLibrary(library.getLibraryName());
        } catch (Throwable e) {
            log.error(e.getMessage());
            throw new LibraryResolveException(library, e);
        }
    }

    /**
     * Make native call for the level.
     *
     * @return Result of the native call.
     * @throws LibraryNativeCallException If the result does not contain a level.
     */
    protected abstract String makeNativeCall() throws LibraryNativeCallException;

    /**
     * Parse library native call result to the level.
     *
     * @param nativeCallResult Result of the native call.
     * @return Parsed level value.
     * @throws LibraryParsingException If the result of the native method call could not be parsed.
     */
    protected abstract Level parseNativeCallResult(String nativeCallResult)
            throws LibraryParsingException;
}

After this I implemented test class TestMethod that extends AMethod and implements IMethod:

public class TestLibrary extends ALibrary implements IMethod {
    private static final Library LIBRARY = Library.Test;

    private final Logger log = LoggerWrapper.getLogger();

    public TestLibrary() {
        super(LIBRARY);
    }

    /**
     * A native method of the Test library that returns level.
     *
     * @return Level in numeric format.
     */
    private native String getlvl();

    @Override
    protected String makeNativeCall() throws LibraryNativeCallException {
        try {
            return new TestLibrary().getlvl();
        } catch (Throwable e) {
            log.error(e.getMessage());
            throw new LibraryNativeCallException(LIBRARY);
        }
    }

    @Override
    protected Level parseNativeCallResult(String nativeCallResult) throws LibraryParsingException {
        log.info(nativeCallResult);
        // TODO: Test with lib and remove stub
        throw new LibraryParsingException(LIBRARY, null);
    }
}

And use it as:

IMethod testMethod = new TestLibrary();
testMethod.getLevel();

But I got UnsatisfiedLinkError from the getlvl() in the makeNativeCall() method of the TestMethod class that cause LibraryNativeCallException.

But when I'm doing something like:

public class TestMethod {
    public native String getlvl();

    static {
        System.loadLibrary(Library.Test.getLibraryName());
    }

    public String getLevel() throws UnsatisfiedLinkError {
        return new TestMethod().getlvl();
    }
}

Everything works well. It seems to me that there is some kind of problem with an abstract class, but I can't figure out what it is. I would like to fix the new code and remove the old one.

Can you help please?


Solution

  • As I understood, if I change the java code, I need to change and recompile the C/C++ code.

    In the testlib.cpp I have this contract:

    JNIEXPORT jstring JNICALL Java_lvl_method_library_impl_TestMethod_getlvl(JNIEnv * env, jobject jobj)
    

    And when I use TestMethod everything works well, but with TestLibrary my code does not call getlvl().

    On the advice of @aled, I recompiled the library by changing the name of the function in it to:

    JNIEXPORT jstring JNICALL Java_lvl_method_library_impl_TestLibrary_getlvl(JNIEnv * env, jobject jobj)
    

    And the new code started working.
    Yaaaaas!