WMI For C++/COM基础知识
知识背景
WBEM——Web Based Enterprise Management是一种行业规范,建立了在企业网络中访问和共享管理信息的标准。
WMI——Windows Management Instrumentation是WBEM的Windows实现。通过WMI,我们可以获取关于硬件\软件的数据,也可以提供关于硬件或软件服务的数据给WMI。
CIM——Common Information Model(公共信息模型)。在WMI架构中,CIM对象部件是结构的中心部件,控制着信息的流程。CIM把管理区域分为物理区域和逻辑区域,他们分别对应管理环境的物理和逻辑元素。而逻辑域又可进一步分为系统域、设备域、应用程序域和网络域。
WMI整体架构
可以在MSDN上找到WMI的架构图,虽然说MSDN提供的架构图很详细,但是也很复杂,初学者学习起来可能会晕晕的,可以做如下的简化:
管理应用程序是任何一种语言编写的程序,如脚本语言、C++、C#等。
WMI核心是基于CIM的对象管理器,用于处理管理数据和管理应用程序的静态或动态的资源。
WMI库是用于静态管理数据的CIM中央储备库。类、实例和属性都在这个库中保存,是对象定义的数据库。
提供程序是一些COM对象,他们直接与被管理的部件(如Win32系统、注册表、目录服务等)进行交互。他们的信息是动态生成的。这些信息包含了来自管理应用程序请求的响应或被管理环境中的变更通知。是WMI和操作系统及其组件的中介。
被管理对象是被管理环境中的逻辑和物理对象。如硬盘,光驱,进程等。
获取数据流程
提供程序生成的数据可以存放在CIM储备库(静态)或者对来自CIM对象管理器的请求作出相应时,动态传递。实际中采取的机制,是根据数据变更的频率来决定的。CIM储备库的更新影响这系统的性能。因此,如果提供程序监控的被管理部件的状态经常变化,数据管理就倾向于动态(如笔记本的Monitor切换);如果数据是比较静态的(如BIOS版本),到储备库就会终止。这决定了管理应用程序、CIM对象管理器、CIM储备库和提供程序之间的信息流程。
数据的获取一般包括如下步骤:
1)管理应用程序发送请求给CIM对象管理器。
2)CIM对象管理器确定请求数据是静态的还是动态的。
3)如果数据是静态的,那么CIM对象管理器就直接从储备库中获取数据;如果对象是动态的,那么CIM根据提供程序的注册信息把请求引向恰当的提供程序。
4)提供程序提取数据后把它发回到CIM对象管理器,然后CIM对象管理器把这些数据发送到请求数据的应用程序中。
WMI命名空间
CIM是以面向对象原理为基础的,它的类都是抽象的,因为他们要在任何环境中表示管理对象的一般模型。CIM规定提供了这样的模型——CIM Schema。Schema是具有各种特征的组织结构,在CIM的条件下,这种结构就是类的层次结构以及他们的方法、属性等。
WMI作为WBEM的Windows实现,也反应了CIM Schema,他的很多类是从CIM派生而来的,WMI Schema也包含自己特有的类,而且这些类可以用户扩展。
为了方便操作,WMI引入了名字空间的概念,用于对相同环境中存在的类进行分组。此外,名字空间也用于相关的安全限制(关于安全限制待调查)。和类相似,名字空间也构成了一个层次结构,类似与文件系统中的层次结构。
root——作为所有其他名字空间的顶层空间,他只包含了WMI的系统类,这些类与WMI有关。
root\Default——与Windows注册表操纵相关的主机类。
root\Security——用于与Windows安全相关的类。
root\CIMV2——包含从CIM Schema派生的类,他们代表着我们最常工作的Win32环境。
root\WMI——包含了Windows 硬件驱动信息的类,其中包含了一些我们可能用到的关于电源、显示器等信息的类。
WMI标准提供程序
微软在WMI核心部件和微软WMI软件开发工具中包括了很多提供程序。
我们最常用的是Win32提供程序(核心WMI),他用于处理Win32系统特征。CIM存储库把这些信息存储为以“Win32_”为前缀的类中,他包含在root\CIMV2名字空间中。例如,Win32_BIOS、Win32_Service、Win32_Processor等等。
另一个可能用到的是注册表提供程序,他可以获取或修改注册表数据。当注册表中发生变更时,他可设置事件通知。注册表提供程序与root\default名字空间的StdRegProv类交互,这些类中包含了很多方法以查找和修改注册表。
Win32 Provider不仅仅提供类和实例的信息,而且有些Provider会提供了一些方法,供用户调用。调用方法的流程如下:
1)取得Provider实例对象
调用IWbemServices::GetObject方法可以取得我们想要调用的Provider类型实例,他以一个IWbemClassObject类型指针返回。
BSTR MethodName = SysAllocString(L"Create");
BSTR ClassName = SysAllocString(L"Win32_Process");
IWbemClassObject* pClass = NULL;
hres = pSvc->GetObject(ClassName, 0, NULL, &pClass, NULL);
2)取得Provider提供方法参数
调用IWbemClassObject::GetMethod方法可以取得我们想要调用方法的参数,他以一个IWbemClassObject类型指针返回。
IWbemClassObject* pInParamsDefinition = NULL;
hres = pClass->GetMethod(MethodName, 0, &pInParamsDefinition, NULL);
3)生成Provider提供方法的参数的对象
调用IWbemClassObject::SpawnInstance方法生成调用方法的参数实例。需要将第二步得到的参数类型指针调用这个SpawnInstance方法,并且传递给该方法一个IWbemClassObject指针作为生成的参数对象的指针。
IWbemClassObject* pClassInstance = NULL;
hres = pInParamsDefinition->SpawnInstance(0, &pClassInstance);
4)设置参数对象的属性
调用IWbemClassObject::Put方法就可以设置参数对象的类型。
VARIANT varCommand;
varCommand.vt = VT_BSTR;
varCommand.bstrVal = L"notepad.exe";
hres = pClassInstance->Put(L"CommandLine", 0,&varCommand, 0);
wprintf(L"The command is: %s ", V_BSTR(&varCommand));
5)调用方法
与查询信息相同,调用方法也可以分为同步方式和异步方式,同步方式等待执行进程结束,才继续往下执行;异步方式则利用实现IWbemObjectSink接口的类型,创建一个新的线程后继续运行当前线程,而由新创建的线程完成调用方法,然后回调IWbemObjectSink:: Indicate函数,处理函数返回值。
以下是以同步方式调用IWbemServices::ExecMethod方法即调用Provider提供的方法,将之前生成的关于参数的实例传给该方法即可。
IWbemClassObject* pOutParams = NULL;
hres = pSvc->ExecMethod(ClassName, MethodName, 0,NULL, pClassInstance, &pOutParams, NULL);
以下是以异步方式调用同样的Provider的函数,不同于同步方式,异步方式调用IWbemServices::ExecMethodAsync方法,第三个参数IFlag可以设置为WBEM_FLAG_SEND_STATUS以接受调用时中间状态信息,没有输出参数(本例中是pOutParams),最后一个参数是实现IWbemObjectSink接口的类型。
QuerySink *pSink = new QuerySink();
hres = pSvc->ExecMethodAsync(ClassName, MethodName, WBEM_FLAG_SEND_STATUS,
NULL, pClassInstance, pSink); 但是调用方法比查询信息多出一种半同步方式,由于同步方法使得调用者线程等待方法执行完,而异步方法则需要编程人员继承IWbemObjectSink接口并进行多线程编程,所以半同步方式则是以一种折中的方法进行IWbemServices实例中方法的调用,创建一个线程去查询,然后写入一个IWbemCallResult接口的实例中,由主线程去查询。
IWbemCallResult *pCallRes = 0;
IWbemClassObject *pObj = 0;
hres = pSvc->ExecMethod(ClassName, MethodName, WBEM_FLAG_RETURN_IMMEDIATELY,
NULL, pClassInstance,NULL, &pCallRes);
while (true)
...{
LONG lStatus = 0;
HRESULT hRes = pCallRes->GetCallStatus(5000, &lStatus);
if ((hRes == WBEM_S_NO_ERROR))
break;
if ((hRes == WBEM_S_TIMEDOUT))
continue;
}
hres = pCallRes->GetResultObject(5000, &pObj);
if (hres)
...{
pCallRes->Release();
return 1;
}
VARIANT varReturnValue;
hres = pObj->Get(_bstr_t(L"ReturnValue"), 0,
&varReturnValue, NULL, 0);
cout << varReturnValue.lVal << endl;
pCallRes->Release();
pObj->Release();
以上就是通过WMI调用方法的基本形式,在XP下,调用方法的功能比较少,但是在Vista下,Windows在root\wmi命名空间中提供了一些Provider和方法,代替了一些本来由驱动做的事情。