如何在jni中创建CBTHook,然后在回调函数中调用java函数?

如何在jni中创建CBTHook,然后在回调函数中调用java函数?,java,c,callback,java-native-interface,Java,C,Callback,Java Native Interface,更新(23/02/13):对挂钩进行了单独测试,结果正常。问题是不能在.dll中使用printf,除非将控制台与之关联 我将每个printf替换为fprintf,并将输出写入日志文件 更新(2013年2月22日):以正确的方式初始化变量clazz 如果创建/激活/销毁了任何桌面窗口,我想在java应用程序中调用一个方法 我编写了一个简单的Java类,其中包含两个本机函数: public class Hook { public Hook(){} public void setstatus(){

更新(23/02/13):对挂钩进行了单独测试,结果正常。问题是不能在.dll中使用
printf
,除非将控制台与之关联

我将每个
printf
替换为
fprintf
,并将输出写入日志文件

更新(2013年2月22日):以正确的方式初始化变量
clazz

如果创建/激活/销毁了任何桌面窗口,我想在java应用程序中调用一个方法

我编写了一个简单的Java类,其中包含两个本机函数:

public class Hook {

public Hook(){}

public void setstatus(){
    System.out.println("status set");
}

public native void starthook();
public native void stophook();
}
我用C写了以下内容:

#include "Hook.h"
#include <windows.h>
#include <jni.h>
#include <stdio.h>

#pragma data_seg("Shared")
HHOOK   g_hHook  = NULL;
FILE *pFile=NULL;
//do mid, clazz and jvm have to be shared ?
//jmethodID mid=NULL;
//jclass clazz=NULL;
//static JavaVM *jvm = NULL;

#pragma data_seg()

HINSTANCE   g_hInstance = NULL;

BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved ){
    switch (ul_reason_for_call){
    case DLL_PROCESS_ATTACH:
        g_hInstance  = (HINSTANCE) hModule;
        break;

    case DLL_THREAD_ATTACH:  break;
    case DLL_THREAD_DETACH:  break;
    case DLL_PROCESS_DETACH: break;
    }

    return TRUE;
}

LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam){
    if (nCode < 0)
        return CallNextHookEx(g_hHook, nCode, wParam, lParam);       
    /*
    JNIEnv *env;
    int res =(*jvm)->AttachCurrentThread(jvm,(void **)&env, NULL);
        if(res<0){
            fprintf(pFile,"AttachCurrentThread failed\n");
            return 0;
        }
    */

    HWND hWnd = (HWND)wParam;
    if (!hWnd){
        fprintf(pFile,"hWnd==NULL\n");
        //(*jvm)->DetachCurrentThread(jvm);
        return 0;
    }
    if (nCode == HCBT_ACTIVATE){            
        // is printed
        fprintf(pFile,"activated\n");
        // isn't called
        //(*env)->CallVoidMethod(env, clazz, mid);
        // Nothing is logged
        //if((*env)->ExceptionOccurred(env))
        //      (*env)->ExceptionDescribe(env);
    }
    else if (nCode == HCBT_DESTROYWND){
        fprintf(pFile,"destroyed\n");
    }
    else if (nCode == HCBT_CREATEWND){
        fprintf(pFile,"created\n");
    }
    //(*jvm)->DetachCurrentThread(jvm);
    return 0;
}

JNIEXPORT void JNICALL Java_Hook_starthook(JNIEnv *e, jobject o){
/*
if(jvm==NULL)
    if(((*e)->GetJavaVM(e,&jvm))<0){
        printf("GetJavaVM failed\n");
        return;
        }
    jclass localRef = (*e)->GetObjectClass(e, o);
        clazz = (*e)->NewGlobalRef(e,localRef);
    if(clazz==NULL){
        printf("GetObjectClass failed\n");
        return;
    }
    mid = (*e)->GetMethodID(e, clazz, "setstatus", "()V");
    if(mid==0){
        printf("GetMethodID failed\n");
        return;
    }
*/
    pFile = fopen("C:/workspace/CBTHook/log.txt","a");
    g_hHook      = SetWindowsHookEx(WH_CBT, (HOOKPROC) CBTProc, g_hInstance, 0);
    printf("Hook started\n");
}

JNIEXPORT void JNICALL Java_Hook_stophook(JNIEnv *e, jobject o){
    if (g_hHook){
        UnhookWindowsHookEx(g_hHook);
        g_hHook = NULL;
        //(*e)->DeleteGlobalRef(e,clazz);
    }
    if(pFile)
        fclose(pFile);
    }
    printf("Hook stopped\n");
}
不执行任何操作,也不会引发异常

我该怎么办

此外,我是否必须共享全局变量:
mid
clazz
jvm
? 表中的每个变量

#pragma data_seg("SHARED")
段是“共享的”,这意味着它们不是特定流程所独有的


如果有必要,附加/分离其他线程的正确方法是什么?

鉴于没有明确解释“不接收消息”的确切含义,我推测您的
CBTProc
正在被调用,而您没有得到JVM回调。如果未调用
CBTProc
,则根本不是JNI问题

你几乎做对了
jmethodID
在线程之间和JNI调用之间是可重用的,因此它可以像您所做的那样被缓存。在
starthook()
中获取
JavaVM*
并缓存它的方法也是正确的。关于
jclass-clazz
,您只错了。您尝试将它缓存在
starthook()
中,然后尝试不仅在不同的调用中使用它,而且很可能在不同的线程中使用它。您从
GetObjectClass
获得的是一个本地引用,这意味着在当前JNI调用退出到JVM后,它的有效性无法得到保证。它可能有用,但你不能依赖它。当从不同的线程使用时,它肯定不会工作。您必须进行全局引用:

然后,您可以按照
CBTProc
中注释掉的方式使用
clazz
。剩下的唯一工作是在
止动钩中

(*e)->DeleteGlobalRef(e,clazz);
这样它就不会泄漏


下面的注意事项:我不明白你说的“共享全局变量”是什么意思。

嘿,谢谢你的快速回复。我对“不接收消息”的意思是,例如,如果java应用程序的窗口被激活,则会打印回调函数中的“printf(“activated\n”);”,但如果其他应用程序的窗口被激活,则不会打印。Process Viewer显示Hook.dll已挂接在该应用程序中。我创建了一个全局引用,然后尝试调用“(*env)->CallVoidMethod(env,clazz,mid)”;但是现在,如果我将“(*env)->CallVoidMethod(env,clazz,mid);”放在“printf(“activated\n”);”上方,则不会打印任何内容。请参阅下一个注释…如果我将其反转,则会打印“printf(“activated\n”);”,但“(*env)->CallVoidMethod(env,clazz,mid);”仍然不起任何作用。此外,我询问是否发生异常,但未记录任何内容。我所说的“共享全局变量”是指“#pragma data_seg(“共享”)部分。该段中的每个变量都是“共享的”,这意味着它们不是特定流程的唯一变量。我的问题是,如果我在该部分中输入的每个变量都必须共享。PS:对不起,我的英语不好。PPS:我会对你的答案投赞成票,但我还没有足够的声誉。-S.O.程序提示:如果你被要求澄清,请将其添加到你原来的帖子中,或许可以将其命名为“更新”之类。注释不是为它准备的,如果它包含代码就更糟糕了。它不可读。你应该一个一个地解决问题。我知道你甚至不确定你的钩子每次都被调用,每扇窗户。因此,去掉对JNI的所有依赖,从
DllMain
调用
SetWindowHookEx
,并在它可靠工作时返回。然后我们可以处理JNI回调。嘿,钩子可以用了。请参阅原始帖子中的更新。
jclass localRef = (*e)->GetObjectClass(e,o);
clazz = (*e)->NewGlobalRef(e,localRef);
(*e)->DeleteGlobalRef(e,clazz);