1. 程式人生 > >利用JNI方法,通過WMI獲取本地硬體資訊(主機板型號,硬碟序列號,CPU引數等)

利用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