//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1997. // // File: T F I N D . C P P // // Contents: Asynchronous find mechanism for UPnP tray monitor. // // Notes: // // Author: jeffspr 22 Nov 1999 // //---------------------------------------------------------------------------- #include "pch.h" #pragma hdrstop #include #include "tfind.h" #include "clist.h" #include "clistndn.h" #include "tconst.h" extern UINT g_iTotalBalloons; CListString g_CListUDN; CListString g_CListUDNSearch; CListNDN g_CListNewDeviceNode; CListNameMap g_CListNameMap; BOOL g_fIconPresent = FALSE; // (tongl) // List of devices currently on the network // This is the cached device list to show in "My Network Places" folder CListFolderDeviceNode g_CListFolderDeviceNode; CRITICAL_SECTION g_csFolderDeviceList; // CLSID_NetworkNeighborhood CONST WCHAR c_szNetworkNeighborhoodFolderPath[] = L"::{208D2C60-3AEA-1069-A2D7-08002B30309D}\\"; // CLSID UPnP Delegate Folder - note "," is used for the new parse syntax (guru) CONST WCHAR c_szUPnPFolderPath[] = L"::{e57ce731-33e8-4c51-8354-bb4de9d215d1},"; CONST WCHAR c_szDelegateFolderPrefix[] = L"__upnpitem:"; CONST SIZE_T c_cchDelegateFolderPrefix = celems(c_szDelegateFolderPrefix) - 1; BOOL CListFolderDeviceNode::FCompare(FolderDeviceNode * pNode, PCWSTR pszUDN) { // see whether UDNs match between two device nodes Assert(pNode); Assert(pszUDN); return (wcscmp(pNode->pszUDN, pszUDN) == 0); } BOOL CListNDN::FCompare(NewDeviceNode * pNode, LPCTSTR pszUDN) { // see whether UDNs match between two device nodes Assert(pNode); Assert(pszUDN); return (_tcscmp(pNode->pszUDN, pszUDN) == 0); } BOOL CListString::FCompare(LPTSTR pszCurrentNodeString, LPCTSTR pszKey) { // see whether UDNs match between two device nodes Assert(pszCurrentNodeString); Assert(pszKey); return (_tcscmp(pszCurrentNodeString, pszKey) == 0); } BOOL CListNameMap::FCompare(NAME_MAP *pnm, LPCTSTR szUdn) { // see whether UDNs match between two device nodes Assert(pnm); Assert(szUdn); return (_tcscmp(pnm->szUdn, szUdn) == 0); } TCHAR * BSTRToTsz(BSTR bstrInput) { return TszFromWsz(bstrInput); } // The string created is: // "::CLSID_NetworkNeighborhood\\__upnpitem:UPNP_device_UDN" // // The new shell changes // "::CLSID_NetworkNeighborhood\\::GUID for UPnP Delegate Folder, // __upnpitem:UDN of the device LPWSTR CreateChangeNotifyString(LPCWSTR pszUdn) { LPWSTR pszNotifyString; pszNotifyString = new WCHAR [ MAX_PATH ]; if (pszNotifyString) { Assert((celems(c_szNetworkNeighborhoodFolderPath) + celems(c_szDelegateFolderPrefix)) < MAX_PATH); CONST SIZE_T cchMax = MAX_PATH - (celems(c_szNetworkNeighborhoodFolderPath) * sizeof(WCHAR)) - (celems(c_szUPnPFolderPath) * sizeof(WCHAR)) - (celems(c_szDelegateFolderPrefix) * sizeof(WCHAR)) + 2; // +2 we have subtracted Three null characters // note: we know that the folder path and the prefix can fit // in the MAX_PATH buffer wcscpy(pszNotifyString, c_szNetworkNeighborhoodFolderPath); wcscat(pszNotifyString, c_szUPnPFolderPath); wcscat(pszNotifyString, c_szDelegateFolderPrefix); wcsncat(pszNotifyString, pszUdn, cchMax); } else { TraceTag(ttidShellFolder, "CreateChangeNotifyString: new failed"); } return pszNotifyString; } //+--------------------------------------------------------------------------- // // Function: NewDeviceNode::NewDeviceNode // // Purpose: Initializes a NewDeviceNode structure. // // Notes: // NewDeviceNode::NewDeviceNode() { pszUDN = NULL; pszDisplayName = NULL; pszType = NULL; pszPresentationURL = NULL; pszManufacturerName = NULL; pszModelName = NULL; pszModelNumber = NULL; pszDescription = NULL; } //+--------------------------------------------------------------------------- // // Function: NewDeviceNode::~NewDeviceNode // // Purpose: Deletes a NewDeviceNode structure. // // Author: donryan 18 Feb 2000 // // Notes: Moved from CUPnPMonitorDeviceFinderCallback::DeviceAdded // NewDeviceNode::~NewDeviceNode() { delete pszUDN; delete pszDisplayName; delete pszType; delete pszPresentationURL; delete pszManufacturerName; delete pszModelName; delete pszModelNumber; delete pszDescription; } //+--------------------------------------------------------------------------- // // Function: HrMapUdnToFriendlyName // // Purpose: Given a UPnP device and UDN, maps the UDN to a friendly name // from the registry if it is present, otherwise falls back to // the friendly name from the device // // Arguments: // pdev [in] UPnP device to check // bstrUdn [in] UDN of device // pbstrName [out] Returns friendly name of device // // Returns: S_OK if success, E_OUTOFMEMORY if no memory, or Win32 error // code // // Author: danielwe 2000/10/25 // // Notes: // HRESULT HrMapUdnToFriendlyName(IUPnPDevice *pdev, BSTR bstrUdn, BSTR *pbstrName) { HRESULT hr = S_OK; NAME_MAP * pnm = NULL; Assert(pbstrName); *pbstrName = NULL; if (g_CListNameMap.FFind(bstrUdn, &pnm)) { Assert(pnm); *pbstrName = SysAllocString(pnm->szName); if (!*pbstrName) { hr = E_OUTOFMEMORY; } } else { // UDN doesn't have a mapping in registry. Fall back to device's // friendly name // hr = pdev->get_FriendlyName(pbstrName); } TraceError("HrMapUdnToFriendlyName", hr); return hr; } //+--------------------------------------------------------------------------- // // Function: HrCreateDeviceNodeFromDevice // // Purpose: Utility function to transfer information from device object // into NewDeviceNode structure. // // Arguments: // pDevice [in] The device pointer // ppNDN [out] The pointer to the NewDeviceNode pointer. // // Returns: // // Author: donryan 18 Feb 2000 // // Notes: Moved from CUPnPMonitorDeviceFinderCallback::DeviceAdded // HRESULT HrCreateDeviceNodeFromDevice( IUPnPDevice * pDevice, NewDeviceNode ** ppNDN ) { HRESULT hr = S_OK; BSTR bstrUDN = NULL; BSTR bstrDisplayName = NULL; BSTR bstrType = NULL; BSTR bstrPresentationURL = NULL; BSTR bstrManufacturerName = NULL; BSTR bstrModelName = NULL; BSTR bstrModelNumber = NULL; BSTR bstrDescription = NULL; PTSTR pszUDN = NULL; NewDeviceNode * pNDN = NULL; Assert(pDevice); Assert(ppNDN); hr = pDevice->get_UniqueDeviceName(&bstrUDN); if (FAILED(hr)) { TraceTag(ttidShellTray, "Error calling pDevice->get_UniqueDeviceName"); goto Exit; } hr = HrMapUdnToFriendlyName(pDevice, bstrUDN, &bstrDisplayName); if (FAILED(hr)) { goto Exit; } hr = pDevice->get_Type(&bstrType); if (FAILED(hr)) { TraceTag(ttidShellTray, "Error calling pDevice->get_Type"); goto Exit; } hr = pDevice->get_PresentationURL(&bstrPresentationURL); if (FAILED(hr)) { TraceTag(ttidShellTray, "Error calling pDevice->get_PresentationURL"); goto Exit; } hr = pDevice->get_ManufacturerName(&bstrManufacturerName); if (FAILED(hr)) { TraceTag(ttidShellTray, "Error calling pDevice->get_ManufacturerName"); goto Exit; } hr = pDevice->get_ModelName(&bstrModelName); if (FAILED(hr)) { TraceTag(ttidShellTray, "Error calling pDevice->get_ModelName"); goto Exit; } hr = pDevice->get_ModelNumber(&bstrModelNumber); if (FAILED(hr)) { TraceTag(ttidShellTray, "Error calling pDevice->get_ModelNumber"); goto Exit; } hr = pDevice->get_Description(&bstrDescription); if (FAILED(hr)) { TraceTag(ttidShellTray, "Error calling pDevice->get_Description"); goto Exit; } // Create a new Device node, copy the strings into it, and add it to the device map // pNDN = new NewDeviceNode; if (pNDN) { Assert(bstrUDN); pNDN->pszUDN = BSTRToTsz(bstrUDN); Assert(bstrDisplayName); pNDN->pszDisplayName = BSTRToTsz(bstrDisplayName); Assert(bstrType); pNDN->pszType = BSTRToTsz(bstrType); if (bstrPresentationURL) { pNDN->pszPresentationURL = BSTRToTsz(bstrPresentationURL); } else { pNDN->pszPresentationURL = new TCHAR [ 1 ]; if (pNDN->pszPresentationURL) { pNDN->pszPresentationURL[0] = TEXT('\0'); } } Assert(bstrManufacturerName); pNDN->pszManufacturerName = BSTRToTsz(bstrManufacturerName); Assert(bstrModelName); pNDN->pszModelName = BSTRToTsz(bstrModelName); if (bstrModelNumber) { pNDN->pszModelNumber = BSTRToTsz(bstrModelNumber); } else { pNDN->pszModelNumber = new TCHAR [ 1 ]; if (pNDN->pszModelNumber) { pNDN->pszModelNumber[0] = TEXT('\0'); } } if (bstrDescription) { pNDN->pszDescription = BSTRToTsz(bstrDescription); } else { pNDN->pszDescription = new TCHAR [ 1 ]; if (pNDN->pszDescription) { pNDN->pszDescription[0] = TEXT('\0'); } } // If they didn't all copy fine, delete them // if (!(pNDN->pszUDN && pNDN->pszDisplayName && pNDN->pszType && pNDN->pszPresentationURL && pNDN->pszManufacturerName && pNDN->pszModelName && pNDN->pszModelNumber && pNDN->pszDescription)) { hr = E_OUTOFMEMORY; delete pNDN; pNDN = NULL; } } else { TraceTag(ttidShellTray, "Could not allocate NewDeviceNode"); hr = E_OUTOFMEMORY; } Exit: // transfer *ppNDN = pNDN; SysFreeString(bstrUDN); SysFreeString(bstrDisplayName); SysFreeString(bstrPresentationURL); SysFreeString(bstrType); SysFreeString(bstrManufacturerName); SysFreeString(bstrModelName); SysFreeString(bstrModelNumber); SysFreeString(bstrDescription); TraceHr(ttidShellTray, FAL, hr, FALSE, "HrCreateDeviceNodeFromDevice"); return hr; } //+--------------------------------------------------------------------------- // // Member: HrAddFolderDevice // // Purpose: // // Arguments: // pDevice // // Returns: // // Author: tongl 15 Feb 2000 // // Notes: // HRESULT HrAddFolderDevice(IUPnPDevice * pDevice) { HRESULT hr = S_OK; BSTR bstrUDN = NULL; BSTR bstrDisplayName = NULL; BSTR bstrType = NULL; BSTR bstrPresentationURL = NULL; BSTR bstrDescription = NULL; // (tongl) Per cmr, some device property (display name, presentation url) // could change on an existing device and this function will be called but // the device was not first removed. In this case we need to notify shell // to first remove the existing device then add the new one. BOOL fUpdate = FALSE; BOOL fUpdateOldDevice = FALSE; // fNewNode - If its really a new device // fUpdate - The device is in MNP but must be updated bcoz device property has // changed. Note above. // fUpdateOldDevice - An old device has sent bye bye and later comes up alive // with same UDN. We have cached the list of UDNs. // Assert(pDevice); pDevice->AddRef(); hr = pDevice->get_UniqueDeviceName(&bstrUDN); if (SUCCEEDED(hr)) { // If we're doing a search right now, just add this UDN to the list // of devices that we've found so far in the search // if (g_fSearchInProgress) { if (!g_CListUDNSearch.FAdd(WszDupWsz(bstrUDN))) { hr = E_OUTOFMEMORY; } } } else { TraceTag(ttidShellFolder, "Failed in pDevice->get_UniqueDeviceName from HrAddFolderDevice"); } if (SUCCEEDED(hr)) { hr = HrMapUdnToFriendlyName(pDevice, bstrUDN, &bstrDisplayName); if (SUCCEEDED(hr)) { hr = pDevice->get_Type(&bstrType); if (SUCCEEDED(hr)) { hr = pDevice->get_PresentationURL(&bstrPresentationURL); if (SUCCEEDED(hr)) { hr = pDevice->get_Description(&bstrDescription); if (SUCCEEDED(hr)) { BOOL fNewNode; EnterCriticalSection(&g_csFolderDeviceList); FolderDeviceNode * pNewDevice = NULL; if( g_CListFolderDeviceNode.FFind((PWSTR)bstrUDN, &pNewDevice)) { Assert(pNewDevice); fNewNode = FALSE; if (!pNewDevice->fDeleted) { // Only update if friendly name or description // changed. // if ((wcscmp(pNewDevice->pszDescription, bstrDescription)) || (wcscmp(pNewDevice->pszDisplayName, bstrDisplayName))) { fUpdate = TRUE; } } else { fUpdateOldDevice = TRUE; } } else { // truely a new device pNewDevice = new FolderDeviceNode; fNewNode = TRUE; if (!pNewDevice) { hr = E_OUTOFMEMORY; } } if (pNewDevice) { CONST SIZE_T cchMax = MAX_PATH - 1; pNewDevice->fDeleted = FALSE; Assert(bstrUDN); wcscpy(pNewDevice->pszUDN, L""); wcsncat(pNewDevice->pszUDN,(PWSTR)bstrUDN, cchMax); Assert(bstrDisplayName); wcscpy(pNewDevice->pszDisplayName, L""); wcsncat(pNewDevice->pszDisplayName, bstrDisplayName, cchMax); Assert(bstrType); wcscpy(pNewDevice->pszType, L""); wcsncat(pNewDevice->pszType, (PWSTR)bstrType, cchMax); wcscpy(pNewDevice->pszPresentationURL, L""); if (bstrPresentationURL) { wcsncat(pNewDevice->pszPresentationURL, (PWSTR)bstrPresentationURL, cchMax); } wcscpy(pNewDevice->pszDescription, L""); if (bstrDescription) { wcsncat(pNewDevice->pszDescription, (PWSTR)bstrDescription, cchMax); } // add to our list if a true new device if (fNewNode) { g_CListFolderDeviceNode.FAdd(pNewDevice); TraceTag(ttidShellFolder, "HrAddFolderDevice: Added %S to g_CListFolderDeviceNode", bstrDisplayName); } else { TraceTag(ttidShellFolder, "HrAddFolderDevice: Modified %S in g_CListFolderDeviceNode", bstrDisplayName); } TraceTag(ttidShellFolder, "HrAddFolderDevice: Now, g_CListFolderDeviceNode has %d elements", g_CListFolderDeviceNode.GetCount()); } LeaveCriticalSection(&g_csFolderDeviceList); if (fNewNode || fUpdate || fUpdateOldDevice) { if (SUCCEEDED(hr)) { // notify shell to add the new device LPWSTR pszNotifyString; pszNotifyString = CreateChangeNotifyString(bstrUDN); if (pszNotifyString) { if (fUpdate) { TraceTag(ttidShellFolder, "Generating update device event for %S", bstrDisplayName); SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW, pszNotifyString, NULL); } else { // Assert(fNewNode); // fNewNode or fUpdateOldDevice TraceTag(ttidShellFolder, "Generating new device event for %S", bstrDisplayName); SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, pszNotifyString, NULL); } delete [] pszNotifyString; } } else { // Don't make a failure here. // TraceTag(ttidShellFolder, "Couldn't add device to list in CUPnPDeviceFolderDeviceFinderCallback::DeviceAdded"); } } else { TraceTag(ttidShellFolder, "Nothing about device %S:%S changed", bstrUDN, bstrDisplayName); } } else { TraceTag(ttidShellFolder, "Failed in get_Description from HrAddFolderDevice"); } } else { TraceTag(ttidShellFolder, "Failed in get_PresentationURL from HrAddFolderDevice"); } } else { TraceTag(ttidShellFolder, "Failed in pDevice->get_Type from HrAddFolderDevice"); } } else { TraceTag(ttidShellFolder, "Failed in pDevice->get_FriendlyName from HrAddFolderDevice"); } } SysFreeString(bstrUDN); SysFreeString(bstrDisplayName); SysFreeString(bstrPresentationURL); SysFreeString(bstrType); SysFreeString(bstrDescription); pDevice->Release(); TraceHr(ttidError, FAL, hr, (S_FALSE == hr), "HrAddFolderDevice"); return hr; } //+--------------------------------------------------------------------------- // // Member: HrDeleteFolderDevice // // Purpose: // // Arguments: // szUDN // // Returns: // // Author: tongl 18 Feb 2000 // // Notes: // HRESULT HrDeleteFolderDevice(PWSTR szUDN) { HRESULT hr = S_OK; FolderDeviceNode * pDevice = NULL; EnterCriticalSection(&g_csFolderDeviceList); if (g_CListFolderDeviceNode.FFind(szUDN, &pDevice)) { pDevice->fDeleted = TRUE; } else { // can't delete a device that's not in our cache TraceTag(ttidShellFolder, "The device to delete is not in the cache: %S.", szUDN); hr = E_FAIL; } LeaveCriticalSection(&g_csFolderDeviceList); if (SUCCEEDED(hr)) { // notify shell to delete the device LPWSTR pszNotifyString; pszNotifyString = CreateChangeNotifyString(szUDN); if (pszNotifyString) { TraceTag(ttidShellFolder, "Delete device event for %S", szUDN); SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, pszNotifyString, NULL); delete [] pszNotifyString; } else { hr = E_OUTOFMEMORY; } } TraceError("HrDeleteFolderDevice", hr); return hr; } //+--------------------------------------------------------------------------- // // Member: CUPnPMonitorDeviceFinderCallback::DeviceAdded // // Purpose: Our callback for "new device" -- duh. Here we get the // important properties, pack them in a struct, and store them // in our device list. We'll use this data if the user opens // properties on one of the dialogs, or adds shortcuts. // // Arguments: // lFindData [in] Our callback ID // pDevice [in] The device pointer. // // Returns: // // Author: jeffspr 23 Nov 1999 // // Notes: // HRESULT CUPnPMonitorDeviceFinderCallback::DeviceAdded(LONG lFindData, IUPnPDevice * pDevice) { HRESULT hr = S_OK; NewDeviceNode * pNDN = NULL; Assert(pDevice); pDevice->AddRef(); #if DBG BSTR bstrUDN = NULL; hr = pDevice->get_UniqueDeviceName(&bstrUDN); TraceTag(ttidShellTray, "DeviceFinderCallback -- New Device. SearchId: %x, UDN: %S", lFindData, bstrUDN); SysFreeString(bstrUDN); #endif // DBG BSTR bstrPresUrl; hr = pDevice->get_PresentationURL(&bstrPresUrl); if (S_OK == hr) { URL_COMPONENTS urlComp = {0}; TraceTag(ttidShellTray, "Checking if %S is a valid URL...", bstrPresUrl); // All we want to do here is verify that the URL is valid. We don't // need anything back from this function // urlComp.dwStructSize = sizeof(URL_COMPONENTS); if (!InternetCrackUrl(bstrPresUrl, 0, 0, &urlComp)) { TraceTag(ttidShellTray, "%S is NOT a valid URL!", bstrPresUrl); hr = HrFromLastWin32Error(); } SysFreeString(bstrPresUrl); } else { hr = E_FAIL; TraceError("Device did not have presentation URL!", hr); } if (SUCCEEDED(hr)) { // (tongl) // add the device to our folder device list and notify shell in case folder is open hr = HrAddFolderDevice(pDevice); if (SUCCEEDED(hr)) { // transfer information from device object hr = HrCreateDeviceNodeFromDevice(pDevice, &pNDN); if (SUCCEEDED(hr)) { // Assuming we don't already have this device is our list, add it. // if (!g_CListUDN.FFind(pNDN->pszUDN, NULL)) { // Add it to the New Device list. // g_CListNewDeviceNode.FAdd(pNDN); // reset the total balloon count g_iTotalBalloons =0; } else { // Cleanup already known device // delete pNDN; } } } } pDevice->Release(); if (SUCCEEDED(hr)) { if (!g_fSearchInProgress) { hr = HrUpdateTrayInfo(); } } TraceError("CUPnPMonitorDeviceFinderCallback::DeviceAdded", hr); return S_OK; } //+--------------------------------------------------------------------------- // // Member: CUPnPMonitorDeviceFinderCallback::DeviceRemoved // // Purpose: Device removed notification. NYI // // Arguments: // lFindData [] // bstrUDN [] // // Returns: // // Author: jeffspr 23 Nov 1999 // // Notes: // HRESULT CUPnPMonitorDeviceFinderCallback::DeviceRemoved( LONG lFindData, BSTR bstrUDN) { TraceTag(ttidShellTray, "CUPnPMonitorDeviceFinderCallback::DeviceRemoved" " lFindData = %x, UDN = %S", lFindData, bstrUDN); Assert(bstrUDN); // (tongl) // delete the device from our folder device list and notify shell in case folder is open HrDeleteFolderDevice((PWSTR)bstrUDN); HRESULT hr = S_OK; LPTSTR pszUdn; BOOL fResult; pszUdn = BSTRToTsz(bstrUDN); if (!pszUdn) { TraceTag(ttidShellTray, "Could not copy UDN to TCHAR"); hr = E_OUTOFMEMORY; goto Exit; } // search through new device list for new node, removing any nodes // with matching UDNs // fResult = g_CListNewDeviceNode.FDelete(pszUdn); if (!fResult) { // node wasn't deleted TraceTag(ttidShellTray, "CUPnPMonitorDeviceFinderCallback::DeviceRemoved: " "%S not found in g_CListNewDeviceNode", pszUdn); } // update tray information if search has completed if (!g_fSearchInProgress) { hr = HrUpdateTrayInfo(); } else { // search is still running so delete this from the search list fResult = g_CListUDNSearch.FDelete(pszUdn); if (!fResult) { // node wasn't deleted TraceTag(ttidShellTray, "CUPnPMonitorDeviceFinderCallback::DeviceRemoved: " "%S not found in g_CListUDNSearch", pszUdn); } } Exit: delete [] pszUdn; TraceError("CUPnPMonitorDeviceFinderCallback::DeviceRemoved", hr); return S_OK; } //+--------------------------------------------------------------------------- // // Member: CUPnPMonitorDeviceFinderCallback::SearchComplete // // Purpose: Search complete. At this point I can add the tray icon // if needed and allow for the UI to come up. // // Arguments: // lFindData [] // // Returns: // // Author: jeffspr 6 Jan 2000 // // Notes: // HRESULT CUPnPMonitorDeviceFinderCallback::SearchComplete(LONG lFindData) { TraceTag(ttidShellTray, "CUPnPMonitorDeviceFinderCallback::SearchComplete" " lFindData = %x", lFindData); HRESULT hr = S_OK; g_fSearchInProgress = FALSE; // Add the tray icon and such (if appropriate) // hr = HrInitializeUI(); if (SUCCEEDED(hr)) { EnterCriticalSection(&g_csFolderDeviceList); FolderDeviceNode * pCurrentNode = NULL; BOOL fReturn = g_CListFolderDeviceNode.FFirst(&pCurrentNode); // Loop through all UDNs in the cached list of devices we've found // and for each one, see if it was found during the search. If not, // we should delete it from the folder view and from the cache. while (fReturn && SUCCEEDED(hr)) { LPWSTR szUdn; if (!g_CListUDNSearch.FFind(pCurrentNode->pszUDN, &szUdn)) { hr = HrDeleteFolderDevice(pCurrentNode->pszUDN); if (SUCCEEDED(hr)) { if (!g_CListFolderDeviceNode.FDelete(pCurrentNode->pszUDN)) { hr = E_FAIL; } } } // move to the next node fReturn = g_CListFolderDeviceNode.FNext(&pCurrentNode); } LeaveCriticalSection(&g_csFolderDeviceList); } g_CListUDNSearch.Flush(); TraceError("CUPnPMonitorDeviceFinderCallback::SearchComplete", hr); return S_OK; } CUPnPMonitorDeviceFinderCallback::CUPnPMonitorDeviceFinderCallback() { m_pUnkMarshaler = NULL; TraceTag(ttidShellTray, "CUPnPMonitorDeviceFinderCallback"); } CUPnPMonitorDeviceFinderCallback::~CUPnPMonitorDeviceFinderCallback() { TraceTag(ttidShellTray, "~CUPnPMonitorDeviceFinderCallback"); }