JNI и конструкторы - PullRequest
       11

JNI и конструкторы

1 голос
/ 01 октября 2019

У меня есть скомпилированная библиотека, которую мне нужно использовать в проекте. Короче говоря, это библиотека для взаимодействия с определенным оборудованием. У меня есть файлы библиотек .a и .dll, соответственно для linux и windows, и куча заголовков C ++ .h со всеми описанными там общими функциями и классами.

Проблема в том, что проекту необходимобыть на Java, поэтому мне нужно написать оболочку JNI для этой библиотеки, и, честно говоря, я никогда не делал этого. Но ничего страшного, я должен это изучить.

Я прочитал в Интернете кучу документации и выяснил, как передавать переменные, создавать объекты Java из собственного кода и т. Д.

Что я не могу понять, так это как работать с нативными конструкторами с использованием JNI? Я понятия не имею, каков исходный код этих конструкторов, у меня есть только такие заголовки:

namespace RFDevice {

class RFDEVICE_API RFEthernetDetector
{
public:
    //-----------------------------------------------------------------------------
    //  FUNCTION  RFEthernetDetector::RFEthernetDetector
    /// \brief    Default constructor of RFEthernetDetector object.
    ///           
    /// \return   void : N/A
    //-----------------------------------------------------------------------------
    RFEthernetDetector();
    RFEthernetDetector(const WORD wCustomPortNumber);

Так что, в принципе, если бы я должен был написать свою программу на C ++ (что я не могу), ябудет делать что-то вроде

RFEthernetDetector ethernetDetector = new RFEthernerDetector(somePort);

, а затем работать с этим объектом. Но ... Как мне сделать это в Java с помощью JNI? Я не понимаю, как я должен создать собственный метод для конструктора, который будет вызывать конструктор из моей библиотеки .a, а затем иметь какой-то способ работы с этим конкретным объектом? Я знаю, как создавать Java-объекты из собственного кода, но дело в том, что у меня нет никакой информации о внутренней структуре класса RFEthernetDetector - только некоторые из его открытых полей и открытых методов.

И я могу 'Кажется, я не нашел нужных статей в сети, чтобы помочь мне. Как мне это сделать?

Обновление: немного дальнейших разъяснений.

Я создаю класс-оболочку .java следующим образом:

public class RFEthernetDetector
{
    public RFEthernetDetector(int portNumber)
    {
        Init(portNumber);
    }

    public native void Init(int portNumber);            // Void? Or what?
}

, затем компилирую его с -Параметр h для создания файла JNI .h:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class RFEthernetDetector */

#ifndef _Included_RFEthernetDetector
#define _Included_RFEthernetDetector
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     RFEthernetDetector
 * Method:    Init
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_RFEthernetDetector_Init
  (JNIEnv *, jobject, jint);

#ifdef __cplusplus
}
#endif
#endif

Затем я создаю реализацию, которая будет вызывать функции из моей библиотеки .a:

#include "RFEthernetDetector.h"     // auto-generated JNI header
#include "RFEthernetDetector_native.h"  // h file that comes with the library, 
                    //contains definition of RFEthernetDetector class
/*
 * Class:     RFEthernetDetector
 * Method:    Init
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_RFEthernetDetector_Init(JNIEnv *env, jobject thisObj, jint value)
{
    RFEthernetDetector *rfeDetector = new RFEthernetDetector(value);    // constructor from the library
    // now how do I access this new object from Java?
    // if I need to later call rfDetector->doSomething() on that exact class instance?
}

Ответы [ 3 ]

2 голосов
/ 01 октября 2019

Вам потребуется создать класс RFEthernetDetector Java, который через указатель владеет a RFEthernetDetector на стороне C ++. Это не весело , но межъязыковой клей никогда не бывает.

// In this design, the C++ object needs to be explicitly destroyed by calling
// close() on the Java side.
// I think that Eclipse, at least, is configured by default to complain
// if an AutoCloseable is never close()d.
public class RFEthernetDetector implements AutoCloseable {
   private final long cxxThis; // using the "store pointers as longs" convention
   private boolean closed = false;
   public RFEthernetDetector(int port) {
       cxxThis = cxxConstruct(port);
   };
   @Override
   public void close() {
       if(!closed) {
           cxxDestroy(cxxThis);
           closed = true;
       }
   }
   private static native long cxxConstruct(int port);
   private static native void cxxDestroy(long cxxThis);

   // Works fine as a safety net, I suppose...
   @Override
   @Deprecated
   protected void finalize() {
       close();
   }
}

А на стороне C ++:

#include "RFEthernetDetector.h"

JNIEXPORT jlong JNICALL Java_RFEthernetDetector_cxxConstruct(JNIEnv *, jclass, jint port) {
    return reinterpret_cast<jlong>(new RFEthernetDetector(port));
}

JNIEXPORT void JNICALL Java_RFEthernetDetector_cxxDestroy(JNIEnv *, jclass, jlong thiz) {
    delete reinterpret_cast<RFEthernetDetector*>(thiz);
    // calling other methods is similar:
    // pass the cxxThis to C++, cast it, and do something through it
}

Если все это reinterpret_cast ingзаставляет вас чувствовать себя некомфортно, вместо этого вы можете выбрать map:

#include <map>

std::map<jlong, RFEthernetDetector> references;

JNIEXPORT jlong JNICALL Java_RFEthernetDetector_cxxConstruct(JNIEnv *, jclass, jint port) {
    jlong next = 0;
    auto it = references.begin();
    for(; it != references.end() && it->first == next; it++) next++;
    references.emplace_hint(it, next, port);
    return next;
}

JNIEXPORT void JNICALL Java_RFEthernetDetector_cxxDestroy(JNIEnv *, jclass, jlong thiz) {
    references.erase(thiz);
}
0 голосов
/ 01 октября 2019

Итак, в данный момент я в основном сохранил адрес в своем классе .java как переменную long, а затем метод Init () вернул адрес экземпляра C ++ как jlong.

Затем, когда мне нужно вызвать что-то для этого экземпляра класса - я передаю адрес в качестве дополнительного аргумента и выполняю преобразование в коде C следующим образом (проверено на тестовом классе / custom .so):

//constructor wrapper
JNIEXPORT jlong JNICALL Java_Test_Greetings(JNIEnv *env, jobject thisObj, jint value)
{
    Greetings *greetings = new Greetings(value);
    return (jlong)greetings;
}

JNIEXPORT void JNICALL Java_Test_HelloWorld(JNIEnv *env, jobject thisObj, jlong reference)
{
    Greetings *greetings;
    greetings = (Greetings*)reference;
    greetings->helloValue();
}

Я понятия не имею, правильный ли это способ, но он работает ... был бы признателен, если бы кто-то сказал мне, как я ошибаюсь.

0 голосов
/ 01 октября 2019

Вам нужно будет создать класс native в Java, а затем запустить программу javah, которая создаст заглушки, ожидаемые Java. Затем вам нужно сопоставить Java-заглушки с кодом C ++, чтобы скомпилировать и распространить эту библиотеку связывания вместе с вашей Java-программой.

https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html

...