因为有几台服务器是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.打开头文件,把里面的
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