更新(2013年2月23日):分开测试Hook,它有效。问题是您不能在.dll中使用printf
,除非您将控制台与它关联。
我用printf
替换每个fprintf
并在日志文件中写入输出。
更新(2013年2月22日):以正确的方式初始化变量clazz
。
我想在我的java应用程序中调用一个方法,如果有任何桌面窗口是created/activated/destroyed
。
我编写了一个带有两个本机函数的简单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");
}
CBTHook
有效,但我无法在setstatus
函数中调用callback
函数。
(*env)->CallVoidMethod(env, clazz, mid);
没有做任何事情,也没有抛出异常。
我该怎么做?
此外,我是否必须共享全局变量:mid
,clazz
和jvm
?
#pragma data_seg("SHARED")
段是“共享”,这意味着它们不是特定进程的唯一。
如果有必要,附加/分离其他线程的正确方法是什么?
答案 0 :(得分:0)
由于缺乏关于“不接收消息”的确切含义的明确解释,我将推测您的CBTProc
被称为,并且你只是没有获得JVM回调。 如果未调用CBTProc
,那么它根本不是JNI问题。
你做得差不多。 jmethodID
可以在线程之间和JNI调用之间重用,因此可以像您一样缓存它。在JavaVM*
中获取starthook()
并缓存它的方式也是正确的。你对jclass clazz
的看法是错的。您尝试将其缓存在starthook()
中,然后尝试不仅在不同的调用中使用它,而且很可能甚至在不同的线程中使用它。从GetObjectClass
获得的是本地引用,这意味着在当前JNI调用退出到JVM之后无法确保其有效性。它可能会奏效,但你不能依赖它。当从不同的线程使用时,肯定将无效。您必须创建全局参考:
jclass localRef = (*e)->GetObjectClass(e,o);
clazz = (*e)->NewGlobalRef(e,localRef);
然后,您可以按照clazz
注释掉的方式使用CBTProc
。剩下的唯一工作是stophook
:
(*e)->DeleteGlobalRef(e,clazz);
这样它就不会泄漏。
底部注释:我不明白“分享全局变量”是什么意思。