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

参考资料: http://blog.csdn.net/crayonyi/article/details/7413017

                       http://www.cnblogs.com/youxilua/archive/2011/09/16/2178554.html

 

1.首先在java中声明本地方法

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

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编写

参考资料:http://blog.csdn.net/fdyang2008/article/details/7615708

http://msdn.microsoft.com/en-us/library/windows/desktop/aa390423(v=vs.85).aspx

http://blog.csdn.net/ly402609921/article/details/7446943

 

JNI构建好之后,可以进入WMI编程。根据微软的MSDN, WMI的C++编程是基于COM组件的,其实我也没学过COM,但是知道COM就是一种接口规范。既然知道是一种规范的话,那么就按着规范走就行了,没有什么难的。

这里的具体过程见代码。

 

	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);

	/*加入你的操作*/
	VariantClear(&vtProp); 
        pclsObj->Release();
    }

Notice:

1.在windows2000的平台上,在进行CoInitializeSecurity时需要对 void*  pAuthList赋一个具体的值而不能是NULL。这里我赋值为RPC_C_AUTHN_LEVEL_DEFAULT。其他的赋值参考MSDN

http://msdn.microsoft.com/en-us/library/aa393617.aspx

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中文传输解决方案

http://www.cnblogs.com/mingzi/archive/2009/09/14/1566573.html

JNI如何传递参数

http://blog.csdn.net/wangkr111/article/details/7883461

这个解决得更加彻底

http://www.cnblogs.com/lifesting/archive/2010/10/11/1846909.html

你可能感兴趣的:(java)