//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation, 2000 // // File: propbag.cpp // // This property bag implementation supports the standard COM interface // IPropertyBag as well as the specialized interface ITaskSheetPropertyBag. // // The following facilities are available through ITaskSheetPropertyBag. // // 1. Property change notifications. // 2. Constants // 3. Property groups (namespace scoping) // 4. Use IDs or names for property identification // // // Here's the general structure of the implementation. // // The implementation uses 8 C++ classes. // // CPropertyBag - The property bag. // // CPropertyGroup - A group of properties. All properties in a group // exist in their own namespace. A default 'global' group is always // present. A property bag can have an unlimited number of groups. // Groups can be added and removed dynamically by the application. // // CPropertyBucket - A set of properties who's identifiers (ID or name) // hash to the same value. It's just a simple container to manage // hash table collisions. Each property group has HASH_TABLE_SIZE - 1 // buckets. Buckets are created on-demand so an unused hash value // has a minimal cost. // // CProperty - A property in the property bag. A property has an // identifier (ID or name), a value and a set of flags. Currently, // only the "CONSTANT" flag is used. A property may be a read-write // or a constant value. Each property bucket contains one or more // CProperty objects. CProperty objects may not be removed individually. // They are removed only through destruction of the property bag or // removal of the parent property group. // // CPropertyName - The identifier of a property. Abstracts the use // of both a property ID and property name from the rest of the // implementation. Each CProperty object has one CPropertyName // member. Handles identity comparison of two properties. // // CNotifyMap - A simple bit map used to represent notification clients // interested in changes to one or more properties in the property // bag. Each bit corresponds directly to an entry in the "notify // client table" in the CNotifier object. The table is merely an // array of pointers to ITaskSheetPropertyNotifySink. Therefore, if // bit 5 is set, that means the the client who's notify sink pointer // is stored in element 5 of the notify table wants to be notified. // // Each of the following objects has a notify map: // // 1. CPropertyBag - This is the 'global' notify map. A bit set // in this map indicates the corresponding client is // interested in changes to ANY property in the bag. // // 2. CPropertyGroup - A bit set in a group's notify map indicates // the corresponding client is interested in changes to // any property in the group. // // 3. CProperty - A bit set in a property's notify map indicates // the corresponding client is interested in changes // to this property. // // 4. CNotifier - This is the 'active' notify map and is used // to indicate which clients are to receive notification // of the change to the property currently being changed // by a 'set' operation. See below for a description of // the CNotifier object. // // CNotifier - Handles registration of notification clients and performs // client notifications when properties change. The property bag // has a single CNotifier member. When a client registers for change // notifications, the client's ITaskSheetPropertyNotifySink pointer // is stored in the notifier's client table (array of ptrs). The // index of the pointer is the "cookie" returned to the client. // When a "set" operation begins, the notifier is initialized and // a reference to it is passed down the function call chain // (bag->group->bucket->property). At the 'bag', 'group' and 'property' // points in the call stack each respective object's "notify map" is // merged into the notifier's 'active' notify map. Once the call reaches // the point of setting the property value, the map contains bits set to // represent each of the notification clients interested in changes to // this property. The notifier is then called to notify all interested // clients. Clients may be dynamically added or removed at any time. // // // // The implementation of IPropertyBag automatically uses the 'global' // property group. Therefore, if you were to write some properties // through IPropertyBag::Write, those properties would be avaialble through // the 'global' property group (PROPGROUP_GLOBAL) when using // ITaskSheetPropertyBag. // // // [brianau - 06/20/00] // //-------------------------------------------------------------------------- #include "stdafx.h" #include "taskui.h" #include "cdpa.h" // // Size of each property group hash table. This should be a prime // number for best hashing distribution. // const int HASH_TABLE_SIZE = 13; // // The type of data used by the change notify map. // A ULONGLONG provides 64 bits which means we can have 64 registered // notification clients. Use a DWORD and this drops to 32. // typedef ULONGLONG NOTIFYMAPTYPE; // // The maximum number of unique change notify connections. It's just the // number of bits available in a notify map. // const int MAX_NOTIFY_CNX = sizeof(NOTIFYMAPTYPE) * 8; // // ---------------------------------------------------------------------------- // CASE SENSITIVITY // ---------------------------------------------------------------------------- // The following macro controls the case sensitivity of the property bag. // By default the macro is undefined and the property bag is case-insensitive // with respect to property names. To make the bag case sensitive, uncomment // this macro and recompile. // // #define TASKUI_PROPBAG_CASE_SENSITIVE // inline int ComparePropertyNames(LPCWSTR s1, LPCWSTR s2) { #ifdef TASKUI_PROPBAG_CASE_SENSITIVE return lstrcmpW(s1, s2); #else return lstrcmpiW(s1, s2); #endif } inline int CharValue(WCHAR c) { #ifdef TASKUI_PROPBAG_CASE_SENSITIVE return c; #else return towlower(c); #endif } // // Validate the writeability of an "out" pointer. This just saves // us from entering the sizeof() part, potentially reducing bugs. // template inline bool IsValidWritePtr(T *p) { return (!IsBadWritePtr(p, sizeof(*p))); } //----------------------------------------------------------------------------- // CNotifyMap // // Represents a 64-bit bitmap for the purpose of representing notification // registrations in the property bag. There are 3 levels of notify maps // in the system. // // 1. Global - owned by the CPropertyBag object. // 2. Group - one owned by each CPropertyGroup object. // 3. Property - one owned by each CProperty object. // // Whenever a client registers for notification at one of these levels, // the appropriate map is located and the bit corresponding to that client's // connection cookie is set. When a property is set, the union of all three // maps represents the clients requesting notification. // //----------------------------------------------------------------------------- class CNotifyMap { public: CNotifyMap(void) : m_bits(0) { } bool IsSet(int iBit) const; HRESULT Set(int iBit, bool bSet); bool AnySet(void) const { return NOTIFYMAPTYPE(0) != m_bits; } void Clear(void) { m_bits = NOTIFYMAPTYPE(0); } void Union(const CNotifyMap& rhs) { m_bits |= rhs.m_bits; } private: NOTIFYMAPTYPE m_bits; bool _IsValidBitIndex(int iBit) const { return (iBit < (sizeof(m_bits) * 8)); } NOTIFYMAPTYPE _MaskFromBit(int iBit) const { return NOTIFYMAPTYPE(1) << iBit; } }; // // Set or clear a bit corresponding to a client // // Returns: // S_OK - Bit was set or cleared. // TSPB_E_NOTIFYCOOKIE - Passed an invalid bit index. // HRESULT CNotifyMap::Set( int iBit, bool bSet // true == set bit, false == clear ) { HRESULT hr = TSPB_E_NOTIFYCOOKIE; ASSERT(_IsValidBitIndex(iBit)); if (_IsValidBitIndex(iBit)) { if (bSet) { m_bits |= _MaskFromBit(iBit); } else { m_bits &= ~(_MaskFromBit(iBit)); } hr = S_OK; } return hr; } // // Determines if a given bit is set in the map. // On 'free' builds, returns false if an invalid bit number // is specified. Asserts on 'check' builds. // bool CNotifyMap::IsSet( int iBit ) const { ASSERT(_IsValidBitIndex(iBit)); return (_IsValidBitIndex(iBit) && (0 != (_MaskFromBit(iBit) & m_bits))); } //----------------------------------------------------------------------------- // CNotifier // // This class handles all of the duties of client notification including // client registration, client unregistration and client notification. // // A property bag has a notifier as a member. When a client wishes to // register for a change notification, the property bag calls CNotifier::Register // to create an entry in the client table. The cookie returned is merely the // index into the client table. // // When a "set prop" operation begins, the notifier's notification map is set // equal to the bag's global notify map and the prop group handle is set to that // of the group specified in the "set" operation. A reference to the // notifier is then passed down the chain of "set" calls // (bag -> group -> property). At each point the notify maps are merged // (union) so that the map in the notifier represents all requested // notifications. Finally, the CProperty::SetValue method sets the property // then calls CNotifier::Notify. The notifier then scans it's current notify // map and notifies each client corresponding to bits set. // // That sounds like a lot but it really is quite simple and happens very fast. // The best thing is that is cleanly handles dynamic registration and // unregstration of clients as well as addition and removal of properties // and property categories. // //----------------------------------------------------------------------------- class CNotifier { public: explicit CNotifier(ITaskSheetPropertyBag *pBag); ~CNotifier(void); HRESULT Register(ITaskSheetPropertyNotifySink *pClient, DWORD *pdwCookie); HRESULT Unregister(DWORD dwCookie); void Notify(LPCWSTR pszPropertyName); void MergeWithActiveNotifyMap(const CNotifyMap& map) { m_NotifyMapActive.Union(map); } void Reset(void) { m_NotifyMapActive.Clear(); m_hGroup = PROPGROUP_INVALID; } void SetPropertyGroup(HPROPGROUP hGroup) { ASSERT(PROPGROUP_INVALID != hGroup); m_hGroup = hGroup; } private: // // A single entry in the connection table. // Why use an array of structures rather than simply an array // of client pointers? Good question! While developing this property // bag I've twice stored something along with the client pointer in each // client table slot. Once a ref count, the other time a private data // ptr. As the final design solidified, neither were required. However, // I've left the structure just in case we need to again store something // with the client notification pointer. [brianau - 6/20/00]. // struct Entry { ITaskSheetPropertyNotifySink *pClient; // Client's notification interface ptr. }; ITaskSheetPropertyBag *m_pBag; // Don't addref. Will create circular reference Entry m_rgCnx[MAX_NOTIFY_CNX]; // The connection table. CNotifyMap m_NotifyMapActive; // The "active" notify map. HPROPGROUP m_hGroup; // Handle of group associated with prop. // with the property bag. HRESULT _FindEntry(ITaskSheetPropertyNotifySink *pClient, Entry **ppEntry, DWORD *pdwIndex); HRESULT _FindFirstUnusedEntry(Entry **ppEntry, DWORD *pdwIndex); // // Prevent copy. // CNotifier(const CNotifier& rhs); CNotifier& operator = (const CNotifier& rhs); }; //----------------------------------------------------------------------------- // CNotifier //----------------------------------------------------------------------------- CNotifier::CNotifier( ITaskSheetPropertyBag *pBag ) : m_hGroup(PROPGROUP_INVALID), m_pBag(pBag) { ASSERT(NULL != m_pBag); ZeroMemory(m_rgCnx, sizeof(m_rgCnx)); // // Note that we DON'T addref the property bag interface. // Since the CNotifier object is a member of CPropertyBag, // that would create a circular reference with the property bag. // We can be assured that the notifier's lifetime is contained // within the property bag's lifetime. // } CNotifier::~CNotifier( void ) { for (int i = 0; i < ARRAYSIZE(m_rgCnx); i++) { if (NULL != m_rgCnx[i].pClient) { m_rgCnx[i].pClient->Release(); } } // // DON'T Release m_pBag. See ctor above for details. // } // // Register a notifiation client. Returns Cookie in pdwCookie to be // used in call to Unregister if unregistration is desired. // // Returns: // S_OK - Registration successful. // S_FALSE - Already registered. // HRESULT CNotifier::Register( ITaskSheetPropertyNotifySink *pClient, DWORD *pdwCookie // Optional. May be NULL. ) { ASSERT(NULL != pClient); Entry *pEntry = NULL; DWORD dwIndex = DWORD(-1); HRESULT hr = _FindEntry(pClient, &pEntry, &dwIndex); if (S_OK == hr) { // // Found existing entry for this client. // hr = S_FALSE; } else if (S_FALSE == hr) { // // Need to create a new entry for this client. // Find a slot for it. // hr = _FindFirstUnusedEntry(&pEntry, &dwIndex); if (SUCCEEDED(hr)) { ASSERT(NULL == pEntry->pClient); pClient->AddRef(); pEntry->pClient = pClient; hr = S_OK; } } if (SUCCEEDED(hr) && NULL != pdwCookie) { ASSERT(IsValidWritePtr(pdwCookie)); ASSERT(DWORD(-1) != dwIndex); *pdwCookie = dwIndex; } return hr; } // // Unregister a client to cancel it's reception of all notifications. // The dwCookie parameter is the one received via the Register method. // // Returns: // S_OK - Connection unregistered. // TSPB_E_NOTIFYCOOKIE - Cookie references an invalid connection. // HRESULT CNotifier::Unregister( DWORD dwCookie ) { HRESULT hr = TSPB_E_NOTIFYCOOKIE; if (int(dwCookie) >= 0 && int(dwCookie) < ARRAYSIZE(m_rgCnx)) { Entry& entry = m_rgCnx[dwCookie]; if (NULL != entry.pClient) { entry.pClient->Release(); entry.pClient = NULL; hr = S_OK; } } return hr; } // // Called by CProperty::SetValue when a value has been altered. // This function scans the active notify map and notifies each // registered client specified by a bit set in the map. // void CNotifier::Notify( LPCWSTR pszPropertyName ) { ASSERT(NULL != m_pBag); ASSERT(NULL != pszPropertyName); if (m_NotifyMapActive.AnySet()) { // // We pass a pointer to the property bag in the notification // so we AddRef it to ensure it remains alive across the // notification. // m_pBag->AddRef(); // // As we notify clients we'll clear the corresponding bit in // notify map. This way we examine the map only as long as // necessary. // for (int i = 0; i < ARRAYSIZE(m_rgCnx) && m_NotifyMapActive.AnySet(); i++) { if (m_NotifyMapActive.IsSet(i)) { ITaskSheetPropertyNotifySink *pClient = m_rgCnx[i].pClient; ASSERT(NULL != pClient); if (NULL != pClient) { pClient->OnPropChanged(m_pBag, m_hGroup, pszPropertyName); m_NotifyMapActive.Set(i, false); } } } m_pBag->Release(); } // // Reset our internal state in preparation for the next // property notify. The map should be clear at this point. // ASSERT(!m_NotifyMapActive.AnySet()); Reset(); } // // Locates an entry in the client table. // The pClient argument is the key. // Using COM identity rules, we QI for IUnknown on both the key // interface and each of the table entries. If IUnknown ptrs // match then we found the entry. // // Returns: // S_OK - Entry found. // S_FALSE - Entry not found. // // HRESULT CNotifier::_FindEntry( ITaskSheetPropertyNotifySink *pClient, // Looking for this client. Entry **ppEntry, DWORD *pdwIndexOut // Optional. Can be NULL. ) { ASSERT(NULL != pClient); ASSERT(NULL != ppEntry); ASSERT(IsValidWritePtr(ppEntry)); Entry *pEntry = NULL; DWORD dwIndex = DWORD(-1); IUnknown *pUnkClient; // // Get the IUnknown interface pointer from both the key and // each entry. When we find a match we've found the entry. // HRESULT hr = pClient->QueryInterface(IID_IUnknown, (void **)&pUnkClient); if (SUCCEEDED(hr)) { for (int i = 0; i < ARRAYSIZE(m_rgCnx) && NULL == pEntry; i++) { if (NULL != m_rgCnx[i].pClient) { IUnknown *pUnkEntry; hr = m_rgCnx[i].pClient->QueryInterface(IID_IUnknown, (void **)&pUnkEntry); if (SUCCEEDED(hr)) { if (pUnkEntry == pUnkClient) { pEntry = &m_rgCnx[i]; dwIndex = i; } pUnkEntry->Release(); } } } pUnkClient->Release(); } *ppEntry = pEntry; if (NULL != pEntry) { // // Entry found. // ASSERT(DWORD(-1) != dwIndex); if (NULL != pdwIndexOut) { ASSERT(IsValidWritePtr(pdwIndexOut)); *pdwIndexOut = dwIndex; } hr = S_OK; } else if (hr == S_OK) { // // Entry not found. // ASSERT(NULL == pEntry); ASSERT(DWORD(-1) == dwIndex); hr = S_FALSE; } return hr; } // // Search the table for the first unused entry. // Returns: // S_OK - Unused entry found. // TSPB_E_MAXNOTIFYCNX - Notify client table is full. // HRESULT CNotifier::_FindFirstUnusedEntry( Entry **ppEntry, DWORD *pdwIndexOut // Optional. May be NULL. ) { ASSERT(NULL != ppEntry); ASSERT(IsValidWritePtr(ppEntry)); HRESULT hr = TSPB_E_MAXNOTIFYCNX; Entry *pEntry = NULL; DWORD dwIndex = DWORD(-1); for (int i = 0; i < ARRAYSIZE(m_rgCnx); i++) { if (NULL == m_rgCnx[i].pClient) { pEntry = &m_rgCnx[i]; dwIndex = i; hr = S_OK; break; } } *ppEntry = pEntry; if (SUCCEEDED(hr)) { if (NULL != pdwIndexOut) { ASSERT(DWORD(-1) != dwIndex); ASSERT(IsValidWritePtr(pdwIndexOut)); *pdwIndexOut = dwIndex; } } ASSERT(FAILED(hr) || DWORD(-1) != dwIndex); ASSERT(FAILED(hr) || NULL != pEntry); return hr; } //----------------------------------------------------------------------------- // CPropertyName // // This class handles the dual-nature of property names. A name can either be // a text string or a property ID created wtih the MAKEPROPID macro. Prop // IDs are used the same was as Windows Resource IDs thorugh the // MAKEINTRESOURCE macro. //----------------------------------------------------------------------------- class CPropertyName { public: CPropertyName(LPCWSTR pszName); ~CPropertyName(void); bool IsValid(void) const { return NULL != m_pszName; } bool CompareEqual(LPCWSTR pszName) const; operator LPCWSTR() const { return m_pszName; } private: LPWSTR m_pszName; // // Prevent copy. // CPropertyName(const CPropertyName& rhs); CPropertyName& operator = (const CPropertyName& rhs); static bool IsPropID(LPCWSTR pszName) { return IS_PROPID(pszName); } }; CPropertyName::CPropertyName( LPCWSTR pszName ) : m_pszName(NULL) { ASSERT(NULL != pszName); if (!IsPropID(pszName)) { m_pszName = StrDupW(pszName); } else { m_pszName = (LPWSTR)pszName; } } CPropertyName::~CPropertyName( void ) { if (!IsPropID(m_pszName)) { if (NULL != m_pszName) { LocalFree(m_pszName); } } } bool CPropertyName::CompareEqual ( LPCWSTR pszName ) const { bool bEqual = false; // // The pszName (and m_pszName) values can be either a pointer to // a name string or a property ID. // const bool bLhsIsID = IsPropID(m_pszName); const bool bRhsIsID = IsPropID(pszName); if (bLhsIsID == bRhsIsID) { if (bLhsIsID) { bEqual = (m_pszName == pszName); } else { bEqual = (0 == ComparePropertyNames(m_pszName, pszName)); } } return bEqual; } //----------------------------------------------------------------------------- // CProperty // // Represents a single property in the property bag. //----------------------------------------------------------------------------- class CProperty { public: static HRESULT CreateInstance(LPCWSTR pszName, const VARIANT *pVar, bool bConstant, CProperty **ppPropOut); ~CProperty(void); HRESULT SetValue(const VARIANT *pVar, CNotifier& notifier); HRESULT GetValue(VARIANT *pVarOut) const; LPCWSTR GetName(void) const { return m_Name; } bool CompareNamesEqual(LPCWSTR pszName) const { return m_Name.CompareEqual(pszName); } HRESULT Advise(int iClient) { m_NotifyMapProperty.Set(iClient, true); return S_OK; } HRESULT Unadvise(int iClient) { m_NotifyMapProperty.Set(iClient, false); return S_OK; } private: CProperty(LPCWSTR pszName, const VARIANT *pVar, bool bConstant); enum { CONSTANT = 0x00000001 }; CPropertyName m_Name; // The property name or prop ID. CComVariant m_Value; // The value. DWORD m_dwFlags; // Flags like 'CONSTANT'. CNotifyMap m_NotifyMapProperty; // Clients who want notification. // // Prevent copy. // CProperty(const CProperty& rhs); CProperty& operator = (const CProperty& rhs); }; // // Create a property. // HRESULT CProperty::CreateInstance( // [static] LPCWSTR pszName, const VARIANT *pVar, bool bConstant, CProperty **ppPropOut ) { ASSERT(NULL != pszName); ASSERT(NULL != ppPropOut); ASSERT(IsValidWritePtr(ppPropOut)); HRESULT hr = E_OUTOFMEMORY; *ppPropOut = NULL; CProperty *pProp = new CProperty(pszName, pVar, bConstant); if (NULL != pProp) { if (pProp->m_Name.IsValid()) { *ppPropOut = pProp; hr = S_OK; } else { delete pProp; } } return hr; } CProperty::CProperty( LPCWSTR pszName, const VARIANT *pVar, bool bConstant ) : m_dwFlags(0), m_Name(pszName) { ASSERT(NULL != pszName); ASSERT(NULL != pVar); if (bConstant) { m_dwFlags |= CONSTANT; } m_Value.Copy(pVar); } CProperty::~CProperty( void ) { } // // Set the property's value. // // Returns: // S_OK - Value set. // S_FALSE - New value same as old. Not changed. // TSPB_E_MODIFYCONST - Attempt to modify a const property. // HRESULT CProperty::SetValue( const VARIANT *pVar, CNotifier& notifier ) { ASSERT(NULL != pVar); HRESULT hr = TSPB_E_MODIFYCONST; if (0 == (CONSTANT & m_dwFlags)) { hr = S_FALSE; // Assume no change. // // Only set the value and notify clients if the value is // going to change. // if (m_Value != *pVar) { // // Copy the value. Must first clear the value to release // any VARIANT memory we might be holding. // m_Value.Clear(); hr = m_Value.Copy(pVar); if (SUCCEEDED(hr)) { // // Merge the property's notify map with the 'active' // notify map. The notifier's 'active' map is now the // union of the 'global' map, the 'group' map and the // 'property' map. It contains bits representing // all of the clients interested in this property change. // notifier.MergeWithActiveNotifyMap(m_NotifyMapProperty); // // Notify the clients whos bits are set in the 'active' notify // map. // notifier.Notify(m_Name); } } } return hr; } // // Retrieve the property's value. The value is copied // the output variant. // HRESULT CProperty::GetValue( VARIANT *pVarOut ) const { ASSERT(NULL != pVarOut); ASSERT(IsValidWritePtr(pVarOut)); // // Must first initialize the output variant before copying. // ::VariantInit(pVarOut); return ::VariantCopy(pVarOut, (VARIANT *)&m_Value); } //----------------------------------------------------------------------------- // CPropertyBucket // // This is a simple container representing the chain of collisions in // a conventional 'linear-chaining' hash table. The bucket is just a // DPA of CProperty object ptrs. //----------------------------------------------------------------------------- class CPropertyBucket { public: static HRESULT CreateInstance(CPropertyBucket **ppBucketOut); ~CPropertyBucket(void); HRESULT SetProperty(LPCWSTR pszName, const VARIANT *pVar, CNotifier& notifier); HRESULT SetConstProperty(LPCWSTR pszName, const VARIANT *pVAr); HRESULT GetProperty(LPCWSTR pszName, VARIANT *pVarOut) const; HRESULT Advise(LPCWSTR pszPropertyName, int iClient); HRESULT Unadvise(int iClient); private: CPropertyBucket(void); CDpa m_dpaProp; // DPA of (CProperty *) CProperty *_FindProperty(LPCWSTR pszName) const; HRESULT _InsertProperty(CProperty *pProp); // // Prevent copy. // CPropertyBucket(const CPropertyBucket& rhs); CPropertyBucket& operator = (const CPropertyBucket& rhs); }; // // Create a bucket. // HRESULT CPropertyBucket::CreateInstance( // [static] CPropertyBucket **ppBucketOut ) { HRESULT hr = E_OUTOFMEMORY; CPropertyBucket *pBucket = new CPropertyBucket(); if (NULL != pBucket) { if (pBucket->m_dpaProp.IsValid()) { ASSERT(IsValidWritePtr(ppBucketOut)); *ppBucketOut = pBucket; hr = S_OK; } else { delete pBucket; } } return hr; } CPropertyBucket::CPropertyBucket( void ) : m_dpaProp(4) { } CPropertyBucket::~CPropertyBucket( void ) { // // Note that the m_dpaProp member is self-destructive. // } // // Set a named property in the bucket to a new value. // If the property doesn't exist it is created. // // Returns: // S_OK - Property value set. // S_FALSE - Property value unchanged (dup value). // E_OUTOFMEMORY - Insuffient memory to add property. // HRESULT CPropertyBucket::SetProperty( LPCWSTR pszName, const VARIANT *pVar, CNotifier& notifier ) { ASSERT(NULL != pszName); ASSERT(NULL != pVar); HRESULT hr; CProperty *pProp = _FindProperty(pszName); if (NULL != pProp) { // // Found the property in the bucket. Set it's value. // hr = pProp->SetValue(pVar, notifier); } else { // // Must be a new property. Create a property object and // insert it into the bucket. // hr = CProperty::CreateInstance(pszName, pVar, false, &pProp); if (SUCCEEDED(hr)) { hr = _InsertProperty(pProp); if (FAILED(hr)) { delete pProp; } } } return hr; } // // Create a named constant property in the bucket and intialize // it with the specified value. Note that this property is now // a constant and cannot be modified. It will however be deleted // when it's parent property group is removed. If a property of the // specified name already exists, this method fails. // // Returns: // S_OK - Constant created. // TSPB_E_MODIFYCONST - Property of this name already exists. // E_OUTOFMEMORY - Insuffient memory to add property. // HRESULT CPropertyBucket::SetConstProperty( LPCWSTR pszName, const VARIANT *pVar ) { ASSERT(NULL != pszName); ASSERT(NULL != pVar); HRESULT hr = TSPB_E_MODIFYCONST; CProperty *pProp = _FindProperty(pszName); if (NULL == pProp) { hr = CProperty::CreateInstance(pszName, pVar, true, &pProp); if (SUCCEEDED(hr)) { hr = _InsertProperty(pProp); if (FAILED(hr)) { delete pProp; } } } return hr; } // // Retrieve the value of a named property in the bucket. // Returns: // S_OK - Property retrieved. // TSPB_E_PROPNOTFOUND - Property not found. // HRESULT CPropertyBucket::GetProperty( LPCWSTR pszName, VARIANT *pVarOut ) const { ASSERT(NULL != pszName); ASSERT(NULL != pVarOut); ASSERT(IsValidWritePtr(pVarOut)); HRESULT hr = TSPB_E_PROPNOTFOUND; CProperty *pProp = _FindProperty(pszName); if (NULL != pProp) { hr = pProp->GetValue(pVarOut); } return hr; } // // Register a client for a change notification on a property. // Returns: // S_OK - Change notify registered on the property. // TSPB_E_PROPNOTFOUND - Property not found. // HRESULT CPropertyBucket::Advise( LPCWSTR pszPropertyName, int iClient ) { ASSERT(NULL != pszPropertyName); HRESULT hr = TSPB_E_PROPNOTFOUND; CProperty *pProp = _FindProperty(pszPropertyName); if (NULL != pProp) { hr = pProp->Advise(iClient); } return hr; } // // Remove the change notification registration for a particular // client. This cancels the registration on all properties in the // bucket. // Returns: // Always returns S_OK. // HRESULT CPropertyBucket::Unadvise( int iClient ) { const int cProps = m_dpaProp.Count(); for (int i = 0; i < cProps; i++) { CProperty *pProp = m_dpaProp.Get(i); if (NULL != pProp) { pProp->Unadvise(iClient); } } return S_OK; } // // Find a property (by name) in a bucket. This is simply // a linear search. We don't do any sorting of items in the // bucket. // // REVIEW: Sorting items could yield a slight performance // improvement if collision chains become long. // // Returns NULL if the property is not found. // If found, the pointer returned is for reference only and // is not to be deleted by the caller. // CProperty * CPropertyBucket::_FindProperty( LPCWSTR pszName ) const { ASSERT(NULL != pszName); ASSERT(m_dpaProp.IsValid()); const int cProps = m_dpaProp.Count(); for (int iProp = 0; iProp < cProps; iProp++) { const CProperty *pProp = m_dpaProp.Get(iProp); ASSERT(NULL != pProp); if (pProp->CompareNamesEqual(pszName)) { // // This function is const because it doesn't modify // the DPA. However, it returns a non-const ptr // to a CProperty object because the caller may // need to change the object. // return const_cast(pProp); } } return NULL; } // // Insert a new property object into the bucket. // This simply appends the pointer onto the DPA. // It is assumed that the pProp argument is a heap address. // HRESULT CPropertyBucket::_InsertProperty( CProperty *pProp ) { ASSERT(NULL != pProp); HRESULT hr = E_OUTOFMEMORY; if (-1 != m_dpaProp.Append(pProp)) { hr = S_OK; } return hr; } //----------------------------------------------------------------------------- // CPropertyGroup // // This class represents a group of properties in the property bag. // The point is to simulate "scopes" of properties so that clients can create // separate namespaces within the property bag. This should help greatly with // reducing bugs related to 'unexpected' changes in property values resulting // in name collisions. Each group is identified in the application // namespace by a GUID. GUID_NULL always represents the 'global' namespace. // When a group is first created, we hand back a 'handle' that is used in // all other accesses. The handle is merely an index into our group table // providing direct access to a group. Helper functions are provided to // return a handle from a property group ID (guid) if necessary. // //----------------------------------------------------------------------------- class CPropertyGroup { public: explicit CPropertyGroup(REFGUID id); ~CPropertyGroup(void); REFGUID GetID(void) const { return m_id; } HRESULT SetProperty(LPCWSTR pszName, const VARIANT *pVar, CNotifier& notifier); HRESULT SetConstProperty(LPCWSTR pszName, const VARIANT *pVar); HRESULT GetProperty(LPCWSTR pszName, VARIANT *pVarOut) const; HRESULT Advise(LPCWSTR pszPropertyName, int iClient); HRESULT Unadvise(int iClient); private: const GUID m_id; // Property group ID. CNotifyMap m_NotifyMapGroup; CPropertyBucket *m_rgpBuckets[HASH_TABLE_SIZE]; // Hash table buckets. DWORD _HashName(LPCWSTR pszName) const; CPropertyBucket *_GetBucket(LPCWSTR pszName) const; // // Prevent copy. // CPropertyGroup(const CPropertyGroup& rhs); CPropertyGroup& operator = (const CPropertyGroup& rhs); }; CPropertyGroup::CPropertyGroup( REFGUID id ) : m_id(id) { ZeroMemory(m_rgpBuckets, sizeof(m_rgpBuckets)); } CPropertyGroup::~CPropertyGroup( void ) { for (int iBucket = 0; iBucket < ARRAYSIZE(m_rgpBuckets); iBucket++) { delete m_rgpBuckets[iBucket]; } } // // Set the value of a property in the group. // Will fail if the named property is a constant. // // Returns: // S_OK - Property value changed. // S_FALSE - Property value unchanged (dup value). // E_OUTOFMEMORY // TSPB_E_PROPNOTFOUND - Property name not found. // TSPB_E_MODIFYCONST - Attempt to modify a const property. // HRESULT CPropertyGroup::SetProperty( LPCWSTR pszName, const VARIANT *pVar, CNotifier& notifier ) { ASSERT(NULL != pszName); ASSERT(NULL != pVar); HRESULT hr = E_OUTOFMEMORY; CPropertyBucket *pBucket = _GetBucket(pszName); if (NULL != pBucket) { // // Merging the group's notify map with the 'active' // map adds those notifications registered at the // group level for this group. // notifier.MergeWithActiveNotifyMap(m_NotifyMapGroup); hr = pBucket->SetProperty(pszName, pVar, notifier); } return hr; } // // Creates a new constant property and sets its value. // Since constants are only initialized and cannot be changed, // this operation does not generate a notification event. // // Returns: // S_OK // E_OUTOFMEMORY // TSPB_E_PROPNOTFOUND - Property name not found. // TSPB_E_MODIFYCONST - Attempt to modify a const property. // HRESULT CPropertyGroup::SetConstProperty( LPCWSTR pszName, const VARIANT *pVar ) { ASSERT(NULL != pszName); ASSERT(NULL != pVar); HRESULT hr = E_OUTOFMEMORY; CPropertyBucket *pBucket = _GetBucket(pszName); if (NULL != pBucket) { hr = pBucket->SetConstProperty(pszName, pVar); } return hr; } // // Retrieve the value of a property in the property group. // // Returns: // S_OK // E_OUTOFMEMORY // TSPB_E_PROPNOTFOUND - Property name not found. // HRESULT CPropertyGroup::GetProperty( LPCWSTR pszName, VARIANT *pVarOut ) const { ASSERT(NULL != pszName); ASSERT(NULL != pVarOut); ASSERT(IsValidWritePtr(pVarOut)); HRESULT hr = E_OUTOFMEMORY; CPropertyBucket *pBucket = _GetBucket(pszName); if (NULL != pBucket) { hr = pBucket->GetProperty(pszName, pVarOut); } return hr; } // // Register a client for change notifications on a single property // or all properties in a group. // // Returns: // S_OK // E_OUTOFMEMORY // TSPB_E_PROPNOTFOUND - Property name not found. // TSPB_E_NOTIFYCOOKIE - iClient is invalid. // HRESULT CPropertyGroup::Advise( LPCWSTR pszPropertyName, // NULL == All properties in group. int iClient ) { HRESULT hr = E_OUTOFMEMORY; if (NULL == pszPropertyName || L'\0' == *pszPropertyName) { // // Register for a change on any property in this group. // hr = m_NotifyMapGroup.Set(iClient, true); } else { // // Register for a change on a specific property. // CPropertyBucket *pBucket = _GetBucket(pszPropertyName); if (NULL != pBucket) { hr = pBucket->Advise(pszPropertyName, iClient); } } return hr; } // // Cancel change notifications for a given client on all the property // group and all properties in the group. // Always returns S_OK. // HRESULT CPropertyGroup::Unadvise( int iClient ) { m_NotifyMapGroup.Set(iClient, false); for (int i = 0; i < ARRAYSIZE(m_rgpBuckets); i++) { CPropertyBucket *pBucket = m_rgpBuckets[i]; if (NULL != pBucket) { pBucket->Unadvise(iClient); } } return S_OK; } // // Retrieve the address of a bucket object containing a named property. // The bucket number is calculated by a hash on the property name. // If a bucket does not yet exist for the property, one is created. // Returns NULL if a bucket doesn't exist and can't be created. // CPropertyBucket * CPropertyGroup::_GetBucket( LPCWSTR pszName ) const { ASSERT(NULL != pszName); const DWORD iBucket = _HashName(pszName); if (NULL == m_rgpBuckets[iBucket]) { // // Create a new bucket. // CPropertyBucket *pBucket; HRESULT hr = CPropertyBucket::CreateInstance(&pBucket); if (SUCCEEDED(hr)) { CPropertyGroup *pNonConstThis = const_cast(this); pNonConstThis->m_rgpBuckets[iBucket] = pBucket; } } return m_rgpBuckets[iBucket]; } // // Generate a hash value from a property name. // The number generated will be between 0 and HASH_TABLE_SIZE. // DWORD CPropertyGroup::_HashName( LPCWSTR pszText ) const { LPCWSTR p = NULL; DWORD dwCode = 0; if (IS_INTRESOURCE(pszText)) { // // Property name is really an ID. In that case just // use the (ID % tablesize) as the hash code. // dwCode = (DWORD)((ULONG_PTR)(pszText)); } else { DWORD dwTemp = 0; for (p = pszText; TEXT('\0') != *p; p++) { dwCode = (dwCode << 4) + CharValue(*p); if (0 != (dwTemp = dwCode & 0xF0000000)) { dwCode = dwCode ^ (dwTemp >> 24); dwCode = dwCode ^ dwTemp; } } } dwCode %= HASH_TABLE_SIZE; ASSERT(dwCode < HASH_TABLE_SIZE); return dwCode; } //----------------------------------------------------------------------------- // CPropertyBag //----------------------------------------------------------------------------- class CPropertyBag : public IPropertyBag, public ITaskSheetPropertyBag { public: ~CPropertyBag(void); // // IUnknown // STDMETHOD(QueryInterface)(REFIID riid, void **ppv); STDMETHOD_(ULONG, AddRef)(); STDMETHOD_(ULONG, Release)(); // // IPropertyBag // STDMETHOD(Read)(LPCOLESTR pszPropName, VARIANT *pVar, IErrorLog *pErrorLog); STDMETHOD(Write)(LPCOLESTR pszPropName, VARIANT *pVar); // // ITaskSheetPropertyBag // STDMETHOD(CreatePropertyGroup)(REFGUID idGroup, HPROPGROUP *phGroupOut); STDMETHOD(RemovePropertyGroup)(HPROPGROUP hGroup); STDMETHOD(Set)(HPROPGROUP hGroup, LPCWSTR pszName, VARIANT *pVar); STDMETHOD(SetConst)(HPROPGROUP hGroup, LPCWSTR pszName, VARIANT *pVar); STDMETHOD(Get)(HPROPGROUP hGroup, LPCWSTR pszName, VARIANT *pVarOut); STDMETHOD(PropertyGroupIdToHandle)(REFGUID id, HPROPGROUP *phGroupOut); STDMETHOD(PropertyGroupHandleToId)(HPROPGROUP hGroup, GUID *pidCatOut); STDMETHOD(RegisterNotify)(ITaskSheetPropertyNotifySink *pSink, DWORD *pdwCookie); STDMETHOD(UnregisterNotify)(DWORD dwCookie); STDMETHOD(Advise)(DWORD dwCookie, HPROPGROUP hGroup, LPCWSTR pszPropName); private: CPropertyBag(void); LONG m_cRef; CNotifyMap m_NotifyMapGlobal; // Global notify map. CNotifier m_Notifier; // Handles notifying clients on chg. CDpa m_dpaGroups; // DPA of (CPropertyGroup *) // // Prevent copy. // CPropertyBag(const CPropertyBag& rhs); CPropertyBag& operator = (const CPropertyBag& rhs); HRESULT _FindPropertyGroupByID(REFGUID idGroup, HPROPGROUP *phGroup) const; HRESULT _GetPropertyGroup(HPROPGROUP hGroup, CPropertyGroup **ppGroup); bool _IsValidPropertyGroupHandle(HPROPGROUP hGroup) const; // // Friendship with instance generator is typical. // friend HRESULT TaskUiPropertyBag_CreateInstance(REFIID riid, void **ppv); }; // // Create a property bag. // HRESULT TaskUiPropertyBag_CreateInstance( REFIID riid, void **ppv ) { HRESULT hr = E_OUTOFMEMORY; CPropertyBag *pBag = new CPropertyBag(); if (NULL != pBag) { if (pBag->m_dpaGroups.IsValid()) { // // Create the 'global' property group. // HPROPGROUP hGroup; hr = pBag->CreatePropertyGroup(PGID_GLOBAL, &hGroup); if (SUCCEEDED(hr)) { ASSERT(PROPGROUP_GLOBAL == hGroup); hr = pBag->QueryInterface(riid, ppv); pBag->Release(); } } if (FAILED(hr)) { delete pBag; } } return hr; } CPropertyBag::CPropertyBag( void ) : m_cRef(1), m_dpaGroups(4), m_Notifier(this) { } CPropertyBag::~CPropertyBag( void ) { // // Note that the m_dpaGroups member is self-destructive. // } STDMETHODIMP CPropertyBag::QueryInterface( REFIID riid, void **ppv ) { static const QITAB qit[] = { QITABENT(CPropertyBag, IPropertyBag), QITABENT(CPropertyBag, ITaskSheetPropertyBag), { 0 }, }; return QISearch(this, qit, riid, ppv); } STDMETHODIMP_ (ULONG) CPropertyBag::AddRef( void ) { return InterlockedIncrement(&m_cRef); } STDMETHODIMP_ (ULONG) CPropertyBag::Release( void ) { if (InterlockedDecrement(&m_cRef)) return m_cRef; delete this; return 0; } // // Returns: // S_OK - Group created. // E_OUTOFMEMORY. // TSPB_E_GROUPEXISTS // STDMETHODIMP CPropertyBag::CreatePropertyGroup( REFGUID idGroup, HPROPGROUP *phGroupOut ) { ASSERT(NULL != phGroupOut); ASSERT(IsValidWritePtr(phGroupOut)); if (NULL == phGroupOut || !IsValidWritePtr(phGroupOut)) { // // Parameter validation for public API. // return E_INVALIDARG; } HRESULT hr = _FindPropertyGroupByID(idGroup, phGroupOut); if (S_OK == hr) { hr = TSPB_E_GROUPEXISTS; } else if (S_FALSE == hr) { hr = E_OUTOFMEMORY; CPropertyGroup *pGroup = new CPropertyGroup(idGroup); if (NULL != pGroup) { // // First try to find an empty slot in the DPA. // int cSlots = m_dpaGroups.Count(); for (int i = 0; i < cSlots; i++) { if (NULL == m_dpaGroups.Get(i)) { m_dpaGroups.Set(i, pGroup); *phGroupOut = i; pGroup = NULL; break; } } if (NULL != pGroup) { // // DPA is full, extend it. // const int iGroup = m_dpaGroups.Append(pGroup); if (-1 != iGroup) { *phGroupOut = iGroup; hr = S_OK; } else { delete pGroup; } } } } return hr; } // // Removes a property group from the bag. This includes removing // all of the properties associated with the group. // Note that the 'global' group cannot be // removed. It's scope is the lifetime of the property bag and // is destroyed when the bag is destroyed. // STDMETHODIMP CPropertyBag::RemovePropertyGroup( HPROPGROUP hGroup ) { CPropertyGroup *pGroup; HRESULT hr = _GetPropertyGroup(hGroup, &pGroup); if (SUCCEEDED(hr) && PROPGROUP_GLOBAL != hGroup) { delete pGroup; m_dpaGroups.Set(hGroup, NULL); } return hr; } // // Converts a property group ID (guid) to a group handle. // The handle returned is the same value that was returned // in CreatePropertyGroup. // STDMETHODIMP CPropertyBag::PropertyGroupIdToHandle( REFGUID idGroup, HPROPGROUP *phGroupOut ) { ASSERT(NULL != phGroupOut); ASSERT(IsValidWritePtr(phGroupOut)); if (NULL == phGroupOut || !IsValidWritePtr(phGroupOut)) { // // Parameter validation for public API. // return E_INVALIDARG; } HRESULT hr = TSPB_E_GROUPNOTFOUND; const int cGroups = m_dpaGroups.Count(); for (int i = 0; i < cGroups; i++) { CPropertyGroup *pGroup = m_dpaGroups.Get(i); if (NULL != pGroup && IsEqualGUID(pGroup->GetID(), idGroup)) { *phGroupOut = i; hr = S_OK; } } return hr; } // // Converts a property group handle to a group ID (guid). // Returns TSPB_E_GROUPNOTFOUND if the group handle is invalid. // Cannot retrieve a handle to the pseudo-group handles // PROPGROUP_ANY or PROPGROUP_INVALID. // STDMETHODIMP CPropertyBag::PropertyGroupHandleToId( HPROPGROUP hGroup, GUID *pidGroupOut ) { ASSERT(NULL != pidGroupOut); ASSERT(IsValidWritePtr(pidGroupOut)); if (NULL == pidGroupOut || !IsValidWritePtr(pidGroupOut)) { // // Parameter validation for public API. // return E_INVALIDARG; } CPropertyGroup *pGroup; HRESULT hr = _GetPropertyGroup(hGroup, &pGroup); if (SUCCEEDED(hr)) { ASSERT(NULL != pGroup); *pidGroupOut = pGroup->GetID(); } return hr; } // // Set a property value in the bag. // // Returns: // S_OK - Property value changed. // S_FALSE - Property value unchanged (dup value). // E_INVALIDARG // E_OUTOFMEMORY // TSPB_E_GROUPNOTFOUND - Invalid group handle. // TSPB_E_MODIFYCONST - Attempt to modify a const property. // STDMETHODIMP CPropertyBag::Set( HPROPGROUP hGroup, LPCWSTR pszName, VARIANT *pVar ) { ASSERT(NULL != pszName); ASSERT(NULL != pVar); if (NULL == pszName || NULL == pVar) { // // Parameter validation for public API. // return E_INVALIDARG; } CPropertyGroup *pGroup; HRESULT hr = _GetPropertyGroup(hGroup, &pGroup); if (SUCCEEDED(hr)) { ASSERT(NULL != pGroup); // // Initialize the active notify map to the content of the // global notify map. // m_Notifier.Reset(); m_Notifier.MergeWithActiveNotifyMap(m_NotifyMapGlobal); m_Notifier.SetPropertyGroup(hGroup); hr = pGroup->SetProperty(pszName, pVar, m_Notifier); } return hr; } // // Create a constant property value in the bag. // // // Returns: // S_OK // E_INVALIDARG // E_OUTOFMEMORY // TSPB_E_GROUPNOTFOUND - Invalid group handle. // TSPB_E_MODIFYCONST - Attempt to modify a const property. // STDMETHODIMP CPropertyBag::SetConst( HPROPGROUP hGroup, LPCWSTR pszName, VARIANT *pVar ) { ASSERT(NULL != pszName); ASSERT(NULL != pVar); if (NULL == pszName || NULL == pVar) { // // Parameter validation for public API. // return E_INVALIDARG; } CPropertyGroup *pGroup; HRESULT hr = _GetPropertyGroup(hGroup, &pGroup); if (SUCCEEDED(hr)) { ASSERT(NULL != pGroup); hr = pGroup->SetConstProperty(pszName, pVar); } return hr; } // // Retrieve property value from the bag. // // Returns: // S_OK // E_INVALIDARG // E_OUTOFMEMORY // TSPB_E_GROUPNOTFOUND - Invalid group handle. // TSPB_E_MODIFYCONST - Attempt to modify a const property. // STDMETHODIMP CPropertyBag::Get( HPROPGROUP hGroup, LPCWSTR pszName, VARIANT *pVarOut ) { ASSERT(NULL != pszName); ASSERT(NULL != pVarOut); ASSERT(IsValidWritePtr(pVarOut)); if (NULL == pszName || NULL == pVarOut || !IsValidWritePtr(pVarOut)) { // // Parameter validation for public API. // return E_INVALIDARG; } CPropertyGroup *pGroup; HRESULT hr = _GetPropertyGroup(hGroup, &pGroup); if (SUCCEEDED(hr)) { ASSERT(NULL != pGroup); hr = pGroup->GetProperty(pszName, pVarOut); } return hr; } // // Register an interface pointer for property change notifications. // STDMETHODIMP CPropertyBag::RegisterNotify( ITaskSheetPropertyNotifySink *pSink, DWORD *pdwCookie ) { ASSERT(NULL != pSink); ASSERT(NULL != pdwCookie); if (NULL == pSink || NULL == pdwCookie || !IsValidWritePtr(pdwCookie)) { // // Parameter validation for public API. // return E_INVALIDARG; } return m_Notifier.Register(pSink, pdwCookie); } // // Cancel all notifications associated with a given connection cookie. // // Returns: // S_OK // TSPB_E_GROUPNOTFOUND - Invalid group handle. // STDMETHODIMP CPropertyBag::UnregisterNotify( DWORD dwCookie ) { // // Unegister the sink with the notifier. // HRESULT hr = m_Notifier.Unregister(dwCookie); if (SUCCEEDED(hr)) { // // Unregister from the global notify map. // m_NotifyMapGlobal.Set(dwCookie, false); // // Unregister each property group. // const cGroups = m_dpaGroups.Count(); for (int i = 0; i < cGroups; i++) { CPropertyGroup *pGroup = m_dpaGroups.Get(i); if (NULL != pGroup) { pGroup->Unadvise(dwCookie); } } } return hr; } // // Request that a notification interface be notified when certain // properties change. The interface is identified by the 'cookie' that // was returned in RegisterNotify. // The property(s) of interested are identified through the hGroup // and pszPropertyName arguments as follows: // // hGroup pszPropName(*) Notify triggered on change // ------------ ---------------- ------------------------------------ // ANYPROPGROUP Any property in the bag. // PROPGROUP_GLOBAL NULL Any property in the global group // PROPGROUP_GLOBAL Non-NULL Named property in the global group // Other NULL Any property in the group // Other Non-NULL Named property in the group // // (*) For the prop name, NULL and L"" are equivalent. // // Returns: // S_OK // E_INVALIDARG // E_OUTOFMEMORY // TSPB_E_GROUPNOTFOUND - Invalid group handle. // STDMETHODIMP CPropertyBag::Advise( DWORD dwCookie, HPROPGROUP hGroup, LPCWSTR pszPropertyName ) { HRESULT hr; if (PROPGROUP_ANY == hGroup) { // // Register for changes in any property. // hr = m_NotifyMapGlobal.Set(dwCookie, true); } else { CPropertyGroup *pGroup; hr = _GetPropertyGroup(hGroup, &pGroup); if (SUCCEEDED(hr)) { ASSERT(NULL != pGroup); // // Register for changes in either a group or a specific // property. // hr = pGroup->Advise(pszPropertyName, dwCookie); } } return hr; } // // IPropertyBag implementation. // // Read a property from the 'global' namespace. // // BUGBUG: Do we need to do anything with pErrorLog? // STDMETHODIMP CPropertyBag::Read( LPCOLESTR pszPropName, VARIANT *pVar, IErrorLog * /* unused */ ) { ASSERT(NULL != pszPropName); ASSERT(NULL != pVar); ASSERT(IsValidWritePtr(pVar)); if (NULL == pszPropName || NULL == pVar || !IsValidWritePtr(pVar)) { // // Parameter validation for public API. // return E_INVALIDARG; } return Get(PROPGROUP_GLOBAL, pszPropName, pVar); } // // Write a property to the 'global' namespace. // STDMETHODIMP CPropertyBag::Write( LPCOLESTR pszPropName, VARIANT *pVar ) { ASSERT(NULL != pszPropName); ASSERT(NULL != pVar); if (NULL == pszPropName || NULL == pVar) { // // Parameter validation for public API. // return E_INVALIDARG; } return Set(PROPGROUP_GLOBAL, pszPropName, pVar); } // // Locate a property group by it's ID (guid), returning it's handle. // Returns: // S_OK - Found group. // S_FALSE - Didn't find group. // HRESULT CPropertyBag::_FindPropertyGroupByID( REFGUID idGroup, HPROPGROUP *phGroup ) const { ASSERT(NULL != phGroup); ASSERT(IsValidWritePtr(phGroup)); const int cCategories = m_dpaGroups.Count(); for (int i = 0; i < cCategories; i++) { const CPropertyGroup *pGroup = m_dpaGroups.Get(i); if (NULL != pGroup && IsEqualGUID(pGroup->GetID(), idGroup)) { *phGroup = i; return S_OK; } } return S_FALSE; } // // Determine if a group handle is valid. // Note that the pseudo handle PROPGROUP_GLOBAL is valid. // bool CPropertyBag::_IsValidPropertyGroupHandle( HPROPGROUP hGroup ) const { return (PROPGROUP_ANY != hGroup && PROPGROUP_INVALID != hGroup && hGroup < m_dpaGroups.Count()); } // // Retrieve the address of the CPropertyGroup object associated // with a particular group handle. The function performs handle // validation. // HRESULT CPropertyBag::_GetPropertyGroup( HPROPGROUP hGroup, CPropertyGroup **ppGroup ) { ASSERT(NULL != ppGroup); ASSERT(IsValidWritePtr(ppGroup)); HRESULT hr = TSPB_E_GROUPNOTFOUND; if (_IsValidPropertyGroupHandle(hGroup)) { *ppGroup = m_dpaGroups.Get(hGroup); hr = S_OK; } return hr; }