我们知道C++的每个窗体都对应着一个句柄HWND,通过这个句柄我们可以对这个窗口以及它的资源进行各种操作,比如让它前端显示等。那么操作系统给Java编写的窗体(Frame或者JFrame的对象或者子类对象)有没有分配这个唯一的HWND呢?如果存在的话我们怎么来在Java程序中得到它并利用它来操作这个窗口呢?
用spy++查看一下,Frame或者JFrame的对象或者子类对象确实都是存在这个窗口句柄的,那么我们怎么来提到它呢?有人可能会问了,得到这个HWND有什么用呢,它可以帮我们实现什么JDK还没有给我们提供的功能呢?JDK1.5为我们提供了窗体最前的方法, JDK1.4为我们提供了窗口最小化到系统托盘的相关API……
相信大家都用过QQ吧,在你把聊天窗口最小化,当收到消息时任务栏的标题按钮就会闪一下以提示用户收到了新的消息,如果现在让你用Java来实现这个小小的功能,你要如何做呢?我相信你不用JNI还是办不到的吧,不光这点,要实现Java的不规则窗体,如果用到JNI的话,那将事半功倍。要在C++中操作Java生成的窗体,就必须先要得到Java应用程序的窗口句柄。
下面我就以用Java实现那个闪烁标题的功能来说一下怎么在C++中操作Java生成的窗口。这部分用到了JNI,建议你在继续前先熟悉一下JNI的相关内容。
1: 安装JDK和VC件并把JDK所带的头文件(jni.h jni_md.h jawt.h jawt_md.h)全部放入VC的Include目录里
2:在一个Java类(JNIWindowUtilProxy.java)中定义两个方法分别如下
// 闪烁任务栏的标题按钮
public static native void flashTaskTitle(int HWND);
// 得到这个Java窗体的窗口句柄HWND
public static native int getWindowHWND(String jawtpath,
Frame target);
3:编译上面的java文件并使用javah JNIWindowUtilProx命令生成其对应的.h头文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNIWindowUtilProxy */
#ifndef _Included _JNIWindowUtilProxy
#define _Included _JNIWindowUtilProxy
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JNIWindowUtilProxy
* Method: flashTaskTitle
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_JNIWindowUtilProxy_flashTaskTitle
(JNIEnv *, jclass, jint);
/*
* Class: JNIWindowUtilProxy
* Method: getWindowHWND
* Signature: (Ljava/lang/String;Ljava/awt/Frame;)I
*/
JNIEXPORT jint JNICALL Java_JNIWindowUtilProxy_getWindowHWND
(JNIEnv *, jclass, jstring, jobject);
#ifdef __cplusplus
}
#endif#endif
4:在VC中新建一个动态链接库工程并把上一步生成的头文件放到工程里
5:编写一个C++源文件来具体实现头文件中的两个方法
至于Java_JNIWindowUtilProxy_flashTaskTitle这个方法,只要有一个窗口的HWND做参数,调用C++为我们提供的现成的方法FlashWindow就行了, 而这个HWND则可以通过Java_JNIWindowUtilProxy_getWindowHWND这个方法的返回值来取得。所以Java_JNIWindowUtilProxy_flashTaskTitle的具体实现如下
/*
* Class: JNIWindowUtilProxy
* Method: flashTaskTitle
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_com_caokai_jni_JNIWindowUtilProxy_flashTaskTitle
(JNIEnv *, jclass, jint hwnd)
{
HWND h = (HWND) hwnd;
FlashWindow(h, true);
}
下面关键就是得到Java窗体的窗口句柄HWND,也就是方法JNICALL Java_JNIWindowUtilProxy_getWindowHWND的具体实现了。要实现这个功能所需要的代码量比较大,但是如果分为以下几步就变得很简单了。
1:分别定义下面四个结构体(与图形绘制相关)的一个对象以备后面之用
JAWT awt;
JAWT_DrawingSurface *ds;
JAWT_DrawingSurfaceInfo *dsi;
JAWT_Win32DrawingSurfaceInfo *win;
2:加载jawt.dll这个动态链接库(采用运行时加载的方式)
加载动态链接库时而要提供它的路径,也就是Java文件中的jawtpath那个参数,而在C++中对应的是jstring,由于jawt.dll是位于JRE目录的bin目录里,所以我们的jawtpath应该用下面的方法来赋值
jawtpath = System.getProperty("java.home")
+ System.getProperty("file.separator")
+ "bin"
+ System.getProperty("file.separator")
+ "jawt.dll";
而加载动态链接库的具体代码如下
// Load the jawt dynamic library.
const char *path = env -> GetStringUTFChars(jawtpath, JNI_FALSE);
HMODULE HJAWT = LoadLibrary(path);
env -> ReleaseStringUTFChars(jawtpath, path);
if(HJAWT == NULL)
{
return -1;
}
3:得到动态链接库中JAWT_GetAWT这个函数以初始化JAWT
// Get the function address from jawt.dll
GETAWT JAWT_GetAWT =
(GETAWT) GetProcAddress(HJAWT, "_JAWT_GetAWT@8");
if (JAWT_GetAWT == NULL)
{
return -1;
}
4:利用上一步提到的函数来初始化JAWT
// Get the JAWT
awt.version = JAWT_VERSION_1_3;
result = JAWT_GetAWT(env, &awt);
if (result == JNI_FALSE)
{
return -1;
}
5:得到与图形绘制相关的对象
ds = awt.GetDrawingSurface(env, window);
ds -> Lock(ds);
dsi = ds -> GetDrawingSurfaceInfo(ds);
6:得到JAWT_Win32DrawingSurfaceInfo 员hwnd的值
HWND hwnd = win -> hwnd;
7:释放所用的资源
ds->FreeDrawingSurfaceInfo(dsi);
ds->Unlock(ds);
awt.FreeDrawingSurface(ds);
FreeLibrary(HJAWT);
8:返回刚才得到的HWND对象
return (int)hwnd;
通过上面的几步就可以得到Java窗口的句柄了,得到了之后就可以通过它来操作这个窗口了,比如来实现我上面提到的闪烁标题的功能等等。下面是我的程序运行时的屏幕截图
1:闪烁标题栏按钮
2:得到窗口句柄的值
2:使窗口成为椭圆形状
注:1:这里的40788是十六进制的整数,与spy++所查出的结果是一致的。
2:如果你的VC工程编译失败,请在前面加上下面一行代码
1:HWND与jint之间的转换会损失精度,所以应该把它与jlong进行转换
typedef jboolean (JNICALL *GETAWT)(JNIEnv*, JAWT*);
2:由于Visual Studio 2005的工程默认是支持国际化的,也就是说,它采用的是和Java一样的Unicode字符集,因此LoadLibrary函数的参数如果是const char *则编译不能通过,更正的代码如下:
path = env->GetStringUTFChars(jstr, &isCopy);
CString re = path;
HMODULE HJAWT = LoadLibrary(re);
要用到CString类的话,要引入atlstr.h这个头文件。