利用JNI方法,通過WMI獲取本地硬體資訊(主機板型號,硬碟序列號,CPU引數等)
因為有幾臺伺服器是windows 2000的,之前採用的WMIC方法不適用(windows 2003開始有),更不用說WMI .NET了(採用.NET Framework 3.5),只能採用JNI的方法,通過C++來呼叫本地WMI介面來查詢資料。幸好,這次WMI從windows 2000就開始有,如果再之前的版本我就沒辦法了。
程式結構:
在java中宣告本地方法,然後呼叫C++編寫的DLL,在C++中實現WMI查詢,返回結果到java,最後由java封裝成XML檔案。
具體過程
(1) 構建JNI
1.首先在java中宣告本地方法
</pre><pre name="code" class="cpp">public native String GetInfoFromWMI(String head, String content);
2.然後用命令列cd到java檔案所在目錄,執行javac編譯。如我的是javac GetHWInfo.java
3.編譯好之後cd.. 到上級目錄,執行javah 包名.類名。如我的是 javah WMI.GetHWInfo
這時候就在檔案目錄下得到一個.h的標頭檔案了。(我的是WMI_GetHWInfo.h),其中包名和類名之間由下劃線隔開。
Notice:
編譯時在java檔案中只需要宣告好C++檔案中需要的本地方法就足夠了,其他要用到的方法可以先不用管或先註釋掉。事實上,因為我們還可能引用到其他的jar包,而這些包又有可能不在我們的src下面,比如我引用到dom4j.jar,那麼在編譯時就需要通過classpath關鍵字加上其他jar的引用,如果包多起來還是挺麻煩的。所以可以先編譯必要的native方法,反正編譯出來的標頭檔案少幾個無關的方法也不影響。
4.將編譯好的標頭檔案新增到vs解決方案裡面。我用的是vs2008, 操作就是專案->新增項->已有項->WMI_GetHWInfo.h. 然後在程式中#include WMI_GetHWInfo.h.
5.開啟標頭檔案,把裡面的<jni.h>改成”jni.h”,因為<>的話只在本路徑中找,而””會在本路徑找不到的時候再去外圍路徑找,這樣才能找到jni.h
6.在{JAVA_HOME}/include/中的jni.h和jni_md.h按照上面4的方式加進來。
7.複製WMI_GetHWInfo.h中的方法名,比如我的是
JNIEXPORTjstring JNICALL Java_WMI_GetHWInfo_GetInfoFromWMI
(JNIEnv * env,jobject, jstring head, jstring content)
後面就可以開始實現之了。
(2) COM編寫
JNI構建好之後,可以進入WMI程式設計。根據微軟的MSDN, WMI的C++程式設計是基於COM元件的,其實我也沒學過COM,但是知道COM就是一種介面規範。既然知道是一種規範的話,那麼就按著規範走就行了,沒有什麼難的。
這裡的具體過程見程式碼。
<span style="white-space:pre"> </span>setlocale(LC_ALL, "chs"); //設定本地化語言, 沒有的話中文輸出為亂碼
//初始化COM元件
HRESULT hres;
hres = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hres))
{
cout<< "fail to initialize COM library"<< hres<< endl;
return refalse;
}
//初始化COM元件的安全等級
hres = CoInitializeSecurity(
NULL,
-1,
NULL,
NULL,
RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IMPERSONATE,
RPC_C_AUTHN_LEVEL_DEFAULT, // If in Windows 2000: need to specify a the default authentication credentials
// for a user by using a SOLE_AUTHENTICATION_LIST structure in the pAuthList
// parameter of CoInitializeSecurity
EOAC_NONE,
NULL
);
if (FAILED(hres))
{
CoUninitialize();
cout << "fail to initial security level" << hres << endl;
return refalse;
}
//通過呼叫CoCreateInstance初始化WMI的定位器(IWbemLocator型別的例項)
IWbemLocator *pLoc = NULL;
hres = CoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID *) &pLoc);
if (FAILED(hres))
{
CoUninitialize();
cout << "fail to create IWemLocator object" << hres << endl;
return refalse;
}
//呼叫IWbemLocator::ConnectServer方法,通過這個定位器連線到WMI的名稱空間,
//通過把一個IWbemServices的例項以引數形式傳遞給ConnectServer方法,就會建立這個服務。
//如我們需要一些BIOS資訊,那麼需要使用的WMI提供程式是Win32_BIOS,則需要連線到ROOT//CIMV2名稱空間中。
IWbemServices *pSvc = NULL;
hres = pLoc->ConnectServer(
_bstr_t(L"ROOT\\CIMV2"),
NULL,
NULL,
0,
NULL,
0,
0,
&pSvc
);
if (FAILED(hres))
{
pLoc->Release();
CoUninitialize();
cout << "cannot connect IWbmService" << hres<< endl;
return refalse;
}
//設定代理的安全等級
hres = CoSetProxyBlanket(
pSvc,
RPC_C_AUTHN_WINNT,
RPC_C_AUTHZ_NONE,
NULL,
RPC_C_AUTHN_LEVEL_CALL,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
EOAC_NONE
);
if (FAILED(hres))
{
pSvc->Release();
pLoc->Release();
CoUninitialize();
cout << "fail to set proxy blanket" << hres << endl;
return refalse;
}
//通過IWbemServices *pSvc建立一個WQL查詢,結果儲存在pEnumerator中
IEnumWbemClassObject* pEnumerator = NULL;
hres = pSvc->ExecQuery(
bstr_t("WQL"),
bstr_t("SELECT * FROM Win32_Processor"),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator);
if (FAILED(hres))
{
cout << "Query for operating system name failed." << hres << endl;
pSvc->Release();
pLoc->Release();
CoUninitialize();
return refalse; // Program has failed.
}
// 列舉pEnumerator,獲得*pclsObj,通過Get方法獲取特定欄位值:vtProp.bstrVal
IWbemClassObject *pclsObj;
ULONG uReturn = 0;
while (pEnumerator)
{
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1,
&pclsObj, &uReturn);
if(0 == uReturn)
{
break;
}
VARIANT vtProp;
//vtProp.bstrVal is what you need.
hr = pclsObj->Get(stmtcont, 0, &vtProp, 0, 0);
//VariantClear(&vtProp);
//hr = pclsObj->Get(L"Capacity", 0, &vtProp, 0, 0);// example "sp3"
//VariantClear(&vtProp);
//hr = pclsObj->Get(L"Name", 0, &vtProp, 0, 0); // example x64
//VariantClear(&vtProp);
/*加入你的操作<span style="font-family: Arial, Helvetica, sans-serif;">*/</span>
VariantClear(&vtProp);
pclsObj->Release();
}
Notice:
1.在windows2000的平臺上,在進行CoInitializeSecurity時需要對 void* pAuthList賦一個具體的值而不能是NULL。這裡我賦值為RPC_C_AUTHN_LEVEL_DEFAULT。其他的賦值參考MSDN
2.WMI有很多很多的欄位可以供我們查詢,那怎麼知道我們需要查哪一個呢?這裡提供兩種方法
第一種 關鍵字+WMI->必應。 比如 os wmi->必應。這樣就可以獲得答案。事實上當google不行了之後我發現必應還是挺好用的。
第二種,神器WMI Code Creator,你可以輕鬆查到所有的可查詢欄位,以及可以自動生成VB和C#查詢程式碼。可惜我用不到= =。下載地址是
http://msdn.microsoft.com/en-us/library/aa393617.aspx
(3) 返回Java
在結束前先把記憶體和物件釋放掉。
然後在Java根據返回的字元進行處理即可。我在java中將獲得的結果封裝成XML檔案。這裡用了dom4j.jar包進行處理。Java的操作就簡單多了啊,主要用到下面幾個方法
Document document =DocumentHelper.createDocument();
Element e =document.addElement("info"); //建立根節點
e.addCDATA(totalSpace+"");
就這麼簡單。
後面可以改進的,在C++中加入可丟擲異常,然後在java端捕獲。參考資料http://blog.csdn.net/a345017062/article/details/8068932
將整個字串陣列的處理交給C++完成從而減少COM元件初始化的耗時。
Notion:學習JNI和COM的過程,感覺最麻煩的就是各種資料型別的轉換了,我主要用到的還只是字串型別的轉換。下面寫一些小收穫。
1.帶_T的表示unicode編碼,帶w的表示寬字元格式。凡是有中文的都必須是帶Unicode編碼的寬字元儲存。(如wchar_t,wstring)這個在JNI和COM中非常重要,因為java和C++中的編碼格式是不一樣的,所以在傳輸過程中都用了同意的UTF-16編碼格式。因此在C++端要傳輸或者接受java傳過來的資料(特別是帶中文的時候),就需要在C端用wchar_t*這種型別來接收。
2.jchar和wchar_t是等價的,可以輕鬆進行強制型別轉換。
3.字串操作在C++中會容易很多,因為在C++中有string型別,可以有各種API對字串進行拼接、複製等操作,而不用像C裡面先宣告變數,再初始化空間,再用strcpy和strcat來翻來覆去的操作。Const char*轉成string很簡單,直接轉換;而string轉成const char*用c_str()方法即可。
4.事實上在MFC裡面也有CString型別,類似於string,只要#include “afx.h”即可。
5.字串賦值的方法,除了strcpy,wsprintf也是很好用的。
附上字串操作參考資料:
JNI中文傳輸解決方案
JNI如何傳遞引數
這個解決得更加徹底
http://www.cnblogs.com/lifesting/archive/2010/10/11/1846909.html