ホームページ  >  記事  >  Java  >  高度な Java 使用法で JNA のコールバック問題を解決する方法

高度な Java 使用法で JNA のコールバック問題を解決する方法

PHPz
PHPz転載
2023-05-05 11:37:061415ブラウズ

    はじめに

    コールバックとは何ですか?簡単に言うと、コールバックはコールバック通知であり、メソッドが完了したりイベントがトリガーされた後に特定のタスクに通知する必要がある場合は、コールバックを使用する必要があります。

    コールバックを最もよく目にする言語は JavaScript ですが、基本的に JavaScript ではコールバックがあらゆる場所に存在します。コールバックによるコールバック地獄の問題を解決するために、ES6 ではこの問題を解決するために特別に Promise が導入されました。

    ネイティブ メソッドとの対話を容易にするために、JNA はコールバック用の Callback も提供します。 JNA のコールバックの本質は、ネイティブ関数へのポインタです。このポインタを通じて、ネイティブ関数のメソッドを呼び出すことができます。見てみましょう。

    JNA でのコールバック

    最初に、JNA でのコールバックの定義を確認します。

    public interface Callback {
        interface UncaughtExceptionHandler {
            void uncaughtException(Callback c, Throwable e);
        }
        String METHOD_NAME = "callback";
    
        List<String> FORBIDDEN_NAMES = Collections.unmodifiableList(
                Arrays.asList("hashCode", "equals", "toString"));
    }

    すべてのコールバック メソッドは、この Callback インターフェイスを実装する必要があります。 Callback インターフェイスは非常にシンプルで、1 つのインターフェイスと 2 つのプロパティを定義します。

    まずこのインターフェイスを見てみましょう。インターフェイス名は UncaughtExceptionHandler で、その中には uncaughtException メソッドがあります。このインターフェイスは主に、JAVA のコールバック コードでキャッチされない例外を処理するために使用されます。

    uncaughtException メソッドでは例外をスローできず、このメソッドからスローされた例外は無視されることに注意してください。

    METHOD_NAME このフィールドは、コールバックによって呼び出されるメソッドを指定します。

    Callback クラスにパブリック メソッドが 1 つだけ定義されている場合、デフォルトのコールバック メソッドはこのメソッドになります。 Callback クラスに複数のパブリック メソッドが定義されている場合、METHOD_NAME = "callback" のメソッドがコールバックとして選択されます。

    最後の属性は FORBIDDEN_NAMES です。このリスト内の名前はコールバック メソッドとして使用できないことを示します。

    現在使用できないメソッド名は「hashCode」「equals」「toString」の3つのようです。

    Callback には DLLCallback という兄弟もあります。DLLCallback の定義を見てみましょう:

    public interface DLLCallback extends Callback {
        @java.lang.annotation.Native
        int DLL_FPTRS = 16;
    }

    DLLCallback は主に Windows API にアクセスするために使用されます。

    コールバック オブジェクトの場合、コールバック オブジェクトを自分で解放する必要があります。ネイティブ コードがリサイクルされたコールバックにアクセスしようとすると、VM がクラッシュする可能性があります。

    コールバックの適用

    コールバックの定義

    JNA のコールバックは実際にはネイティブの関数にポインタをマップするためです。まず、構造体で定義されている関数ポインターを見てください。

    struct _functions {
      int (*open)(const char*,int);
      int (*close)(int);
    };

    この構造体では、2 つの関数ポインターが定義されており、それぞれ 2 つのパラメーターと 1 つのパラメーターがあります。

    対応する JNA コールバック定義は次のとおりです。

    public class Functions extends Structure {
      public static interface OpenFunc extends Callback {
        int invoke(String name, int options);
      }
      public static interface CloseFunc extends Callback {
        int invoke(int fd);
      }
      public OpenFunc open;
      public CloseFunc close;
    }

    Callback を継承する 2 つのインターフェイスを Structure に定義し、対応する呼び出しメソッドは対応するインターフェイスで定義されます。

    次に、具体的な呼び出しメソッドを見てみましょう:

    Functions funcs = new Functions();
    lib.init(funcs);
    int fd = funcs.open.invoke("myfile", 0);
    funcs.close.invoke(fd);

    さらに、以下に示すように、Callback を関数の戻り値として使用することもできます:

    typedef void (*sig_t)(int);
    sig_t signal(int signal, sig_t sigfunc);

    この種の別個の関数ポインターの場合、以下に示すように、ライブラリをカスタマイズし、その中で対応するコールバックを定義する必要があります。

    public interface CLibrary extends Library {
        public interface SignalFunction extends Callback {
            void invoke(int signal);
        }
        SignalFunction signal(int signal, SignalFunction func);
    }

    コールバックの取得と適用

    コールバックが定義されている場合Structure の場合、Structure は初期化時に自動的にインスタンス化されるため、Structure から対応するプロパティにアクセスするだけで済みます。

    コールバックが通常のライブラリで定義されている場合、次のようになります。

    public static interface TestLibrary extends Library {
            interface VoidCallback extends Callback {
                void callback();
            }
            interface ByteCallback extends Callback {
                byte callback(byte arg, byte arg2);
            }
            void callVoidCallback(VoidCallback c);
            byte callInt8Callback(ByteCallback c, byte arg, byte arg2);
        }

    上の例では、ライブラリに 2 つのコールバックを定義しました。1 つは戻り値のないコールバック、もう 1 つは戻り値のないコールバックです。バイトを返すコールバック。

    JNA は、Callback の取得に役立つ単純なツール クラスを提供します。このツール クラスは CallbackReference で、対応するメソッドは CallbackReference.getCallback です。次に示すように:

    Pointer p = new Pointer("MultiplyMappedCallback".hashCode());
    Callback cbV1 = CallbackReference.getCallback(TestLibrary.VoidCallback.class, p);
    Callback cbB1 = CallbackReference.getCallback(TestLibrary.ByteCallback.class, p);
    log.info("cbV1:{}",cbV1);
    log.info("cbB1:{}",cbB1);

    出力結果は次のとおりです。 :

    INFO com.flydean.CallbackUsage - cbV1:ネイティブ関数へのプロキシ インターフェイス@0xffffffffc46eeefc (com.flydean.CallbackUsage$TestLibrary$VoidCallback)
    INFO com.flydean.CallbackUsage - cbB1:Proxyネイティブ関数へのインターフェイス@0xffffffffc46eeefc (com.flydean.CallbackUsage$TestLibrary$ByteCallback)

    これら 2 つのコールバックが実際にはネイティブ メソッドのプロキシであることがわかります。 getCallback の実装ロジックを詳しく見ると、

    private static Callback getCallback(Class<?> type, Pointer p, boolean direct) {
            if (p == null) {
                return null;
            }
            if (!type.isInterface())
                throw new IllegalArgumentException("Callback type must be an interface");
            Map<Callback, CallbackReference> map = direct ? directCallbackMap : callbackMap;
            synchronized(pointerCallbackMap) {
                Reference<Callback>[] array = pointerCallbackMap.get(p);
                Callback cb = getTypeAssignableCallback(type, array);
                if (cb != null) {
                    return cb;
                }
                cb = createCallback(type, p);
                pointerCallbackMap.put(p, addCallbackToArray(cb,array));
                // No CallbackReference for this callback
                map.remove(cb);
                return cb;
            }
        }

    その実装ロジックでは、まず型がインターフェイスであるかどうかを判断し、インターフェイスでない場合はエラーが報告されることがわかります。次に、それが直接マッピングであるかどうかを判断します。実際、JNA の現在の実装はすべてインターフェイス マッピングであるため、次のロジックは関数ポインターに対応するコールバックを pointerCallbackMap から取得することです。次に、渡されたタイプに従って特定のコールバックを見つけます。

    見つからない場合は、新しいコールバックを作成し、最後に新しく作成したコールバックを pointerCallbackMap に保存します。

    ここには Pointer という重要なパラメータがあることに注意してください。実際に使用するときは、実際の単純な関数へのポインタを渡す必要があります。上の例では、簡単にするためにポインターをカスタマイズしましたが、実際にはあまり意味がありません。

    如果真的要想在JNA中调用在TestLibrary中创建的两个call方法:callVoidCallback和callInt8Callback,首先需要加载对应的Library:

    TestLibrary lib = Native.load("testlib", TestLibrary.class);

    然后分别创建TestLibrary.VoidCallback和TestLibrary.ByteCallback的实例如下,首先看一下VoidCallback:

    final boolean[] voidCalled = { false };
            TestLibrary.VoidCallback cb1 = new TestLibrary.VoidCallback() {
                @Override
                public void callback() {
                    voidCalled[0] = true;
                }
            };
            lib.callVoidCallback(cb1);
            assertTrue("Callback not called", voidCalled[0]);

    这里我们在callback中将voidCalled的值回写为true表示已经调用了callback方法。

    再看看带返回值的ByteCallback:

    final boolean[] int8Called = {false};
            final byte[] cbArgs = { 0, 0 };
            TestLibrary.ByteCallback cb2 = new TestLibrary.ByteCallback() {
                @Override
                public byte callback(byte arg, byte arg2) {
                    int8Called[0] = true;
                    cbArgs[0] = arg;
                    cbArgs[1] = arg2;
                    return (byte)(arg + arg2);
                }
            };
    
    final byte MAGIC = 0x11;
    byte value = lib.callInt8Callback(cb2, MAGIC, (byte)(MAGIC*2));

    我们直接在callback方法中返回要返回的byte值即可。

    在多线程环境中使用callback

    默认情况下, callback方法是在当前的线程中执行的。如果希望callback方法是在另外的线程中执行,则可以创建一个CallbackThreadInitializer,指定daemon,detach,name,和threadGroup属性:

     final String tname = "VoidCallbackThreaded";
            ThreadGroup testGroup = new ThreadGroup("Thread group for callVoidCallbackThreaded");
            CallbackThreadInitializer init = new CallbackThreadInitializer(true, false, tname, testGroup);

    然后创建callback的实例:

    TestLibrary.VoidCallback cb = new TestLibrary.VoidCallback() {
                @Override
                public void callback() {
                    Thread thread = Thread.currentThread();
                    daemon[0] = thread.isDaemon();
                    name[0] = thread.getName();
                    group[0] = thread.getThreadGroup();
                    t[0] = thread;
                    if (thread.isAlive()) {
                        alive[0] = true;
                    }
                    ++called[0];
                    if (THREAD_DETACH_BUG && called[0] == 2) {
                        Native.detach(true);
                    }
                }
            };

    然后调用:

    Native.setCallbackThreadInitializer(cb, init);

    将callback和CallbackThreadInitializer进行关联。

    最后调用callback方法即可:

    lib.callVoidCallbackThreaded(cb, 2, 2000, "callVoidCallbackThreaded", 0);

    以上が高度な Java 使用法で JNA のコールバック問題を解決する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    声明:
    この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。