Tutorial :Resolve a COM [out] VARIANT* containing parray as SAFEARRAY of BSTR's in c#.net



Question:

Question: I have a COM server with a method as IDL:

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

In the header file this becomes:

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

The implementation:

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;  }  

//The class CInternals is added in here to complete the picture

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;  

}

in the CS file in C#

    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);          }      }  

The point is that none of the 'out oResult' objects work.

Any advise is welcome.


Solution:1

It looks like your C++ implementation is wrong. You do not set retVal anywhere, also you are copying the wrong value into scanPresent. The calling code has no way of knowing you allocated it using new, and since it is C#, it would have no way of freeing it even if it did. Normally, you allocate the VARIANT using VariantInit (the CComVariant is a wrapper around this), and then directly copy the fields into the result parameter. Additionally, I cannot see how you are creating the safe array for the return.

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;  }  


Solution:2

Please check [1800 INFORMATION]'s excellent post for reference.

Let me clarify a couple of details. When he says:

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

He's asking because that's not enough to create a SAFEARRAY by any stretch. It's really bad practice to initialize pieces of a class at different functions like that. FilterScanners() needs to do that internally anyway, plus more:

  // 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;  

Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »