您的位置:首页 > 编程语言 > Java开发

利用JNI方法,通过WMI获取本地硬件信息(主板型号,硬盘序列号,CPU参数等)

2014-10-31 16:38 801 查看
因为有几台服务器是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中声明本地方法

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

参考资料: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就是一种接口规范。既然知道是一种规范的话,那么就按着规范走就行了,没有什么难的。

这里的具体过程见代码。

 

<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 h
4000
r = 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

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  wmi jni java 硬件
相关文章推荐