在c#.net中解析包含parray的COM [out] VARIANT *作为BSTR的SAFEARRAY

时间:2009-06-01 21:07:31

标签: c# c++ com atl

问题:我有一个COM服务器,其方法为IDL:

  [id(2), helpstring("method ExtractAvailableScanners")]
        HRESULT ExtractAvailableScanners(
              [in] VARIANT scanFilter, [out] VARIANT* scanPresent,
              [out,retval] LONG* retVal);

在头文件中,这变为:

STDMETHOD(ExtractAvailableScanners)
  (VARIANT scanFilter, VARIANT* scanPresent, LONG* retVal);

实施:

STDMETHODIMP CSBIdentify::ExtractAvailableScanners
  (VARIANT scanFilter, VARIANT* scanPresent, LONG* retVal)
{
      // TODO: Return the available scanners given a scanner lookup filter

      CInternals ints;

      //Find all the device strings
      CComVariant Result;
      ints.CollectDeviceStrings(&Result);

      //Extraction of the wanted ones
      CComVariant* pScanners = new CComVariant;
      pScanners->vt = VT_SAFEARRAY;
      ints.FilterScanners(scanFilter, &Result, pScanners);

      // Cleanup
      // ========
      scanPresent = pScanners;
      return S_OK;
}

//此处添加了CInternals类以完成图片

int CInternals :: CollectDeviceStrings(CComVariant * pList) { HRESULT hr = S_OK;     BOOL bRet = FALSE;     HRESULT hres = S_OK;

// Step 3: ---------------------------------------------------
// Obtain the initial locater to WMI -------------------------

IWbemLocator *pLoc = NULL;

hres = CoCreateInstance(
    CLSID_WbemLocator,             
    0, 
    CLSCTX_INPROC_SERVER, 
    IID_IWbemLocator, (LPVOID *) &pLoc);

if (FAILED(hres))
{
    CError::PresetError( "Failed to create IWbemLocator object in SBIdentify::GetDevices", E_FAIL );
    return hres;
}

// Step 4: -----------------------------------------------------
// Connect to WMI through the IWbemLocator::ConnectServer method

IWbemServices *pSvc = NULL;

// Connect to the root\cimv2 namespace with
// the current user and obtain pointer pSvc
// to make IWbemServices calls.
hres = pLoc->ConnectServer(
    _bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
    NULL,                    // User name. NULL = current user
    NULL,                    // User password. NULL = current
    0,                       // Locale. NULL indicates current
    NULL,                    // Security flags.
    0,                       // Authority (e.g. Kerberos)
    0,                       // Context object 
    &pSvc                    // pointer to IWbemServices proxy
    );

if (FAILED(hres))
{
    CError::PresetError( "Could not connect to IWbemServices proxy in SBIdentify::GetDevices", E_FAIL );
    pLoc->Release();     
    return hres;
}

//  CTraceLog::TraceMsg( "Connected to ROOT\\CIMV2 WMI namespace" );

// Step 5: --------------------------------------------------
// Set security levels on the proxy -------------------------

hres = CoSetProxyBlanket(
    pSvc,                        // Indicates the proxy to set
    RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx
    RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx
    NULL,                        // Server principal name 
    RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx 
    RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
    NULL,                        // client identity
    EOAC_NONE                    // proxy capabilities 
    );

if (FAILED(hres))
{
    CError::PresetError( "Could not set proxy blanket in SBIdentify::GetDevices", E_FAIL );
    pSvc->Release();
    pLoc->Release();     
    return hres;
}

// Step 6: --------------------------------------------------
// Use the IWbemServices pointer to make requests of WMI ----
// Use WBEM_FLAG_BIDIRECTIONAL flag to ensure the enumerator is resettable

IEnumWbemClassObject* pEnumerator = NULL;
hres = pSvc->ExecQuery(
    bstr_t("WQL"), 
    bstr_t("SELECT * FROM Win32_PnPEntity"),
    WBEM_FLAG_BIDIRECTIONAL | WBEM_FLAG_RETURN_IMMEDIATELY, 
    NULL,
    &pEnumerator);

if (FAILED(hres))
{
    CError::PresetError( "Query on Win32_PnPEntity failed in SBIdentify::GetDevices", E_FAIL );
    pSvc->Release();
    pLoc->Release();     
    return hres;
}

// Step 7: -------------------------------------------------
// Get the data from the query in step 6 -------------------
int n = 0;
CComPtr< IWbemClassObject > pclsObj;
ULONG uReturn = 0;

//Read the list to determine its length
while (pEnumerator)
{
    pclsObj = NULL;
    hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
    if(0 == uReturn)
        break;
    n++;
}
pEnumerator->Reset();

//The full read mechanism
VARIANT Result;
Result.vt = VT_SAFEARRAY | VT_BSTR;
VARIANT* pResult = &Result;

SAFEARRAYBOUND rgsabound[1];
rgsabound[0].lLbound = 0;
rgsabound[0].cElements = n;
LONG ix[] = {0};
int i = -1;
pResult->parray = ::SafeArrayCreate(VT_BSTR, 1, rgsabound);
if(pResult->parray == NULL)
{
    CError::PresetError( "SafeArrayCreate() failed in SBIdentify::GetDevices", E_OUTOFMEMORY );
    pSvc->Release();
    pLoc->Release();     
    return E_OUTOFMEMORY;
}

while (pEnumerator)
{
    pclsObj = NULL;
    pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);

    if(0 == uReturn)
        break;

    i++;

    VARIANT vtProp;

    // Get the value of the Name property
    hr = pclsObj->Get(L"Name", 0, &vtProp, 0, 0);
    if(hr != S_OK)
    {
        CError::PresetError( "<Get> failed in SBIdentify::GetDevices", hr );
        pSvc->Release();
        pLoc->Release();
        pEnumerator->Release();
        return hr;
    }
    wcout << " Name : " << vtProp.bstrVal << endl;
    ix[0] = i;
    hr = SafeArrayPutElement(pResult->parray, ix, vtProp.bstrVal);
    if(hr != S_OK)
    {
        CError::PresetError( "SafeArrayPutElement() failed in SBIdentify::GetDevices", hr );
        pSvc->Release();
        pLoc->Release();
        pEnumerator->Release();
        return hr;
    }
    VariantClear(&vtProp);
}
pList->Attach(pResult);
return hr;

}

在C#的CS文件中

    public void ExtractScanners(ref ListBox listBox1)
    {
        String[] oNames = {"LS1/LiteUe", "Sagem"};

//            object oResult = new IntPtr(Int32);
//            Object oGeneric;// = new object();
//            System.Array oResult;
//            IntPtr i = (IntPtr)8;// 27;
//            Object oResult = Marshal.GetObjectForNativeVariant(i);
//            Object oResult;// = null;
//            String[] oResult;
//            IntPtr oResult;
        try
        {
            iRet = myCom.ExtractAvailableScanners(oNames, out oResult);

            listBox1.Items.Add("GetAvailableDevices ok");
        }
        catch (COMException comEx)
        {
            ReportCOMError(comEx, ref listBox1);
        }
        catch (ArgumentException argEx)
        {
            ReportArgError(argEx, ref listBox1);
        }
    }

重点是'out oResult'对象都不起作用。

欢迎任何建议。

2 个答案:

答案 0 :(得分:3)

看起来你的C ++实现是错误的。您没有在任何地方设置retVal,也没有将错误的值复制到scanPresent。调用代码无法知道您使用new分配它,并且由于它是C#,即使它已经存在,它也无法释放它。通常,您使用VariantInit分配VARIANT(CComVariant是这个的包装器),然后直接将字段复制到result参数中。另外,我看不出你是如何为返回创建安全数组的。

STDMETHODIMP CSBIdentify::ExtractAvailableScanners
  (VARIANT scanFilter, VARIANT* scanPresent, LONG* retVal)
{
      // TODO: Return the available scanners given a scanner lookup filter

      CInternals ints;

      //Find all the device strings
      CComVariant Result;
      ints.CollectDeviceStrings(&Result);

      //Extraction of the wanted ones
      CComVariant Scanners;

      // why set this here?
      pScanners.vt = VT_SAFEARRAY;

      // what does this call do? It should be allocating the new safe array
      // using the normal methods for creating safe arrays
      ints.FilterScanners(scanFilter, &Result, &Scanners);

      // Cleanup
      // ========
      Scanners.Detach(scanPresent);

      // what to put in here?
      *retVal = something;
      return S_OK;
}

答案 1 :(得分:0)

请查看[1800 INFORMATION]的优秀帖子以供参考。

让我澄清一些细节。当他说:

  // why set this here?
  scanners.vt = VT_SAFEARRAY;

他在问,因为这还不足以创造一个SAFEARRAY。在不同的函数中初始化类的片段是非常糟糕的做法。无论如何,FilterScanners()需要在内部执行此操作,还有更多:

  // Local dimension bounds
  // 'x' is the number of dimensions, as in this VB6:
  // Dim Abc(a,b,c) 'has three dimensions
  SAFEARRAYBOUND sab[x]; 

  // Set the dimensions, as in:
  // Dim Abc(0 TO TOTAL_BOUND_0, 0 TO TOTAL_BOUND_1, ...) 'VB6
  sab[0].lLbound = 0;
  sab[0].cElements = TOTAL_BOUND_0;
  sab[1].lLbound = 0;
  sab[1].cElements = TOTAL_BOUND_1;
  // ... etc.

  // This API creates the actual SafeArray in the COM Heap.
  // Replace proper VT_VARIANT below with your type
  SAFEARRAY * pSA = SafeArrayCreate(VT_VARIANT, x, sab); // x same as before

  // Fill-in the elements of the array as required.
  // Remember to use SafeArrayAccessData() and SafeArrayUnaccessData()

  // Stuff the pointer to the SAFEARRAY in the VARIANT output argument:
  // "OR" whatever the type of the array is. Think in VB6 terms!
  // Dim scanners(...) As Variant ' VT_SAFEARRAY | VT_VARIANT
  // Dim scanners(...) As String  ' VT_SAFEARRAY | VB_BSTR
  // etc.
  VariantInit(pScanners); // Always recommended to clear the VARIANT before using it
  pScanners->vt    = VT_SAFEARRAY | VT_VARIANT; // set the type
  pScanners->pparray = pSA;