2

I am new to jni and very confused if I can use jni to achieve what I need done. I want to make a java api what will use jdbc to update database, but this particular api will be called from C++ program.

So I think I probably should write jni code which access the database via jdbc (is that even possible?), create C++ code and generate dll so other C++ programs can just call the dll to update database. Is this all possible? If so, how do I really call jdbc in jni? If this dll is finally made, can Fortran call it as well?

My other thought is maybe I should make a regular java program to update the database, then use say ikvm to wrap the java class into C++ dll?

The thing is I have to use access database using Java. Our C++ programs will not access database at all, and it would be better if this java api can be accessed via system call.

Or is there any better way to do it?

I hope I explained it well. I am not all familiar with what I am assigned here and cannot find much relevant reference.

Thank you so much!!

UPDATED: The problem is not all computers have C++ postgresql driver installed but they do have Java postgresql driver installed. We don't want to force everyone to install the C++ db driver and no major changes in those C++ program will be made. So it will make sense to come up something in Java to access database. The java system service (preferred, like dll?) /API basically is called to record start time and end time of a C++ program. C++ program will make a "function" call (with pass-in parameter and returned value) to this system service/Java API to record start/end time.

wolf97084
  • 270
  • 4
  • 22
  • 3
    I don't see why you have to call anything from C++. Use JDBC with a type IV driver. No need for C++. Unnecessary complication. Don't do it. – duffymo Aug 19 '14 at 01:18
  • I'm not sure I follow your question, are you looking for a RPC method, an ESB or ODBC? – Elliott Frisch Aug 19 '14 at 01:20
  • I've read question 2 times and cannot understand who will call what and why, maybe jdbc will call java c++ application to use sql queries with jni db? – Iłya Bursov Aug 19 '14 at 01:21
  • 1
    Simplify your problem. Write JNI that calls Java of your own devising, and write Java that calls JDBC. – user207421 Aug 19 '14 at 09:20
  • @ElliottFrisch I am not looking for RPC/ESB method, but a JAVA API so that C++ can call in its program. The call should be just one line, more like a function call with pass-in parameters and return value. – wolf97084 Aug 19 '14 at 13:23
  • @EJB Do that mean I make a JNI and in the C++ JNI file I call JDBC? I am really learning here ... slowly. Thank you all guyes! – wolf97084 Aug 19 '14 at 13:24
  • @duffymo this Java API is for the existing C++ programs to call to record time in the database. I wish I don't have to deal with calling Java from C++ – wolf97084 Aug 19 '14 at 13:26
  • 1
    You wouldn't have to deal with this if you wrote a simple Java web service that the C++ clients could call to record time. If the C++ clients can send HTTP GET requests to the web service they don't have to know or care what language it's written in. – duffymo Aug 19 '14 at 14:33
  • 1
    @duffymo Sorry to came back so late on this. I did eventually develop a web service to handle this. – wolf97084 Oct 01 '20 at 13:18
  • Six years later. Thank you for letting me know. I'm glad you came to a satisfactory solution. You're nothing if not persistent. I hope your Java service is RESTful and easy to use. Extra points if you used Spring Boot. – duffymo Oct 01 '20 at 13:19

1 Answers1

4

I'm not going to lecture you on the right or wrong way to approach what you are trying to do. However, if you are trying to invoke Java code (JDBC.jar) then the following is for you.. Otherwise feel free to downvote.

JVM.hpp:

#ifndef JVM_HPP_INCLUDED
#define JVM_HPP_INCLUDED

#include "../java/jni.h"
#include <windows.h>
#include <iostream>
#include <stdexcept>
#include <algorithm>

class Jvm
{
    private:
        JavaVM* jvm;
        JNIEnv* env;
        JavaVMInitArgs jvm_args;
        jclass systemClassLoader;

    public:
        Jvm(std::string ClassPath = ".");
        ~Jvm();

        inline JavaVM* GetJVM() const {return jvm;}
        inline JNIEnv* GetENV() const {return env;}
        inline jclass GetSystemClassLoader() const {return systemClassLoader;}
        void DestroyJVM();
        void PrintStackTrace();
        jclass DefineClass(const char* FullClassName, const void* ClassBuffer, std::uint32_t BufferLength);
        jclass DefineClass(const char* FullClassName, jobject ClassLoader, const void* ClassBuffer, std::uint32_t BufferLength);

        void RegisterNativeMethod(const char* MethodName, const char* MethodSignature, void* func_ptr);
        void RegisterNativeMethod(jobject ClassLoader, const char* MethodName, const char* MethodSignature, void* func_ptr);
        void RegisterNativeMethods(JNINativeMethod* Methods, std::uint32_t MethodCount);
        void RegisterNativeMethods(jobject ClassLoader, JNINativeMethod* Methods, std::uint32_t MethodCount);

    protected:
        void InitClassLoader();
};

#endif // JVM_HPP_INCLUDED

JVM.cpp:

#include "JVM.hpp"

Jvm::~Jvm()
{
    env->DeleteGlobalRef(this->systemClassLoader);
    jvm->DestroyJavaVM();
}

Jvm::Jvm(std::string ClassPath) : jvm(NULL), env(NULL), jvm_args(), systemClassLoader(NULL)
{
    JavaVMOption* options = new JavaVMOption[2];
    jvm_args.version = JNI_VERSION_1_6;
    JNI_GetDefaultJavaVMInitArgs(&jvm_args);
    options[0].optionString = const_cast<char*>("-Djava.compiler=NONE");
    options[1].optionString = const_cast<char*>(("-Djava.class.path=" + ClassPath).c_str());
    jvm_args.nOptions = 2;
    jvm_args.options = options;
    jvm_args.ignoreUnrecognized = false;

    if (JNI_CreateJavaVM(&jvm, reinterpret_cast<void**>(&env), &jvm_args))
    {
        delete[] options;
        throw std::runtime_error("Failed To Create JVM Instance.");
    }

    delete[] options;
}

void Jvm::InitClassLoader()
{
    if (!this->systemClassLoader)
    {
        jclass classloader = env->FindClass("Ljava/lang/ClassLoader;");
        if (!classloader)
        {
            throw std::runtime_error("Failed To find ClassLoader.");
        }

        jmethodID SystemLoaderMethod = env->GetStaticMethodID(classloader, "getSystemClassLoader", "()Ljava/lang/ClassLoader;");
        jobject loader = env->CallStaticObjectMethod(classloader, SystemLoaderMethod);
        this->systemClassLoader = reinterpret_cast<jclass>(env->NewGlobalRef(loader));
    }
}

void Jvm::PrintStackTrace()
{
    if (env->ExceptionOccurred())
    {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
}

jclass Jvm::DefineClass(const char* FullClassName, const void* ClassBuffer, std::uint32_t BufferLength)
{
    this->InitClassLoader();
    return this->DefineClass(FullClassName, this->systemClassLoader, ClassBuffer, BufferLength);
}

jclass Jvm::DefineClass(const char* FullClassName, jobject ClassLoader, const void* ClassBuffer, std::uint32_t BufferLength)
{
    return ClassLoader ? env->DefineClass(FullClassName, ClassLoader, static_cast<const jbyte*>(ClassBuffer), BufferLength) : NULL;
}

void Jvm::RegisterNativeMethod(const char* MethodName, const char* MethodSignature, void* func_ptr)
{
    JNINativeMethod method;
    method.name = const_cast<char*>(MethodName);
    method.signature = const_cast<char*>(MethodSignature);
    method.fnPtr = func_ptr;
    this->RegisterNativeMethods(&method, 1);
}

void Jvm::RegisterNativeMethod(jobject ClassLoader, const char* MethodName, const char* MethodSignature, void* func_ptr)
{
    JNINativeMethod method;
    method.name = const_cast<char*>(MethodName);
    method.signature = const_cast<char*>(MethodSignature);
    method.fnPtr = func_ptr;
    this->RegisterNativeMethods(ClassLoader, &method, 1);
}

void Jvm::RegisterNativeMethods(JNINativeMethod* Methods, std::uint32_t MethodCount)
{
    this->InitClassLoader();
    this->RegisterNativeMethods(this->systemClassLoader, Methods, MethodCount);
}

void Jvm::RegisterNativeMethods(jobject ClassLoader, JNINativeMethod* Methods, std::uint32_t MethodCount)
{
    if (ClassLoader)
    {
        env->RegisterNatives(static_cast<jclass>(ClassLoader), Methods, MethodCount);
    }
}

You can then create an instance of it that loads your jar.

int main()
{
    Jvm VM("C:/Users/Brandon/IdeaProjects/Eos/out/production/Eos/Bot.jar");

    jclass jMain = VM.GetENV()->FindClass("eos/Main");

    if (jMain != nullptr)
    {
        jmethodID mainMethod = env->GetStaticMethodID(jMain, "main", "([Ljava/lang/String;)V");
        jclass StringClass = env->FindClass("java/lang/String");
        jobjectArray Args = env->NewObjectArray(0, StringClass, 0);
        env->CallStaticVoidMethod(jMain, MainMethod, Args);
    }
}

Now this just shows how to run a jar with a Main Method. However, you can access ANY class from the jar and invoke it with however many parameters needed. It doesn't need a main.

Now it IS a lot MORE work to do this, but I won't lecture you. The question was whether or not is was possible and the answer is YES.. it is. So long as you create an instance of the "JVM". After that, it's a matter of accessing the class via the "Package/Class" NOT "Package.Class" as done in Java.. then invoking whatever methods you want.

Brandon
  • 22,723
  • 11
  • 93
  • 186
  • I hear you. Is it possible to do more like a function call to Java with pass-in parameters to Java and a return value back? – wolf97084 Aug 20 '14 at 11:56
  • Yes it is. You'd have to do something like in the above. First you'd call `FindClass` to get the class the method belongs to. You'd then need to find the method using `GetStaticMethodID` or `GetMethodID`. Then you'd call need to construct a new argument using `NewObject`, `NewStringUTF`, `NewObjectArray`, `NewStringArray`, etc.. Finally, you'd call your function using: `CallObjectMethod`, `CallVoidMethod`, `CallStaticVoidMethod`, etc.. It will return the a value to you which you can check, read, or do whatever with. – Brandon Aug 20 '14 at 13:33
  • I put the code in but it's complaining about env is undefined. Where did I go wrong there? Can I put the main() in a different c++ file than putting it in JVM.cpp? – wolf97084 Aug 26 '14 at 18:47
  • `main` isn't supposed to be in `JNI.cpp`. Also make sure you have the right headers. The above uses: `../java/jni.h` That might not be the same location for you. – Brandon Aug 26 '14 at 19:24
  • I really appreciate your help! I had to add a statement, JNIEnv *env in to get rid of the error but now it's giving me a warning. Then I tried to compile my main cpp (not the jni.cpp) to dll but it has link errors on JNI_CreateJAvaVM and JNI_GetJavaVMInit. The error look like this, 1>JVM.obj : error LNK2019: unresolved external symbol __imp__JNI_CreateJavaVM@12 referenced in function "public: __thiscall Jvm::Jvm(class std::basic_string,class std::allocator >)" (??0Jvm@@QAE@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) – wolf97084 Aug 26 '14 at 19:38
  • You need to link to `jvm.lib` in your "JDK_x.xx/lib" folder. For me it's at: `C:/Program Files/jdk1.xx_x/lib/jvm.lib`. – Brandon Aug 26 '14 at 20:15
  • I put the java/lib filepath in LIB, then set the jvm.lib as additonal dependencies under Linker/Input in property and put my java lib filepath in Additional library directories under Linker/General. But still got the same link error. Isn't this how you link in Visual Studio 2010? – wolf97084 Aug 27 '14 at 12:15
  • I finally got over the linking problem. Apparently I need to set my platform as x64. Now I am trying to solve the jvm.dll not found error during execution. – wolf97084 Aug 27 '14 at 12:57
  • Put the path to your java in your environment "PATH" variable. – Brandon Aug 27 '14 at 14:09
  • Thank you. That solve it. For some reason it's not finding my class. I did use FindClass("packageName/className") but it returned as nullptr. Do I need to include class path in optionString in Jvm::Jvm? – wolf97084 Aug 27 '14 at 14:52
  • Or should I move my .class file from under project/src/package to project/dist folder? – wolf97084 Aug 27 '14 at 14:56
  • I also tried debugging and found that it complains about the optionString in jvm_args, "CXX0030 Error: expression cannot be evaluated" Wonder if this has contributed to my class not found error. – wolf97084 Aug 27 '14 at 15:46
  • I have found the problem but don't know the cause of the problem. The problem is that the passed in jar filepath to VM is not being correctly put in the optionString. I then define the class path in JVM.cpp instead then it runs. – wolf97084 Aug 27 '14 at 19:11
  • I have one more question regarding the JVM code. I called many functions from my jar file in c++ code. The JVM is created the first time but cannot be created while calling second function. I then add DestroyJVM code in JVM.cpp (1 statement: delete this;) and call DestroyJVM before calling 2nd function but still not work. Was there a special way I need to do to destroy the JVM? Thank you. – wolf97084 Aug 29 '14 at 13:04
  • I shall add that I made two function calls in a C++ DLL project to call my java methods. Then call those two C++ functions resided in the DLL in a C++ console application. C++ console application will successfully create a JVM call function #1, but output failed to create JVM when calling function #2. – wolf97084 Aug 29 '14 at 13:18
  • You cannot spawn more than one JVM at any given instance. Why did you need to create multiple? Just create one JVM and call both functions. – Brandon Aug 29 '14 at 15:26
  • My dilemma is that the function in dll will be called twice in any normal C++ program, once at the beginning to record starting time and once before it ends to record ending time. I then called JNI_GetCreatedJavaVMs and tried to attachCurrentThread for the second function call but attachCurrentThread would crash the whole program. – wolf97084 Aug 29 '14 at 15:45
  • But you can do: `JVM jvm("your class path");` call your function, run some more code.. call your second function. It should work without having to attach a thread or create another VM. – Brandon Aug 29 '14 at 16:47
  • I guess the only problem is we are making invoking JVM implicit to the other program so invoking JVM is done in each function in the DLL code. When a function is called, it will always try to invoke JVM. – wolf97084 Aug 29 '14 at 17:19
  • What are the usage of InitClassLoader(), DefineClass() and REgisterNativeMethod() and when to use them? – wolf97084 Sep 03 '14 at 20:13
  • The usage is to be able to take a current java class and load it into the JVM. Register native method allows you to register native functions for a java class from C++. See here: http://stackoverflow.com/questions/1010645/what-does-the-registernatives-method-do Define class will do the same thing as a class loader in java. Look up "java ClassLoader". – Brandon Sep 03 '14 at 21:12
  • I thank you so much for all your help. It's nothing but fantastic! – wolf97084 Sep 04 '14 at 12:05
  • I have run into another problem. How do you invoke JVM with a jar file that reside on a network drive (Jvm VM(xxx.jar))? I tried Jvm VM(\\\\server\\share\xx.jar) but it would say class not found. Since I cannot mapped the network drive, is there any way around to do this? thank you. – wolf97084 Oct 07 '14 at 22:12
  • You might be able to get away with creating a blank JVM and then a url class loader and load the jar using that instead. – Brandon Oct 07 '14 at 23:01
  • My bad. I had a typo in the address. It works with shared drive jar. Next thing is to pass java object to c++, keep it there and pass it back to java in a different function call. JNI sure is challenging. – wolf97084 Oct 09 '14 at 12:11
  • Pass the Java object to JNI. Call CreateGlobalRef on the object. Store it wherever you want.. When you pass it back to Java, delete the global ref. – Brandon Oct 09 '14 at 19:03