tags:

views:

814

answers:

2

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.

+3  A: 

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;
}
1800 INFORMATION
The details are in class CInternals. As you can see I am brand new on this site and I dont know how to expand the details of the question.Info in retVal would have no effect on the problem.Your other comment is valid. The C# "out oResult" has not been initialised in any way in this particular example and -yes- I have put the parm on the heap using new. If I knew what type of object oResult should be I should be able to use it as a VARIANT struct and do the usual such as setting scanPresent->vt = VT_SAFEARRAY and then loading it with the BSTR's as per usual.But how is this achieved?
I think your treatment of new is a bit unclear. VariantInit() doesn't allocate anything; it just initializes existing storage. In this scenario, pScanners is a local, so Softbee is free to use new if he so chooses to (I wouldn't), but then he has to delete it before returning, as usual. If he really needed to return a heap-allocated Variant (like if you had a VARIANT** out argument), you would have to allocate the space with CoTaskMemAlloc() and then calling VariantInit with it.
Euro Micelli
If you want to edit the question, click the "edit" button - it is near the bottom of where the question ends and the answers start
1800 INFORMATION
A: 

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;
Euro Micelli
Having added CInternals, you can see that pScanners carries the information that is needed in the caller's result. The question still is, what should the object's type be as it is passed to COM, should scanPresent ( if its type is a VARIANT* ) itself then be populated with the answer if transfer of a pointer is not legal.My thanks to you for staying with me on this.