How do I access services of UPnP device?

2019-06-19 12:28发布

问题:

The device: Belkin Wemo Switch
Dev environment: MS VC++ 2010 on Windows7

I'm trying to enumerate the services of a UPnP device using C++ from Windows.

I've got the IUPnPDevice pointer and can access several properties.
I've got the IUPnPServices pointer and can count the correct number of services (7).
I use QueryInterface() to get the IEnumVARIANT pointer (which appears to succeed).
However, the Next() method always fails with HRESULT of 0x80040500 - which translates as Windows error 1280 (0x500) - ERROR_ALREADY_FIBER.
This error does not make any sense to me.

(I've tried using both IEnumVARIANT and IEnumUnknown - as the docs indicate it could be either, but both produce the same result.)

I've included below the complete source file, plus the output it produces.
[Note: It's hardcoded to use the udn of my own device]

I'd be very grateful if anyone can help as I'm currently stuck.

Best regards,
Dave

Code:

// UpnpTest1.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <windows.h>
#include <upnp.h>

static void DumpComError(const TCHAR *api, HRESULT hr);

int _tmain(int argc, _TCHAR* argv[])
{
  int retcode=-1; // assume failure

  HRESULT hr = CoInitialize(0);
  if (hr==S_OK)
  {
    IUPnPDeviceFinder *deviceFinder=0;
    hr = CoCreateInstance(CLSID_UPnPDeviceFinder, 0, CLSCTX_INPROC_SERVER, IID_IUPnPDeviceFinder, (void**)&deviceFinder);
    if (hr==S_OK)
    {
      IUPnPDevice *device=0;
      hr = deviceFinder->FindByUDN(L"uuid:Socket-1_0-221239K11002F6", &device);
      if (hr==S_OK)
      {
        if (device)
        {
          TCHAR *manufacturer=0, *manufacturerUrl=0;
          TCHAR *description=0, *name=0, *modelUrl=0;
          TCHAR *serialNumber=0, *udn=0, *upc=0, *deviceType=0;
          TCHAR *presentationUrl=0;

          device->get_ManufacturerName(&manufacturer);
          device->get_ManufacturerURL(&manufacturerUrl);
          device->get_Description(&description);
          device->get_FriendlyName(&name);
          device->get_ModelURL(&modelUrl);
          device->get_SerialNumber(&serialNumber);
          device->get_UniqueDeviceName(&udn);
          device->get_UPC(&upc);
          device->get_Type(&deviceType);
          device->get_PresentationURL(&presentationUrl);

          _tprintf(_T("MANUFACTURER: %s [%s]\n"), manufacturer, manufacturerUrl);
          _tprintf(_T("MODEL:        %s [%s]\n              [%s]\n"), description, name, modelUrl);
          _tprintf(_T("DEVICE:       serial=%s\n              udn=%s\n              upc=%s\n              type=%s\n"), serialNumber, udn, upc, deviceType);
          _tprintf(_T("URL:          %s\n"), presentationUrl);

          IUPnPServices *services=0;
          hr = device->get_Services(&services);
          if (hr==S_OK)
          {
            if (services)
            {
              long numberOfServices=0;
              services->get_Count(&numberOfServices);

              if (numberOfServices>0)
              {
                IUnknown *unknown=0;
                hr = services->get__NewEnum(&unknown);
                if (hr==S_OK)
                {
                  if (unknown)
                  {
                    IEnumVARIANT *enumInterface=0;
                    hr = unknown->QueryInterface(IID_IEnumVARIANT,(void**)&enumInterface);
                    if (enumInterface)
                    {
                      VARIANT var;
                      unsigned long fetched=0;
                      hr = enumInterface->Next(1, &var, &fetched);

                      if (hr==S_OK)
                      {

                      }
                      else
                        DumpComError(_T("IEnumVARIANT::Next"), hr);
                    }
                    else
                      DumpComError(_T("IUnknown::QueryInterface"), hr);
                  }
                  else
                    fprintf(stderr, "Failed to get enumeration interface.\n");
                }
                else
                  DumpComError(_T("IUPnPServices::get__NewEnum"), hr);
              }
              else
                fprintf(stderr, "No services available.\n");
            }
            else
              fprintf(stderr, "Failed to get services collection.\n");
          }
          else
            DumpComError(_T("IUPnPDevice::get_Services"), hr);
        }
        else
          fprintf(stderr, "Device not found.\n");
      }
      else
        DumpComError(_T("IUPnPDeviceFinder::FindByUDN"), hr);
    }
    else
      DumpComError(_T("CoCreateIndex"), hr);
  }
  else
    DumpComError(_T("CoInitialize"), hr);

  return retcode;
}

static void AddBoolToString(const TCHAR *name, bool value, TCHAR *buf, int &i, int max)
{
  if (name && *name && value && buf && i>=0)
    i += _snwprintf_s(&buf[i], max-i, (max-i-1)*sizeof(TCHAR), _T("%s%s=YES"), (i>0? _T("; "): _T("")), name);
}

static void AddIntToString(const TCHAR *name, int value, TCHAR *buf, int &i, int max)
{
  if (name && *name && value && buf && i>=0)
    i += _snwprintf_s(&buf[i], max-i, (max-i-1)*sizeof(TCHAR), _T("%s%s=%d"), (i>0? _T("; "): _T("")), name, value);
}

static void DumpComError(const TCHAR *api, HRESULT hr)
{
  bool failure   = (hr&0x80000000? true: false);
  bool severe    = (hr&0x40000000? true: false);
  bool microsoft = (hr&0x20000000? false: true);
  bool ntStatus  = (hr&0x10000000? true: false);
  bool xBit      = (hr&0x08000000? true: false);
  int facility   = (hr&0x07FF0000)>>16;
  int code       = (hr&0x0000FFFF);

  TCHAR buf[1024]={0};
  int bufsize = sizeof(buf)/sizeof(TCHAR);
  int i=0;

  AddBoolToString(_T("failure"), failure, buf, i, bufsize);
  AddBoolToString(_T("severe"), severe, buf, i, bufsize);
  AddBoolToString(_T("microsoft"), microsoft, buf, i, bufsize);
  AddBoolToString(_T("ntStatus"), ntStatus, buf, i, bufsize);
  AddBoolToString(_T("xBit"), xBit, buf, i, bufsize);
  AddIntToString(_T("facility"), facility, buf, i, bufsize);
  AddIntToString(_T("code"), code, buf, i, bufsize);

  _ftprintf(stderr, _T("\n%s() failed, hr=0x%08x\n[%s]\n"), api, hr, buf);
}

Output: It produces following output:

MANUFACTURER: Belkin International Inc. [http://www.belkin.com/]
MODEL:        Belkin Plugin Socket 1.0 [WeMo Switch]
              [http://www.belkin.com/plugin/]
DEVICE:       serial=221239K11002F6
              udn=uuid:Socket-1_0-221239K11002F6
              upc=123456789
              type=urn:Belkin:device:controllee:1
URL:          http://192.168.1.16:49153/pluginpres.html

IEnumVARIANT::Next() failed, hr=0x80040500
[failure=YES; microsoft=YES; facility=4; code=1280]

EDIT:

After a lot of dead-ends, I have managed to get this working by manually building the SOAP requests, and sending the requests via TCP using Windows sockets. Tricky bit was getting the syntax just right as I had no experience of SOAP before. [UPnP was useful to identify the IP address & port number - as these can change]. Once up and running - it's actually a lot simpler than the UPnP interface. Let me know if you're interested and I can post the code... It doesn't directly answer the question I posed here, so it wouldn't make sense to answer my question with this detail.

However, if you're interested, let me know and I can post the code.

Cheers,
Dave

回答1:

HRESULT of 0x80040500 is not what you think, but UPNP_E_INVALID_DOCUMENT. For explanation of how such ambiguity is possible, see my answer in another SO question.

My guess is that your Belkin device is giving non-conformant device description or service description XML. Non-conformant doesn't necessarily mean broken format, UPnP specification has tons of secondary requirements. Try Device Spy from Intel Developer Tools (link at bottom of the other answer), if the device pops up, then run Device Validator from the same suite on it.



回答2:

My experience is similar, in that UPnPDeviceFinder simply doesn't work. It never sends out the UPnP Search packet, so the devices don't respond. The only way to get it to work is if you also use windows media player or the "Cast To Device" menu (which is WMP) to initiate the search. As it is UPnPDeviceFinder will return some devices, only if they happen to be broadcasting at that moment, but even finding an XBox (another Microsoft product) doesn't work in the examples without other activity going on.