>Java >java지도 시간 >고급 Java 사용 시 JNA의 콜백 문제를 해결하는 방법

고급 Java 사용 시 JNA의 콜백 문제를 해결하는 방법

PHPz
PHPz앞으로
2023-05-05 11:37:061489검색

    소개

    콜백이란? 간단히 말해서 콜백은 콜백 알림입니다. 메소드가 완료되거나 이벤트가 발생한 후 특정 작업을 알려야 할 경우 콜백을 사용해야 합니다.

    콜백을 가장 많이 볼 수 있는 언어는 javascript입니다. 기본적으로 javascript에서는 콜백이 어디에나 있습니다. 콜백으로 인해 발생하는 콜백 지옥 문제를 해결하기 위해 ES6에서는 이 문제를 해결하기 위해 Promise가 특별히 도입되었습니다.

    네이티브 메소드와의 상호작용을 용이하게 하기 위해 JNA는 콜백을 위한 콜백도 제공합니다. 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"));
    }

    모든 콜백 메소드는 이 콜백 인터페이스를 구현해야 합니다. 콜백 인터페이스는 인터페이스와 두 가지 속성을 정의합니다.

    먼저 이 인터페이스를 살펴보겠습니다. 인터페이스 이름은 UncaughtExceptionHandler이고, 그 안에는 uncaughtException 메서드가 있습니다. 이 인터페이스는 주로 JAVA의 콜백 코드에서 catch되지 않는 예외를 처리하는 데 사용됩니다.

    uncaughtException 메서드에서는 예외가 발생할 수 없으며 이 메서드에서 발생한 모든 예외는 무시됩니다.

    METHOD_NAME 이 필드는 콜백이 호출할 메소드를 지정합니다.

    콜백 클래스에 공용 메소드가 하나만 정의되어 있는 경우 기본 콜백 메소드는 이 메소드입니다. 콜백 클래스에 여러 공개 메소드가 정의된 경우 METHOD_NAME = "콜백"인 메소드가 콜백으로 선택됩니다.

    마지막 속성은 FORBIDDEN_NAMES입니다. 이 목록의 이름을 콜백 메서드로 사용할 수 없음을 나타냅니다.

    현재 사용할 수 없는 세 가지 메서드 이름이 있는 것 같습니다. 즉, "hashCode", "equals" 및 "toString"입니다.

    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);
    };

    이 구조체에는 각각 두 개의 매개변수와 하나의 매개변수가 있는 두 개의 함수 포인터가 정의되어 있습니다.

    해당 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에서 상속받은 두 개의 인터페이스를 Structure에 정의하고, 해당 인터페이스에 해당 호출 메소드를 정의합니다.

    그런 다음 구체적인 호출 방법을 살펴보세요.

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

    또한 아래와 같이 콜백을 함수의 반환 값으로 사용할 수도 있습니다.

    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가 초기화될 때 자동으로 인스턴스화될 수 있으며, 그런 다음에서 해당 속성에 액세스하기만 하면 됩니다. 구조.

    일반 라이브러리에 콜백을 정의하면 다음과 같습니다.

    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);
        }

    위의 예에서는 라이브러리에 두 개의 콜백을 정의했는데, 하나는 반환 값이 없는 콜백이고 다른 하나는 반환 값이 있는 콜백입니다. 바이트 .

    JNA는 콜백을 얻는 데 도움이 되는 간단한 도구 클래스를 제공합니다. 이 도구 클래스는 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:네이티브 함수에 대한 프록시 인터페이스@0xffffffffc46eeefc(com.flydean.CallbackUsage$TestLibrary$Byte 콜백 )

    이 두 콜백은 실제로 기본 메서드에 대한 프록시임을 알 수 있습니다. 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 구현은 모두 인터페이스 매핑이므로 다음 로직은 포인터CallbackMap에서 함수 포인터에 해당하는 콜백을 가져오는 것입니다. 그런 다음 전달된 유형에 따라 특정 콜백을 찾습니다.

    찾지 못하면 새 콜백을 생성하고 마지막으로 새로 생성된 콜백을 포인터CallbackMap에 저장합니다.

    여기에는 Pointer라는 핵심 매개변수가 있다는 점에 유의하세요. 실제로 사용할 때는 실제 naitve 함수에 대한 포인터를 전달해야 합니다. 위의 예에서는 단순화를 위해 포인터를 사용자 정의했지만 실제적인 의미는 별로 없습니다.

    如果真的要想在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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    성명:
    이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제