java高階用法之:JNA中的Function

flydean發表於2022-05-06

簡介

在JNA中,為了和native的function進行對映,我們可以有兩種mapping方式,第一種是interface mapping,第二種是direct mapping。雖然兩種方式不同,但是在具體的方法對映中,我們都需要在JAVA中定義一個和native方法進行對映的方法。

而這個JAVA中的對映在JNA中就是一個function。通過或者function物件,我們可以實現一些非常強大的功能,一起看看吧。

function的定義

先來看下JNA中Function的定義:

public class Function extends Pointer

可以看到Function實際上是一個Pointer,指向的是native function的指標。

那麼怎麼得到一個Function的例項呢?

我們知道JNA的流程是先進行Library的對映,然後再對Library中的Function進行對映。所以很自然的我們應該可以從Library中得到Function。

我們看一下根據Library name得到function例項的方法定義:

public static Function getFunction(String libraryName, String functionName, int callFlags, String encoding) {
        return NativeLibrary.getInstance(libraryName).getFunction(functionName, callFlags, encoding);
    }

這個方法可以接受4個引數,前面兩個引數大家應該很熟悉了,第三個引數是callFlags,表示的是函式呼叫的flags,Function定義了三個callFlags:

    public static final int C_CONVENTION = 0;

    public static final int ALT_CONVENTION = 0x3F;

    public static final int THROW_LAST_ERROR = 0x40;

其中C_CONVENTION表示的是C語言型別的方法呼叫。

ALT_CONVENTION表示的其他的呼叫方式。

THROW_LAST_ERROR表示如果native函式的返回值是非零值的時候,將會丟擲一個LastErrorException。

最後一個引數是encoding,表示的是字串的編碼方式,實際上指的是 Java unicode和native (const char*) strings 的轉換方式。

除了根據Library name獲取Function之外,JNA還提供了根據Pointer來獲取Function的方法。

    public static Function getFunction(Pointer p, int callFlags, String encoding) {
        return new Function(p, callFlags, encoding);
    }

這裡的Pointer指的是一個執行native方法的指標,因為Function本身就是繼承自Pointer。所以跟Pointer來建立Function的本質就是在Pointer的基礎上新增了一些Function特有的屬性。

有了Function的定義,更為重要的是如何通過Function來呼叫對應的方法。跟反射很類似,Function中也有一個invoke方法,通過呼叫invoke,我們就可以執行對應的Function的功能。

Function中的invoke方法有兩種,一種是通用的返回物件Object,一種是帶有返回值的invoke方法,比如invokeString,invokePointer,invokeInt等。

Function的實際應用

Function的實際使用和JAVA中的反射有點類似,其工作流程是首先獲得要載入的NativeLibrary,然後從該NativeLibrary中找到要呼叫的Function,最後invoke該Function的某些方法。

C語言中的printf應該是大家最熟悉的native方法了。我們看一下如何使用Function來呼叫這個方法吧:

        NativeLibrary lib = NativeLibrary.getInstance(Platform.C_LIBRARY_NAME);
        Function f = lib.getFunction("printf");
        try {
            f.invoke(getClass(), new Object[] { getName() });
            fail("Invalid return types should throw an exception");
        } catch(IllegalArgumentException e) {
            // expected
        }

可以看到呼叫的流程非常簡潔。如果是用interface Mapping或者direct Mapping的形式,我們還需要自定義一個interface或者class,並且在其中定義一個相應的java方法對映。但是如果使用Function的話,這些都不需要了。我們直接可以從NativeLibrary中拿到對應的函式,並最終呼叫其中的方法。

C語言中的printf的原型如下:

# include <stdio.h>
int printf(const char *format, ...);

printf帶有返回值的,如果要輸出這個返回值,則可以呼叫Function中的invokeInt命令。我們再來看一個有返回值的呼叫例子:

NativeLibrary lib = NativeLibrary.getInstance(Platform.C_LIBRARY_NAME);
        Function f = lib.getFunction("printf");
        Object[] args = new Object[Function.MAX_NARGS+1];
        // Make sure we don't break 'printf'
        args[0] = getName();
        try {
            f.invokeInt(args);
            fail("Arguments should be limited to " + Function.MAX_NARGS);
        } catch(UnsupportedOperationException e) {
            // expected
        }

總結

使用Function可以減少手寫Mapping的工作,在某些情況下是非常好用的,但是Function的invoke支援TypeMapper,並不支援FunctionMapper,所以在使用中還是有一些限制。

大家可以在使用過程中酌情考慮。

本文已收錄於 http://www.flydean.com/07-jna-function/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!

相關文章