2010年1月25日 星期一

Java 呼叫 WinAPI

因為我自己是玩 Visual Basic 6.0出身的當初玩 VB 玩到
很無聊,是一直到無意間買到 WinAPI on VB6 的書,才
開始繼續對 VB 有興趣。一直到接觸了 Java 之後,還是對
Windows API 抱著很大的興趣,一直想要用 Java 呼叫 WinAPI
但是都不得其門而入。過了一年多,現在想想當初真的很好笑
Java 操作 WinAPI 根本破壞了 Java 本身最大的特點:跨平台。
現在用 Java 呼叫 WinAPI 已經是不得以的情況才會用了....
如果有人想要用 Java 呼叫 Windows API,就參考看看吧。

【本文開始】

先備知識:Java、C、WindowsAPI
使用軟體:Dev C++ (Windows)

當安裝好 JDK 和 Dev C++ 之後,要先作一件事情,請到 JDK 安裝
目錄(我的例子是安裝在C:\Program Files\Java\jdk1.6.0_17)
找到 include 資料夾,並把裡面的所有檔案,都複製到 Dev C++ 安
裝目錄下的 include 中。這樣子 Dev C++ 在編譯檔案的時候,才抓的
到需要的 Header File。

因為 Java 不能透過 JNI 直接呼叫 Windows API,必須得另外撰寫一個
DLL 去連結 WinAPI。所以這邊要利用 自己撰寫的 DLL 去連結 WinAPI。

首先編寫好 Java 部分的程式,假設這邊要呼叫 Windows API 的 ShowWindow
方法,讓輸入的 hWnd 視窗隱藏起來:
/* In File WinAPI.java */
public class WinAPI
{
static{System.loadLibrary("WinAPI");}
public static native int setWindowVisible(int hWnd,int state);
}



首先說明一下, static{} 是類別程式區塊,會在類別載入時就執行
由於這個類別需要引用 DLL 檔(Library),所以要先寫在這邊。
而其中的 WinAPI 就是 dll 的檔名,不用和這個 Class 名稱一樣
只是方便識別而已。

要注意的是 native,告知 Java 這是一個 Native Method。

編譯這個檔案: javac WinAPI.java

接著,要根據這個檔案製作出對應的標頭檔,指令是:
javah WinAPI
(javah Class名稱)

成功的話會產生一個 .h 的檔案,內容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class WinAPI */
#ifndef _Included_WinAPI
#define _Included_WinAPI

#ifdef __cplusplus
extern "C" {
#endif
/* * Class: WinAPI
* Method: setWindowVisible
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_WinAPI_setWindowVisible
(JNIEnv *, jclass, jint, jint);

#ifdef __cplusplus
}
#endif

#endif

宣告的方式就是:
JNIEXPORT 傳回型態 JNICALL Java_Class名稱(以_取代.)_方法名稱

剩下的就是一般的宣告,接著開啟 Dev C++ 新增一個 C 語言的
DLL 專案:


接著再開啟的檔案裡面,會有一個 dll.h,把這邊的內容用剛剛產生出來
的文件取代掉。

接著看到開啟的 dllmain.c 把裡面的 HelloWorld 方法整個砍掉(這是預設的範例)

接著,複製 dll.h 裡面的方法宣告到 dllmain.c 裡面,並且給予變數名稱。
(紅色字部分表示增加的)
JNIEXPORT jint JNICALL Java_WinAPI_setWindowVisible
(JNIEnv *jvm, jclass jCls, jint hWnd, jint state)
{

}

這邊要注意兩個特殊參數: jvm 和 jCls (變數名稱可自訂)
jvm 代表這個 JVM 的實體,也就是說可以利用它來直接操作 JVM
jCls 是呼叫的 Java Class 實體,在應用到 CallBack 時會需要,不過
這個範例沒有要用到,所以先不用理它。

接著就是在裡面撰寫要呼叫的 WinAPI 了。先來看 ShowWindow 這個方法:

BOOL ShowWindow(HWND hWnd,int nCmdShow);

但是我們從 Java 那邊接收到的型態卻是兩個 jint,該怎麼辦呢?

請開啟 jni.h (到 dll.h 那邊,找到 #include 按住 Ctrl 再點 jni.h 就好了)
再找到 #include "jni_md.h" 一樣開啟 jni_md.h 看,可以看到:
typedef long jint;

原來 jint 就是 long,所以可以對他強制轉型啦。

我們直接在方法裡面呼叫:
return (jint)ShowWindow((HWND)hWnd,(int)state);

完整如下:
JNIEXPORT jint JNICALL Java_WinAPI_setWindowVisible
(JNIEnv *jvm, jclass jCls, jint hWnd, jint state)
{
  
return (jint)ShowWindow((HWND)hWnd,(int)state);
}

接著就可以編譯了。編譯成功之後,應該會輸出一個 WinAPI.dll
把它放到 WinAPI.class 的目錄底下,讓 Java
loadLibrary 的時候
可以抓到。

接著再撰寫一個 class(TEST.class 和 WinAPI.class 在同目錄下):

/* In File TEST.java */

import java.util.*;
public class TEST
{
public static void main(String[] args)
{
Scanner ipt=new Scanner(System.in);
System.out.print("請輸入 hWnd:");
int hWnd=ipt.nextInt();
System.out.print("輸入選項: (1)隱藏 (2)顯示 (0)離開:");
while(ipt.hasNextInt())
{
int tmp=ipt.nextInt();
switch(tmp)
{
case 0:
System.exit(0);
break;
case 1://SW_HIDE=0
WinAPI.setWindowVisible(hWnd,0);
break;
case 2://SW_SHOW=5
WinAPI.setWindowVisible(hWnd,5);
break; 
}
System.out.print("輸入選項: (1)隱藏 (2)顯示 (0)離開:");
}
}
}
執行,就可以得到我們要的結果了。

範例程式下載:http://w5.loxa.com.tw/kk6/Java/JNI.rar
沒有分類,反正 .java 和 .class 是屬於 Java 的,剩下的就是 Dev C++

沒有留言:

張貼留言