Vlad Vyatkin
Vlad Vyatkin

Reputation: 584

JNI and constructors

I have a compiled library that I need to use in a project. To keep it short, it's a library for interacting with a specific piece of hardware. What I have is .a and .dll library files, for linux and windows respectively, and a bunch of C++ .h headers with all the public functions and classes described there.

The problem is that the project needs to be in Java, so I need to write a JNI wrapper for this library, and honestly, I've never done that. But that's ok, I'm down to learn the thing.

I've read up a bunch of documentation online, and I figured out passing variables, creating java objects from native code, etc.

What I can't figure out, is how to work with native constructors using JNI? I have no idea what the source code of these constructors are, I only have the headers like this:

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

So basically if I was to write my program in C++ (which I can't), I would do something like

RFEthernetDetector ethernetDetector = new RFEthernerDetector(somePort);

and then work with that object. But... How do I do this in Java using JNI? I don't understand how am I supposed to create a native method for constructor, that would call the constructor from my .a library, and then have some way of working with that specific object? I know how to create java objects from native code - but the thing is I don't have any information about internal structure of the RFEthernetDetector class - only some of it's public fields and public methods.

And I can't seem to find the right articles on the net to help me out. How do I do that?

Update: A bit further clarification.

I create a .java wrapper class like this:

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

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

then I compile it with -h parameter to generate JNI .h file:

/* 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

I then create an implementation that will call the functions from my .a library:

#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?
}

Upvotes: 4

Views: 1954

Answers (3)

HTNW
HTNW

Reputation: 29148

You would need to build a RFEthernetDetector Java class that, through a pointer, owns a RFEthernetDetector on the C++ side. This is no fun, but inter-language glue never is.

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

And on the C++ side:

#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
}

If all that reinterpret_casting makes you feel uncomfortable, you could choose to instead keep a map around:

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

Upvotes: 5

Vlad Vyatkin
Vlad Vyatkin

Reputation: 584

So, what I ended up doing for now is basically storing the address in my .java class as "long" variable, and then have Init() method return the address of C++ instance as jlong.

Then, when I need to call something on that instance of a class - I pass the address as an additional argument and do the transformation in the C code like this (tested on test class / 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();
}

I have no idea if that's the correct way to do it, but it works... would appreciate if someone tells me how wrong I am.

Upvotes: 0

PaulProgrammer
PaulProgrammer

Reputation: 17620

You'll need to build the native class in Java, then run the javah program which will build the stubs that Java expects. You'll then need to map the java stubs to the C++ code to compile and distribute that binding library along with your java program.

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

Upvotes: 0

Related Questions