WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类

来源:互联网 发布:家具甲醛标准 知乎 编辑:程序博客网 时间:2024/05/14 19:45

  在《WMI技术介绍和应用——VC开发WMI应用的基本步骤》文中,我们介绍了VC使用WMI技术的基本框架。本节我将讲解封装和实现一个用于半同步查询WMI的类。(转载请指明出于breaksoftware的csdn博客)

        我曾思考过如何编写一个比较有用的类,因为不同平台上WMI的查询结果集是不同的,很难做个通用的类。于是,我使用了最简单的方法——遍历返回结果。我们先看下类的声明

[cpp] view plaincopy
  1. class CSynQuery : public CWMI  
  2. {  
  3. public:  
  4.     CSynQuery(const wstring& wszNamespace, const wstring& wszWQLQuery);  
  5.     ~CSynQuery(void);  
  6. private:  
  7.     HRESULT Excute(CComPtr<IWbemServices> pSvc);  
  8.     HRESULT DealWithIWbemClassObject(CComPtr<IWbemClassObject> pclsObj);  
  9.     virtual HRESULT DealWithSingleItem( CComBSTR bstrName, CComVariant Value, CIMTYPE type, LONG lFlavor );  
  10. private:  
  11.      wstring m_wszWQLQuery;  
  12. };  
        Excute是继承于CWMI基类中的纯虚函数。在CSynQuery实现的Excute中,我将执行一次半同步查询,并枚举返回的结果。

[cpp] view plaincopy
  1. HRESULT CSynQuery::Excute( CComPtr<IWbemServices> pSvc )  
  2. {  
  3.     HRESULT hr = WBEM_S_FALSE;  
  4.   
  5.     do {  
  6.         CComPtr<IEnumWbemClassObject> pEnumerator = NULL;  
  7.         hr = pSvc->ExecQuery(   
  8.             CComBSTR("WQL"),  
  9.             CComBSTR(m_wszWQLQuery.c_str()),  
  10.             WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,  
  11.             NULL,  
  12.             &pEnumerator );  
  13.   
  14.         CHECKWMIHR(hr);  
  15.   
  16.         ULONG uReturn = 0;  
  17.   
  18.         while (pEnumerator) {  
  19.             CComPtr<IWbemClassObject> pclsObj = NULL;  
  20.             HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);  
  21.   
  22.             if ( 0 == uReturn) {  
  23.                 break;  
  24.             }  
  25.             DealWithIWbemClassObject(pclsObj);  
  26.         }  
  27.   
  28.     } while (0);  
  29.   
  30.     return hr;    
  31. }  

        首先我们先看一下用于(半)同步查询的函数ExecQuery的声明

[cpp] view plaincopy
  1. HRESULT ExecQuery(  
  2.   [in]   const BSTR strQueryLanguage,  
  3.   [in]   const BSTR strQuery,  
  4.   [in]   LONG lFlags,  
  5.   [in]   IWbemContext *pCtx,  
  6.   [out]  IEnumWbemClassObject **ppEnum  
  7. );  
        strQueryLanguage是用于标记查询的语言种类。由此参数可以发现,当初微软设计这个接口时是希望未来它支持更多的查询语言。由于目前只支持WQL语言,所以该参数只能传“WQL”。第二个参数strQuery是实际用于查询的命令,比如“Select * From XXX",是不是看着很像SQL?第三个参数lFlags是一个非常重要的参数,如果仔细看该文标题,可以发现,我用的是”半同步“而不是“同步”,该参数就控制着该函数到底是“同步”还是“半同步”。如果该参数包含WBEM_FLAG_RETURN_IMMEDIATELY,则说明该调用是个半同步调用,否则是同步调用。

        现在我说一下WMI中同步和半同步两者的区别。同步这个过程不难理解,如果我们同步调用一个函数,该函数会经过计算后将返回结果准备好,然后返回到调用处。如果该过程非常消耗时间,且返回的结果非常占用空间,比如返回10240个对象,是不是觉得这个调用过程非常笨重?是的!那么解决这个问题的很好的方法便诞生了:半同步。半同步的调用方式非常类似于异步(以后介绍)调用。当我们调用一个半同步操作后,函数内部会启动线程去执行查询工作,之后会立即返回到我们的调用处。当半同步内部线程查询到并封装完一个对象后,便会通知我们外面枚举结果的函数,告诉我们:一个结果准备好了,你可以使用了。这个相当于将合并结果集的过程去掉。考虑到调用半同步的逻辑处理一个返回对象可能需要一定的时间,在半同步启动的线程中可以利用这段时间完成下一个对象的查找和封装。所以总体来说半同步对时间的消耗是比同步好的。而从占用资源的角度看,半同步不用一次返回那么多个结果,所以占用的资源会比同步方式好很多。所以大部分情况下,只在同步和半同步中做出选择的情况下,优先考虑使用半同步。

        回到ExecQuery这个函数,lFlags还有个非常重要的可选值是WBEM_FLAG_FORWARD_ONLY。该参数让ExecQuery函数返回的枚举是个Forward-Only的。这样做的好处是可以让我们程序更快且占用更少的资源。所以lFlags一般是WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY。

        pCtx一般设置为NULL。ppEnum是个返回结果的枚举器。

[cpp] view plaincopy
  1. ULONG uReturn = 0;  
  2. while (pEnumerator) {  
  3.     CComPtr<IWbemClassObject> pclsObj = NULL;  
  4.     HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);  
  5.   
  6.     if ( 0 == uReturn) {  
  7.         break;  
  8.     }  
  9.     DealWithIWbemClassObject(pclsObj);  
  10. }  
        我们可以如此枚举返回的结果集。关注一下Next第一参数。在之前对半同步的介绍中,我们说道:半同步函数启动的线程需要查询和封装下一个结果,其实这个可以看成是个异步操作,于是枚举结果这边需要等待那个实际查询的线程完成一个结果。我给此参数设置为“一直等待”。一般来说,这个结果是立即返回的。pclsObj保存的是一个结果集中的一个结果。我们使用DealWithIWbemClassObject函数处理每个结果。

[cpp] view plaincopy
  1. HRESULT CSynQuery::DealWithIWbemClassObject( CComPtr<IWbemClassObject> pclsObj )  
  2. {  
  3.     HRESULT hr = WBEM_S_NO_ERROR;   
  4.     do  {  
  5.         CComVariant vtClass;   
  6.   
  7.         hr = pclsObj->Get(L"__CLASS", 0, &vtClass, NULL, NULL);   
  8.         CHECKWMIHR(hr);  
  9.         if ( VT_BSTR == vtClass.vt ) {  
  10.             wprintf(L"\n%s\n", vtClass.bstrVal);  
  11.         }  
  12.   
  13.         hr = pclsObj->BeginEnumeration(WBEM_FLAG_LOCAL_ONLY);  
  14.   
  15.         do {  
  16.             CComBSTR bstrName;  
  17.             CComVariant Value;  
  18.             CIMTYPE type;  
  19.             LONG lFlavor = 0;  
  20.             hr = pclsObj->Next(0, &bstrName, &Value, &type, &lFlavor);  
  21.             CHECKWMIHR(hr);  
  22.             hr = DealWithSingleItem(bstrName, Value, type, lFlavor);              
  23.         }while ( WBEM_S_NO_ERROR == hr );  
  24.   
  25.         hr = pclsObj->EndEnumeration();  
  26.     } while (0);  
  27.   
  28.     return hr;  
  29. }  
        该函数首先会打印出该结果属于哪个WMI类,然后会枚举结果中的每个成员。其实在实际使用中大可不必如此,我如此设计只是为了该例子可以在不同平台上,针对不同需求都可以正确执行。需要注意的一点是在枚举前,我们需要制定要枚举什么。于是我们要调用BeginEnumeration,并传WBEM_FLAG_LOCAL_ONLY,即枚举该类自己的成员。在枚举完之后,一般要调用EndEnumeration。其实这个不是必须的,只有在要提前退出枚举的场景下才必须调用EndEnumeration。针对每个成员,我们又要使用虚函数DealWithSingleItem来处理。于是处理逻辑又被我封装到一个继承于CSynQuery的类CSynQueryData中。该类逻辑非常简单,如果成员是非对象,则直接打印出来;如果结果是一个对象,则再解析这个对象,并将其中非对象打印出来。

[cpp] view plaincopy
  1. HRESULT CSynQueryData::DealWithSingleItem( CComBSTR bstrName, CComVariant Value, CIMTYPE type, LONG lFlavor )  
  2. {  
  3.     HRESULT hr = WBEM_S_NO_ERROR;   
  4.     switch ( Value.vt ) {  
  5.         case VT_UNKNOWN : {  
  6.             DealWithUnknownTypeItem(bstrName, Value, type, lFlavor);  
  7.             }break;  
  8.         default: {  
  9.             PrintfItem(bstrName, Value, type, lFlavor);  
  10.         };  
  11.     }  
  12.     return hr;  
  13. }  
  14.   
  15. HRESULT CSynQueryData::DealWithUnknownTypeItem( CComBSTR bstrName, CComVariant Value, CIMTYPE type, LONG lFlavor )  
  16. {  
  17.     HRESULT hr = WBEM_S_NO_ERROR;  
  18.     if ( NULL == Value.punkVal ) {  
  19.         return hr;  
  20.     }  
  21.     // object类型转换成IWbemClassObject接口指针,通过该指针枚举其他属性  
  22.     CComPtr<IWbemClassObject> pObjInstance = (IWbemClassObject*)Value.punkVal;  
  23.     hr = pObjInstance->BeginEnumeration(WBEM_FLAG_LOCAL_ONLY);  
  24.     do {  
  25.         CHECKHR(hr);  
  26.         CComBSTR bstrNewName;  
  27.         CComVariant NewValue;  
  28.         CIMTYPE newtype;  
  29.         LONG lnewFlavor = 0;  
  30.         hr = pObjInstance->Next(0, &bstrNewName, &NewValue, &newtype, &lnewFlavor);  
  31.         CHECKHR(hr);  
  32.         PrintfItem(bstrNewName, NewValue, newtype, lnewFlavor);  
  33.     }while ( WBEM_S_NO_ERROR == hr );  
  34.     hr = pObjInstance->EndEnumeration();  
  35.     return WBEM_S_NO_ERROR;  
  36. }  
  37.   
  38. VOID CSynQueryData::PrintfItem( CComBSTR bstrName, CComVariant Value, CIMTYPE type, LONG lFlavor )  
  39. {  
  40.     wprintf(L"%s\t",bstrName.m_str);  
  41.     switch ( Value.vt ){  
  42.         case VT_BSTR: {  
  43.             wprintf(L"%s",Value.bstrVal);          
  44.                       }break;  
  45.         case VT_I1:  
  46.         case VT_I2:  
  47.         case VT_I4:  
  48.         case VT_I8:   
  49.         case VT_INT: {  
  50.                 wprintf(L"%d",Value.intVal);   
  51.             }break;  
  52.         case VT_UI8:  
  53.         case VT_UI1:      
  54.         case VT_UI2:  
  55.         case VT_UI4:  
  56.         case VT_UINT:{  
  57.             wprintf(L"0x%u",Value.intVal);       
  58.             }break;  
  59.         case VT_BOOL:{  
  60.             wprintf(L"%s", Value.boolVal ? L"TRUE" : L"FASLE" );  
  61.                      }break;  
  62.         default:{  
  63.             ATLASSERT(FALSE);  
  64.                 };  
  65.     }  
  66.     wprintf(L"\n");  
  67. }  
        至此,一个半同步查询的框架就搭建完成。我会之后若干节,结合实际应用场景,利用这个框架,讲解WMI的实际应用。
原创粉丝点击