4

I have some Java methods that I need to call from C++ via JNI. My JNI implementation is based on
Is it possible to make a Java JNI which calls jdbc?

There are two java files in my Java project. One is to define a class, and the other one contains the actual methods that c++ will call.

public class MyObject {
    private static int no;
    private static LocalDateTime time;
    private static String status;
    // getters, setters and toString
}

public class ObjectHandler {
    public static MyObject objectReturnToC;

    public static Object methodA (type1 arg1, type2 arg2, type3 arg3) {
        objectReturnToC = new MyObject();
        // setting fields in returnObject according to passed-in parameters
        return objectReturnToC;
    }
    public static void methodB(Object objectReturnedFromC) {
        // access fields in objectReturnedFromC, do computation and store in
    }
}

I created C++ DLL in Visual Studio 2010. There's JVM.cpp, JVM.h, JavaCalls.h, and JavaCalls.cpp

JavaCalls.h

#ifdef JAVACALLSDLL_EXPORTS
#define JAVACALLSDLL_API __declspec(dllexport) 
#else
#define JAVACALLSDLL_API __declspec(dllimport) 
#endif

namespace JavaCalls
{
    class JavaCalls
    {
    public:
        static JAVACALLSDLL_API void *javaMethodA(type1, type2, type3);
        static JAVACALLSDLL_API string toString(void **javaObject);
        static JAVACALLSDLL_API void javaMethodB(void **javaObject);
    };
}

JavaCalls.cpp

namespace JavaCalls 
{
    void *JavaCalls::javaMethodA(type1 arg1, type2 arg2, type3 arg3) 
    {
        // invoke JVM
        // Find class, methodID
        jobject javaObject = CallStaticObjectMethod(jMain, "methodA",...);
        return javaObject;
    }
    void JavaCalls::javaMethodB(void** javaObject) {
        // invoke JVM
        // Find class, methodID
        CallStaticVoidMethod(jMain, "methodB",...);
    }
}

C++ calling Java methodA and methodB using DLL:

int main() 
{
    void* a = JavaCalls::JavaCalls::javaMethodA(arg1, arg2, arg3);
    // doing other stuff and updating fields in a
    JavaCalls::JavaCalls::javaMethodB(static_cast<void**>(a));
}

Obviously, passing pointer, wishing it would be available to C++ is not working. But what should I do to keep the Java Object in C++ and pass it back to Java later? Should I create a C++ struct and map Java Object field into it using GetObjectField?

Community
  • 1
  • 1
wolf97084
  • 270
  • 4
  • 22
  • 1
    Why are you using `void` pointers? What stops you from just passing `jobject`s around? What's the point of using `void**`? – Anton Savin Oct 09 '14 at 14:00
  • I thought void pointer is meant for generic pointer and did not know I can keep jobject around in c++ dll and passing it around. I should definitely try it. Later I would also need to port this so that C and FORTRAN can call the Java methods. Do you think passing jobject is a good option even with C and FORTRAN? – wolf97084 Oct 09 '14 at 14:11
  • 1
    The question is why do you need to call jdbc that way? You can connect to DB from C++ directly, and it might be more performant. – Ashalynd Oct 09 '14 at 14:28
  • I know I can connect DB from c++ directly, but we have decided not to require obdc driver installed in all user computers (it caused a lot of headache and overhead, and please don't ask me how.) That's why I need to call jdbc via JNI in C++ – wolf97084 Oct 09 '14 at 14:37

2 Answers2

3

I don't quite understand why you need void** in your code. If you want to make the interface opaque, just use void*. Also don't forget to call NewGlobalRef() and DeleteGlobalRef() on the returned jobject - this will prevent its destruction by garbage collector:

void *JavaCalls::javaMethodA(type1 arg1, type2 arg2, type3 arg3) 
{
    jobject javaObject = CallStaticObjectMethod(jMain, "methodA",...);
    return NewGlobalRef(jMain, javaObject);
}

void JavaCalls::javaMethodB(void* javaObject) {
     CallStaticVoidMethod(jMain, "methodB", static_cast<jobject>(javaObject));
}

// add this method - it should be called when you finish using the object
void JavaCalls::ReleaseObject(void* javaObject) {
     DeleteGlobalRef(jMain, static_cast<jobject>(javaObject));
}
Anton Savin
  • 40,838
  • 8
  • 54
  • 90
  • Can I just add the DeleteGlobalRef() at the end of my JavaMethodB? – wolf97084 Oct 09 '14 at 14:45
  • @wolf97084 Yes, of course you can, if you don't need the object anymore. I don't know how many times you want to call `JavaMethodB` or perhaps other methods, so I wrote a general implementation. – Anton Savin Oct 09 '14 at 14:47
  • I updated all void** to void* and now try to call toString on the jobject returned back from methodA. But the returned string from toString() is always 0x00000000 in C++ debugger. Is there a special way to call toString()? I used jmethodID jToString = env->GetMethodIS(jMainObject, "toString", "()Ljava/lang/String;"); jstring jObectString = (jstring) env->CallObjectMethod(objectreturnedFromMethodA, jToString); – wolf97084 Oct 13 '14 at 17:33
  • @wolf97084 I think you better make it a different question. For now I don't know how to solve this. You can try to override `toString()` of your class and put a breakpoint/logging in there to ensure it's called. – Anton Savin Oct 13 '14 at 17:36
0

There is a great helper program called javah.exe.

Write your code like you like to code and execute javah to the .class. It will create a full compatible .h file for you.

You will become for this:

public class Test {

    public class MyObject {
        private int no;
        private String status;
    }

    public class Callback {
        public void callback(MyObject afterCpp) {

        }
    }

    public native void register(Callback v, MyObject beforeCpp);
}

This header:

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

#ifndef _Included_Test
#define _Included_Test
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Test
 * Method:    register
 * Signature: (LTest/Callback;LTest/MyObject;)V
 */
JNIEXPORT void JNICALL Java_Test_register
  (JNIEnv *, jobject, jobject, jobject);

#ifdef __cplusplus
}
#endif
#endif

You simply have to call callback to the 3rd c++-parameter.

Grim
  • 1,938
  • 10
  • 56
  • 123
  • I thought this is to call C++ from Java via JNI? Guess I am wrong. (I am very new to JNI.) I don't think the Java Object I passed to C++ is kept in the memory (it has address but all data are not there.) Do I pass jobject to C++ or just void pointer with your solution? – wolf97084 Oct 09 '14 at 14:19
  • Java will not understand pointers from `c` or `c++`. `Java` is cross-platform, whearat `c++` is `32bit` or `64bit`. JNI is a porting-interface, not a design-interface. Thats why `c++`-Types are compleatly different to `java`-Types: You need to translate them. – Grim Oct 09 '14 at 14:23