Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?
}
like image 890
Vlad Vyatkin Avatar asked Sep 06 '25 15:09

Vlad Vyatkin


1 Answers

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);
}
like image 198
HTNW Avatar answered Sep 09 '25 05:09

HTNW