user25710790
user25710790

Reputation: 31

How to access global variable via JNA interface?

I need to access a "native" C library from Java via Java Native Access (JNA). The idiomatic way to do this seems to be defining an interface that extends Library and that declares all the functions which I need to access. Then create a singleton instance, via Native.load() method:

public interface MyLibrary extends Library {
    MyLibrary INSTANCE = Native.load("mylib", MyLibrary.class);
    int some_function(int param);
}

This works fine with functions. But how do I access global variables via the JNA interface?

In the C header file, it is defined like:

extern const uint16_t SOME_GLOBAL_VARIABLE;

There is little to no information available on how to access global variables via the JNA interface. The closest thing I have found is the NativeLibrary.getGlobalVariableAddress() method. But, unfortunately, this method is in NativeLibrary class, not in the Library interface!

And there also seems to be no way to get a NativeLibrary instance when working with a custom interface (that is derived from the Library interface) and with the Native.load() method. I probably could use NativeLibrary.getInstance() instead, in order to get a NativeLibrary instance and to be able to use getGlobalVariableAddress(). But then, how do I apply my Library-interface to a NativeLibrary instance in order to be able to invoke the functions?

Loading the same library twice, once via Native.Load() in order to be able to call the functions via my interface, and once via NativeLibrary.getInstance() in order to be able to get the global variable address, seems like a very ugly workaround/hack, provided that it would work at all.

So, to make a long story short, is there any way in JNA to go from the Library interface to the underlying NativeLibrary instance, or vice versa?

Or, alternatively, how to declare a global variable directly in the JNA interface? 🤔

Upvotes: 2

Views: 135

Answers (2)

user25710790
user25710790

Reputation: 31

Based on the suggestion by Daniel Widdis, I was able to come up with a solution that is based on java.lang.reflect.Proxy in order to get the Handler instance out of the Library interface via the Proxy.getInvocationHandler() method, which then allows me to call getNativeLibrary() on the Handler object to obtain the required NativeLibrary instance:

import java.lang.reflect.Proxy;

import com.sun.jna.Library;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Native;
import com.sun.jna.Pointer;

public interface MyLibrary extends Library {

    static final MyLibrary INSTANCE = Native.load("mylib123", MyLibrary.class);

    static class NativeLibraryHolder {
        public static final NativeLibrary NATIVE_LIBRARY = ((Handler)Proxy.getInvocationHandler(MyLibrary.INSTANCE)).getNativeLibrary();
    }

    static class SOME_GLOBAL_VARIABLE {
        private static final Pointer ADDRESS = NativeLibraryHolder.NATIVE_LIBRARY.getGlobalVariableAddress("SOME_GLOBAL_VARIABLE");
        int get() { return ADDRESS.getInt(0L); }
        void set(final int newValue) { ADDRESS.setInt(0L, newValue); }
    }

    int some_function(int param);
}

Upvotes: 1

Daniel Widdis
Daniel Widdis

Reputation: 9131

As you've noted in your question, the NativeLibrary class does provide a getGlobalVariableAddress() method to which you can pass the native name of the variable as a string to retrieve a Pointer to it. (Knowing the number of bytes to read from that pointer is something you have to determine manually.)

You can directly assign the NativeLibrary to an instance and fetch the field like this:

NativeLibrary fooLib = NativeLibrary.getInstance("foo");

Pointer p = fooLib.getGlobalVariableAddress("SOME_GLOBAL_VARIABLE");
short s = p.getShort(0); // signed, convert to uint16

As for directly linking a Library instance to the NativeLibrary, it's not supported in the API. The Library class itself is an interface, so doesn't define any methods pointing to the native library. It does have an inner class Handler with a getNativeLibrary method on it which is used as part of the load() method in setting up the JDK Proxy class. It looks like you could probably use methods on either Proxy or InvocationHandler to try to recover the class via reflection, but that's left as an exercise for the reader.

Upvotes: 0

Related Questions