5

I am trying to call a Java method from the code. C code listens to either Escape, Shift, Ctrl key press, then it calls the Java method telling which key was pressed. Following are the snippets that play a role in this.

C Snippet:

mid = (*env)->GetMethodID(env,cls,"callBack","(Ljava/lang/String;)V");
Env = env;
if(called)
    switch(param) {
        case VK_CONTROL:
            printf("Control pressed !\n");
            (*Env)->CallVoidMethodA(Env,Obj,mid,"11"); // calling the java method
            break;
        case VK_SHIFT:
            printf("Shift pressed !\n");
            (*Env)->CallVoidMethodA(Env,Obj,mid,"10"); // calling the java method
            break;
        case VK_ESCAPE:
            printf("Escape pressed !\n");
            (*Env)->CallVoidMethodA(Env,Obj,mid,"1B"); // calling the java method
            break;
        default:
            printf("The default case\n");
            break;
    }

Java Snippet:

public void callBack(String key) {
    String x = KeyEvent.getKeyText(Integer.parseInt(key, 16));
    System.out.println(x);
}

When I run the program and press the Escape key I get this on the console:

Escape pressed !
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x5c8b809a, pid=7588, tid=8088
#
# JRE version: 7.0
# Java VM: Java HotSpot(TM) Client VM (20.0-b01 mixed mode, sharing windows-x86 )
# Problematic frame:
# V  [jvm.dll+0x19809a]
#
# An error report file with more information is saved as:
# W:\UnderTest\NetbeansCurrent\KeyLoggerTester\build\classes\hs_err_pid7588.log
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
#

I know I am calling the Java function the wrong way, but I don't know where I am wrong. As from the output, it satisfies the case when I press the Escape key and then an unexpected error occurs.

Link to the LOG FILE

EDIT:

After the answer by mavroprovato I still get the same errors.

I edited this way:

(*Env)->CallVoidMethodA(Env,Obj,mid,(*Env)->NewStringUTF(Env,"1B"));

EDIT:

COMPLETE CODE version 1

COMPLETE CODE version 2

Community
  • 1
  • 1
Suhail Gupta
  • 22,386
  • 64
  • 200
  • 328
  • You are missing the `break` for the Escape-case, by the way. – unwind Jun 05 '12 at 10:43
  • Can you create a minimal test-case? – Oliver Charlesworth Jun 05 '12 at 10:44
  • Post W:\UnderTest\NetbeansCurrent\KeyLoggerTester\build\classes\hs_err_pid7588.log – Rob Kielty Jun 05 '12 at 10:46
  • Is the problem the parameter you are passing to your Java method? Your method needs a java.lang.String, but you're passing a C string like "1B" and I'm not sure that is compatible. – mah Jun 05 '12 at 10:48
  • @Rob Kielty [Here is the log file](http://suhail03.my3gb.com/hs_err_pid7852.log) – Suhail Gupta Jun 05 '12 at 10:50
  • How do you attach to the JVM from your C code? I guess that this is from a C callback and then you will have to access the JVM through `AttachCurrentThread()`. But since we don't see all your code it is quite difficult. The `JNIEnv` pointer cannot be cached for example. – maba Jun 05 '12 at 11:23
  • @maba what is the point in telling `JNIENv` cannot be cached ? Is this step `Env=env` wrong ? – Suhail Gupta Jun 05 '12 at 11:25
  • We don't see the code you are using in your callback. The `JNIEnv` pointer cannot be cached between different threads. Where did you get hold of the `env` (not the `Env`) pointer in your snippet? And how did you get hold of your `Obj` reference? – maba Jun 05 '12 at 11:31
  • In your `initializeJNIVars` you are caching the `env` pointer which is not allowed! Your `LowLevelKeyboardProc` is most likely running in another thread and then you will have to use the `AttachCurrenThread()` method to get hold on the `JNIEnv`. And the `Obj` reference must be made global. – maba Jun 05 '12 at 11:39
  • @maba can you please explain this in detail in your answer. I don't exactly get it – Suhail Gupta Jun 05 '12 at 11:58
  • JNI coding is quite complex. You have to be aware of some pitfalls and especially when dealing with multiple threads. I just pointed out some _low hanging fruit_ in my answer. My advice is that you read the [JNI Specification](http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html) for details. I could add some more stuff in my answer but then on the other hand I would probably do most of your coding and that is probably not meaningful (at least not to me). I will see if I can add some more code anyway. – maba Jun 05 '12 at 12:04
  • You could maybe up-vote my answer since that is anyway the answer to why you have the JVM crash. – maba Jun 05 '12 at 12:06
  • Why are you using `CallVoidMethodA`, I would think that `CallVoidMethod` will do – bobby Jun 05 '12 at 12:54
  • @jogabonito how can you pass an argument using that? – Suhail Gupta Jun 05 '12 at 16:48
  • [link] (http://journals.ecs.soton.ac.uk/java/tutorial/native1.1/implementing/method.html) In your case it will be something like `CallVoidMethod(env, obj, mid, myJString)` Anyway this needn't be the cause of the crash. It looks to the env problem. Have a look at my answer below – bobby Jun 06 '12 at 04:14
  • CallVoidMethodV is for a variable arguments list and CallVoidMethodA is expecting some sort of a union – bobby Jun 06 '12 at 04:15
  • @jogabonito can't i pass a string using `callVoidMethodA()` ? – Suhail Gupta Jun 06 '12 at 04:18
  • In the link I shared, there is a section "Passing Arguments to Java Methods" with a description of `callVoidMethodA`. I haven't fully understood it, but I dont think it is the method you are looking for. I am sure that `callVoidMethod` works. Is there any special reason why you want to use `callVoidMethodA` – bobby Jun 06 '12 at 04:40
  • @jogabonito i want to pass arguments to the java method defined.And to me `CallVoidMethodA` should work fine – Suhail Gupta Jun 06 '12 at 04:51
  • Your code, your decisions :-) – bobby Jun 06 '12 at 04:55

3 Answers3

4

The JVM is crashing because the JNIEnv that is used is not a valid one. There are other issues with the code as well.

The Sun JNI documentation is providing very good information regarding threads.

Here comes some parts that are obvious:

Create a JNI_OnLoad function in your code. It will be called when the library is loaded. Then cache the JavaVM pointer because that is valid across threads. An alternative is to call (*env)->GetJavaVM in the initializeJNIVars function but I prefer the first one.

In your initializeJNIVars you can save the obj reference by calling Obj = (*env)->NewGlobalRef(obj).

In the LowLevelKeyboardProc you will have to get the env pointer:

AttachCurrentThread(JavaVM *jvm, JNIEnv &env, NULL);


Edit

OK, here are the code that you should add to get it working, I have tried it myself and it works. NB: I have not analyzed what your code is actually doing so I just did some fixes to get it working.

Add these variables among your other global variables:

static JavaVM *javaVM = NULL;
static jmethodID callbackMethod = NULL;
static jobject callbackObject = NULL;

You can remove your cls, mid, Env and Obj variables and use mine instead.

Create the JNI_OnLoad method where you cache the JavaVM pointer:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
    JNIEnv *env = 0;

    if ((*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_4)) {
        return JNI_ERR;
    }

    javaVM = jvm;

    return JNI_VERSION_1_4;
}

Alter your initializeJNIVars to look like the following:

void Java_keylogger_TestKeys_initializeJNIVars(JNIEnv *env, jobject obj) {
    jclass cls = (*env)->GetObjectClass(env,obj);
    callbackMethod = (*env)->GetMethodID(env, cls, "callBack", "(Ljava/lang/String;)V");
    callbackObject = (*env)->NewGlobalRef(env, obj);
    if(cls == NULL || callbackMethod == NULL) {
        printf("One of them is null \n");
    }
    called = TRUE;
}

And finally in your LowLoevelKeyboardProc code you will have to add the following:

...
WPARAM param = kbhook->vkCode;

JNIEnv *env;
jint rs = (*javaVM)->AttachCurrentThread(javaVM, (void**)&env, NULL);
if (rs != JNI_OK) {
    return NULL; // Or something appropriate...
}
...

    case VK_ESCAPE:
        printf("Escape pressed !\n");
        jstring message = (*env)->NewStringUTF(env, "1B");
        (*env)->CallVoidMethod(env, callbackObject, callbackMethod, message);
        break;
...

In your unregisterWinHook you should delete the global reference so that objects can be GC'd.

...
(*env)->DeleteGlobalRef(env, callbackObject);

And that's it.

maba
  • 47,113
  • 10
  • 108
  • 118
  • [i am getting errors on modifying like this](http://i50.tinypic.com/fxdwzl.jpg). Here are the [errors](http://i48.tinypic.com/a9l5hh.jpg) – Suhail Gupta Jun 05 '12 at 12:50
  • Sorry. My bad. Since you are not using c++ you have to pass env as first parameter then the others. – maba Jun 05 '12 at 13:00
  • like this : `env = (*jvm)->AttachCurrentThread(void( **)&env,jvm,NULL);` ? Getting the same errors – Suhail Gupta Jun 05 '12 at 13:09
  • You have to check the api documentation. I will help you soon. Have to get on a train... – maba Jun 05 '12 at 13:12
  • what is wrong with initializing `Obj` in the `initializeJNIVars` ? It doesn't act as a global var ? – Suhail Gupta Jun 05 '12 at 13:34
  • 1
    Yes it does. You have defined it globally in your c-module and it is initialized in `initializeJNIVars` and then you use it in `LowLevelKeyboardProc`. If you don't call `NewGlobalRef` then the JVM can remove it when it runs the GC. – maba Jun 05 '12 at 13:55
  • [my jvm crashes when it enters this case statement](http://i46.tinypic.com/2dw8fme.jpg) what could be the problem ? – Suhail Gupta Jun 06 '12 at 07:27
  • Though i have not tried your code yet,but have applied the methods/procedures you told me.[I have uploaded the code here,which still crashes my jvm when i press the escape key.](http://programworks.co.cc/bummers/crash.html) The call sequences are the same old one. Look exclusively for the `escape` case in the code – Suhail Gupta Jun 08 '12 at 08:02
  • You are not supposed to create a new JVM!!! You are running in a JVM so why create a new??????? – maba Jun 08 '12 at 08:05
  • first what does creating a jvm means exactly ? I did this to get the `env` in the _escape_ case block.`env = (*jvm)->AttachCurrentThread(jvm,&env,NULL);` – Suhail Gupta Jun 08 '12 at 08:09
  • Are you and @program-o-steve the same person? It looks like you just posted a question regarding since the code for creating the JVM looks EXACTLY the same: http://stackoverflow.com/questions/10943698/initializing-the-third-argument-of-jni-createjavavm. – maba Jun 08 '12 at 08:13
  • Please, look at my example and you will see how to store the JVM pointer. – maba Jun 08 '12 at 08:17
  • but can you please answer "what does creating a jvm means exactly" – Suhail Gupta Jun 08 '12 at 08:18
  • finally able to call the function. But i still don't know what does creating a jvm using `JNI_CreateJavaVM` mean ? what mistake was i actually committing in the code [I gave here](http://programworks.co.cc/bummers/crash.html)? – Suhail Gupta Jun 08 '12 at 10:42
  • [This link](http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/invocation.html) will tell you more about the Invocation API. Read it. – maba Jun 08 '12 at 13:33
3

I believe you cannot call a java method that takes a String parameter and pass it a char*. You should call NewStringUTF first.

mavroprovato
  • 8,023
  • 5
  • 37
  • 52
0

I think it is due to the UAC feature enabled on your Operating System. This was a bug for Java 6. Read this for further reference.

The reason I say this is because the event to the escape key is fired correctly and the problem only begins as soon as the call to the java method is done.

Kazekage Gaara
  • 14,972
  • 14
  • 61
  • 108