//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1997. // // File: E V T O B J . C P P // // Contents: Implements the Eventing Manager object for the UPnP Device // Host // // Notes: // // Author: danielwe 7 Aug 2000 // //---------------------------------------------------------------------------- #include #pragma hdrstop #include #include #include "hostp.h" #include "upnphost.h" #include "evtobj.h" #include "evtapi.h" #include "ncbase.h" #include "ncxml.h" #include "ComUtility.h" #include "uhcommon.h" // // IUPnPEventingManager // //+--------------------------------------------------------------------------- // // Member: CUPnPEventingManager::Initialize // // Purpose: Initializes the Eventing Manager object for a hosted service // // Arguments: // szUdn [in] UDN of the device // szSid [in] Service identifier of the service within the device // puap [in] Interface pointer to the service's automation proxy // punkSvc [in] Interface pointer to the service's object // // Returns: S_OK if success, E_OUTOFMEMORY, or any other OLE interface // error code // // Author: danielwe 8 Aug 2000 // // Notes: // STDMETHODIMP CUPnPEventingManager::Initialize(LPCWSTR szUdn, LPCWSTR szSid, IUPnPAutomationProxy *puap, IUnknown *punkSvc, BOOL bRunning) { HRESULT hr = S_OK; DWORD cch; hr = HrIsAllowedCOMCallLocality(CALL_LOCALITY_INPROC); if (FAILED(hr)) { goto Cleanup; } Assert(szUdn); Assert(szSid); Assert(punkSvc); Assert(puap); Assert(!m_szEsid); Assert(!m_puap); Assert(!m_pues); AddRefObj(m_puap = puap); cch = lstrlen(szUdn) + lstrlen(szSid) + lstrlen(L"+") + 1; m_szEsid = new WCHAR[cch]; if (!m_szEsid) { hr = E_OUTOFMEMORY; } else { // Make the event source identifier by combining the UDN and SID lstrcpy(m_szEsid, szUdn); lstrcat(m_szEsid, L"+"); lstrcat(m_szEsid, szSid); hr = punkSvc->QueryInterface(IID_IUPnPEventSource, (LPVOID *)&m_pues); if (SUCCEEDED(hr)) { if(bRunning) { hr = HrCopyProxyIdentity(m_pues, punkSvc); } if(SUCCEEDED(hr)) { // Get a reference to ourselves to hand out to the hosted service // hr = m_pues->Advise(this); } if (FAILED(hr)) { m_pues->Release(); m_pues = NULL; } } else { TraceError("CUPnPEventingManager::Initialize - Object passed in" " does not support IUPnPEventSource!", hr); } } if (FAILED(hr)) { delete [] m_szEsid; m_szEsid = NULL; } Cleanup: TraceError("CUPnPEventingManager::Initialize", hr); return hr; } //+--------------------------------------------------------------------------- // // Member: CUPnPEventingManager::AddSubscriber // // Purpose: // // Arguments: // cszUrl [in] Count of callback URLs // rgszCallbackUrl [in] List of callback URLs to which NOTIFY // requests will be sent // dwIpAddr [in] Local IP address that the subscribe came in on // pcsecTimeout [in out] On entry, this is the requested timeout from // the control point. // On exit, this is the timeout the device chose // pszSid [out] Returns the SID (subscription identifier) for // the new subscription // // Returns: // // Author: danielwe 2000/12/28 // // Notes: // STDMETHODIMP CUPnPEventingManager::AddSubscriber(DWORD cszUrl, LPCWSTR *rgszCallbackUrl, DWORD dwIpAddr, DWORD *pcsecTimeout, LPWSTR *pszSid) { HRESULT hr = S_OK; LPWSTR szBody = NULL; DWORD cVars; LPWSTR * rgszNames; LPWSTR * rgszTypes; VARIANT * rgvarValues; DWORD cDispids; DISPID * rgDispids = NULL; hr = HrIsAllowedCOMCallLocality(CALL_LOCALITY_INPROC); if (FAILED(hr)) { return hr; } AssertSz(m_szEsid, "What? Did we not get initialized or something?"); AssertSz(m_puap, "Automation proxy not initialized?"); Assert(rgszCallbackUrl); Assert(cszUrl); Assert(pszSid); *pszSid = NULL; hr = m_puap->GetDispIdsOfEventedVariables(&cDispids, &rgDispids); if (SUCCEEDED(hr)) { hr = m_puap->QueryStateVariablesByDispIds(cDispids, rgDispids, &cVars, &rgszNames, &rgvarValues, &rgszTypes); if (SUCCEEDED(hr)) { hr = HrComposeEventBody(m_puap, cVars, rgszNames, rgszTypes, rgvarValues, &szBody); if (SUCCEEDED(hr)) { LPWSTR szSid; hr = HrAddSubscriber(m_szEsid, dwIpAddr, cszUrl, rgszCallbackUrl, szBody, pcsecTimeout, &szSid); if (SUCCEEDED(hr)) { *pszSid = (LPWSTR)CoTaskMemAlloc(CbOfSzAndTerm(szSid)); if (*pszSid) { lstrcpy(*pszSid, szSid); } else { TraceError("CUPnPEventingManager::AddSubscriber - " "CoTaskMemAlloc()", hr); hr = E_OUTOFMEMORY; } delete [] szSid; } delete [] szBody; } DWORD ivar; for (ivar = 0; ivar < cVars; ivar++) { CoTaskMemFree(rgszTypes[ivar]); CoTaskMemFree(rgszNames[ivar]); VariantClear(&rgvarValues[ivar]); } CoTaskMemFree(rgszTypes); CoTaskMemFree(rgszNames); CoTaskMemFree(rgvarValues); } CoTaskMemFree(rgDispids); } TraceError("CUPnPEventingManager::AddSubscriber", hr); return hr; } //+--------------------------------------------------------------------------- // // Member: CUPnPEventingManager::RenewSubscriber // // Purpose: Renews an existing subscriber to a hosted service // // Arguments: // pcsecTimeout [in out] On entry, this is the requested timeout from // the control point. // On exit, this is the timeout the device chose // szSid [in] The SID (subscription identifier) of the // subscriber to renew // // Returns: S_OK if success, E_OUTOFMEMORY, or any other OLE interface // error code // // Author: danielwe 8 Aug 2000 // // Notes: // STDMETHODIMP CUPnPEventingManager::RenewSubscriber(DWORD *pcsecTimeout, LPWSTR szSid) { HRESULT hr = S_OK; hr = HrIsAllowedCOMCallLocality(CALL_LOCALITY_INPROC); if (FAILED(hr)) { return hr; } AssertSz(m_szEsid, "What? Did we not get initialized or something?"); Assert(szSid); hr = HrRenewSubscriber(m_szEsid, pcsecTimeout, szSid); TraceError("CUPnPEventingManager::RenewSubscriber", hr); return hr; } //+--------------------------------------------------------------------------- // // Member: CUPnPEventingManager::RemoveSubscriber // // Purpose: Removes a subscriber from the list of subscribers to a hosted // service // // Arguments: // szSid [in] The SID (subscription identifier) of the // subscriber to renew // // Returns: S_OK if success, E_OUTOFMEMORY, or any other OLE interface // error code // // Author: danielwe 8 Aug 2000 // // Notes: // STDMETHODIMP CUPnPEventingManager::RemoveSubscriber(LPWSTR szSid) { HRESULT hr = S_OK; hr = HrIsAllowedCOMCallLocality(CALL_LOCALITY_INPROC); if (FAILED(hr)) { return hr; } AssertSz(m_szEsid, "What? Did we not get initialized or something?"); Assert(szSid); hr = HrRemoveSubscriber(m_szEsid, szSid); TraceError("CUPnPEventingManager::RemoveSubscriber", hr); return hr; } // // IUPnPEventSink // //+--------------------------------------------------------------------------- // // Member: CUPnPEventingManager::OnStateChanged // // Purpose: Notifies the eventing manager that the state of a service on // a hosted device has changed // // Arguments: // cChanges [in] Number of state variables that have changed // rgdispidChanges [in] Array of DISPIDs for those state variables // // Returns: S_OK if success, E_OUTOFMEMORY, or any other OLE interface // error code otherwise. // // Author: danielwe 2000/09/21 // // Notes: // STDMETHODIMP CUPnPEventingManager::OnStateChanged(DWORD cChanges, DISPID rgdispidChanges[]) { HRESULT hr = S_OK; LPWSTR szBody = NULL; DWORD cVars; LPWSTR * rgszNames; LPWSTR * rgszTypes; VARIANT * rgvarValues; hr = HrIsAllowedCOMCallLocality(CALL_LOCALITY_INPROC); if (FAILED(hr)) { goto Cleanup; } AssertSz(m_szEsid, "What? Did we not get initialized or something?"); AssertSz(m_puap, "Automation proxy not initialized?"); if (!m_pues) { hr = E_UNEXPECTED; } else if (!cChanges || !rgdispidChanges) { hr = E_INVALIDARG; TraceError("OnStateChanged() called, but nothing to do.", hr); } else { hr = m_puap->QueryStateVariablesByDispIds(cChanges, rgdispidChanges, &cVars, &rgszNames, &rgvarValues, &rgszTypes); if (SUCCEEDED(hr)) { hr = HrComposeEventBody(m_puap, cVars, rgszNames, rgszTypes, rgvarValues, &szBody); if (SUCCEEDED(hr)) { hr = HrSubmitEvent(m_szEsid, szBody); delete [] szBody; } DWORD ivar; for (ivar = 0; ivar < cVars; ivar++) { CoTaskMemFree(rgszTypes[ivar]); CoTaskMemFree(rgszNames[ivar]); VariantClear(&rgvarValues[ivar]); } CoTaskMemFree(rgvarValues); CoTaskMemFree(rgszTypes); CoTaskMemFree(rgszNames); } } Cleanup: TraceError("CUPnPEventingManager::OnStateChanged", hr); return hr; } //+--------------------------------------------------------------------------- // // Member: CUPnPEventingManager::OnStateChangedSafe // // Purpose: Same as OnStateChanged, except this is for VB users that need // to pass the array of DISPIDs in a SafeArray. // // Arguments: // psa [in] SafeArray of DISPIDs that have changed // // Returns: Same as OnStateChanged // // Author: danielwe 2000/09/21 // // Notes: // STDMETHODIMP CUPnPEventingManager::OnStateChangedSafe(VARIANT varsadispidChanges) { HRESULT hr = S_OK; DISPID HUGEP * rgdispids; SAFEARRAY * psa; hr = HrIsAllowedCOMCallLocality(CALL_LOCALITY_INPROC); if (FAILED(hr)) { goto Cleanup; } psa = V_ARRAY(&varsadispidChanges); if (psa) { // Get a pointer to the elements of the array. hr = SafeArrayAccessData(psa, (void HUGEP**)&rgdispids); if (SUCCEEDED(hr)) { hr = OnStateChanged(psa->rgsabound[0].cElements, rgdispids); SafeArrayUnaccessData(psa); } } else { hr = E_INVALIDARG; } Cleanup: TraceError("CUPnPEventingManager::OnStateChangedSafe", hr); return hr; } //+--------------------------------------------------------------------------- // // Member: CUPnPEventingManager::Shutdown // // Purpose: Tells the eventing manager object to go away // // Arguments: // (none) // // Returns: E_UNEXPECTED if the object was never initialized. // // Author: danielwe 2000/09/21 // // Notes: // STDMETHODIMP CUPnPEventingManager::Shutdown() { HRESULT hr = S_OK; hr = HrIsAllowedCOMCallLocality(CALL_LOCALITY_INPROC); if (FAILED(hr)) { goto Cleanup; } if (!m_pues) { hr = E_UNEXPECTED; } else { hr = m_pues->Unadvise(this); ReleaseObj(m_pues); m_pues = NULL; } Cleanup: TraceError("CUPnPEventingManager::Shutdown", hr); return hr; } // // ATL Methods // //+--------------------------------------------------------------------------- // // Member: CUPnPEventingManager::FinalRelease // // Purpose: Called when the Eventing Manager object is released for the // last time // // Arguments: // (none) // // Returns: Not much. // // Author: danielwe 8 Aug 2000 // // Notes: // HRESULT CUPnPEventingManager::FinalRelease() { HRESULT hr = S_OK; delete [] m_szEsid; ReleaseObj(m_puap); TraceError("CUPnPEventingManager::FinalRelease", hr); return hr; } // // Private functions // //+--------------------------------------------------------------------------- // // Function: HrCreateElement // // Purpose: Creates an element in the specified DOM Document // // Arguments: // pxdd [in] DOM Document to create element in // szName [in] Element name // ppxdnElement [out] Returns the newly created element // // Returns: S_OK if successful, E_OUTOFMEMORY, or OLE error code // // Author: danielwe 16 Aug 2000 // // Notes: The element is NOT automatically inserted into the document // HRESULT HrCreateElement(IXMLDOMDocument *pxdd, LPCWSTR szName, IXMLDOMNode **ppxdnElement) { HRESULT hr = S_OK; BSTR bstrElementName; Assert(pxdd); Assert(ppxdnElement); *ppxdnElement = NULL; bstrElementName = SysAllocString(szName); if (bstrElementName) { IXMLDOMNode * pxdn; BSTR bstrNamespaceURI; bstrNamespaceURI = SysAllocString(L"urn:schemas-upnp-org:event-1-0"); if (bstrNamespaceURI) { VARIANT varNodeType; VariantInit(&varNodeType); varNodeType.vt = VT_I4; V_I4(&varNodeType) = (int) NODE_ELEMENT; hr = pxdd->createNode(varNodeType, bstrElementName, bstrNamespaceURI, &pxdn); if (SUCCEEDED(hr)) { *ppxdnElement = pxdn; } else { ReleaseObj(pxdn); } SysFreeString(bstrNamespaceURI); } else { hr = E_OUTOFMEMORY; } SysFreeString(bstrElementName); } else { hr = E_OUTOFMEMORY; } TraceError("HrAddRootElement", hr); return hr; } //+--------------------------------------------------------------------------- // // Function: HrComposeEventBody // // Purpose: Composes the event notification body given a list of names, // values, and data types // // Arguments: // cVars [in] Number of state variables to work with // rgszNames [in] List of variable names // rgszTypes [in] List of variable types // rgvarValues [in] List of variable values // pszBody [out] Returns newly created XML body as a string // // Returns: S_OK if successful, E_OUTOFMEMORY, or OLE error code // // Author: danielwe 16 Aug 2000 // // Notes: pszBody must be freed by the caller with delete [] // HRESULT HrComposeEventBody(IUPnPAutomationProxy* puap, DWORD cVars, LPWSTR *rgszNames, LPWSTR *rgszTypes, VARIANT *rgvarValues, LPWSTR *pszBody) { HRESULT hr = S_OK; IXMLDOMDocument * pxdd; Assert(pszBody); Assert(cVars); *pszBody = NULL; // Create a new XML DOM Document // hr = CoCreateInstance(CLSID_DOMDocument30, NULL, CLSCTX_INPROC_SERVER, IID_IXMLDOMDocument, (LPVOID *) &pxdd); if (SUCCEEDED(hr)) { IXMLDOMNode * pxdnRoot; hr = HrAppendProcessingInstruction(pxdd, L"xml", L"version=\"1.0\""); if (SUCCEEDED(hr)) { hr = HrCreateElement(pxdd, L"e:propertyset", &pxdnRoot); if (SUCCEEDED(hr)) { IXMLDOMElement* pxde; hr = pxdnRoot->QueryInterface(IID_IXMLDOMElement, (void**)&pxde); if (SUCCEEDED(hr)) { Assert(puap); LPWSTR szServiceType = NULL; hr = puap->GetServiceType(&szServiceType); if (SUCCEEDED(hr)) { HrSetTextAttribute(pxde, L"xmlns:s", szServiceType); CoTaskMemFree(szServiceType); } pxde->Release(); } } } if (SUCCEEDED(hr)) { DWORD iVar; // Loop thru each passed in variable and create a typed element // for each, adding to a new container element for "property" as // described in the UPnP architecture 1.0. for (iVar = 0; iVar < cVars && SUCCEEDED(hr); iVar++) { IXMLDOMNode * pxdnProp; hr = HrCreateElement(pxdd, L"e:property", &pxdnProp); if (SUCCEEDED(hr)) { IXMLDOMElement * pxdeVar; LPWSTR szPrefixedName = new WCHAR[lstrlenW(rgszNames[iVar]) + 3]; if (szPrefixedName) { lstrcpyW(szPrefixedName, L"s:"); lstrcatW(szPrefixedName, rgszNames[iVar]); hr = HrCreateElementWithType(pxdd, szPrefixedName, (LPCWSTR)rgszTypes[iVar], rgvarValues[iVar], &pxdeVar); if (SUCCEEDED(hr)) { // Add both the container "" and the // variable "" to the root // hr = pxdnRoot->appendChild(pxdnProp, NULL); hr = pxdnProp->appendChild(pxdeVar, NULL); ReleaseObj(pxdeVar); } ReleaseObj(pxdnProp); delete [] szPrefixedName; } else { hr = E_OUTOFMEMORY; } } } if (SUCCEEDED(hr)) { // Add the root element to the DOM itself hr = pxdd->appendChild(pxdnRoot, NULL); } // If all went well, ask the document to give us the XML as a string // if (SUCCEEDED(hr)) { BSTR bstrBody; hr = pxdd->get_xml(&bstrBody); if (SUCCEEDED(hr)) { DWORD cch = SysStringLen(bstrBody) + 1; LPWSTR szBody; szBody = new WCHAR[cch]; if (szBody) { lstrcpy(szBody, bstrBody); TraceTag(ttidDefault, "Body is %S", szBody); *pszBody = szBody; } else { hr = E_OUTOFMEMORY; } SysFreeString(bstrBody); } } ReleaseObj(pxdnRoot); } ReleaseObj(pxdd); } TraceError("HrComposeEventBody", hr); return hr; }