Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2232 lines
56 KiB

  1. //+-------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. //
  5. // Copyright (C) Microsoft Corporation, 2000
  6. //
  7. // File: propbag.cpp
  8. //
  9. // This property bag implementation supports the standard COM interface
  10. // IPropertyBag as well as the specialized interface ITaskSheetPropertyBag.
  11. //
  12. // The following facilities are available through ITaskSheetPropertyBag.
  13. //
  14. // 1. Property change notifications.
  15. // 2. Constants
  16. // 3. Property groups (namespace scoping)
  17. // 4. Use IDs or names for property identification
  18. //
  19. //
  20. // Here's the general structure of the implementation.
  21. //
  22. // The implementation uses 8 C++ classes.
  23. //
  24. // CPropertyBag - The property bag.
  25. //
  26. // CPropertyGroup - A group of properties. All properties in a group
  27. // exist in their own namespace. A default 'global' group is always
  28. // present. A property bag can have an unlimited number of groups.
  29. // Groups can be added and removed dynamically by the application.
  30. //
  31. // CPropertyBucket - A set of properties who's identifiers (ID or name)
  32. // hash to the same value. It's just a simple container to manage
  33. // hash table collisions. Each property group has HASH_TABLE_SIZE - 1
  34. // buckets. Buckets are created on-demand so an unused hash value
  35. // has a minimal cost.
  36. //
  37. // CProperty - A property in the property bag. A property has an
  38. // identifier (ID or name), a value and a set of flags. Currently,
  39. // only the "CONSTANT" flag is used. A property may be a read-write
  40. // or a constant value. Each property bucket contains one or more
  41. // CProperty objects. CProperty objects may not be removed individually.
  42. // They are removed only through destruction of the property bag or
  43. // removal of the parent property group.
  44. //
  45. // CPropertyName - The identifier of a property. Abstracts the use
  46. // of both a property ID and property name from the rest of the
  47. // implementation. Each CProperty object has one CPropertyName
  48. // member. Handles identity comparison of two properties.
  49. //
  50. // CNotifyMap - A simple bit map used to represent notification clients
  51. // interested in changes to one or more properties in the property
  52. // bag. Each bit corresponds directly to an entry in the "notify
  53. // client table" in the CNotifier object. The table is merely an
  54. // array of pointers to ITaskSheetPropertyNotifySink. Therefore, if
  55. // bit 5 is set, that means the the client who's notify sink pointer
  56. // is stored in element 5 of the notify table wants to be notified.
  57. //
  58. // Each of the following objects has a notify map:
  59. //
  60. // 1. CPropertyBag - This is the 'global' notify map. A bit set
  61. // in this map indicates the corresponding client is
  62. // interested in changes to ANY property in the bag.
  63. //
  64. // 2. CPropertyGroup - A bit set in a group's notify map indicates
  65. // the corresponding client is interested in changes to
  66. // any property in the group.
  67. //
  68. // 3. CProperty - A bit set in a property's notify map indicates
  69. // the corresponding client is interested in changes
  70. // to this property.
  71. //
  72. // 4. CNotifier - This is the 'active' notify map and is used
  73. // to indicate which clients are to receive notification
  74. // of the change to the property currently being changed
  75. // by a 'set' operation. See below for a description of
  76. // the CNotifier object.
  77. //
  78. // CNotifier - Handles registration of notification clients and performs
  79. // client notifications when properties change. The property bag
  80. // has a single CNotifier member. When a client registers for change
  81. // notifications, the client's ITaskSheetPropertyNotifySink pointer
  82. // is stored in the notifier's client table (array of ptrs). The
  83. // index of the pointer is the "cookie" returned to the client.
  84. // When a "set" operation begins, the notifier is initialized and
  85. // a reference to it is passed down the function call chain
  86. // (bag->group->bucket->property). At the 'bag', 'group' and 'property'
  87. // points in the call stack each respective object's "notify map" is
  88. // merged into the notifier's 'active' notify map. Once the call reaches
  89. // the point of setting the property value, the map contains bits set to
  90. // represent each of the notification clients interested in changes to
  91. // this property. The notifier is then called to notify all interested
  92. // clients. Clients may be dynamically added or removed at any time.
  93. //
  94. //
  95. //
  96. // The implementation of IPropertyBag automatically uses the 'global'
  97. // property group. Therefore, if you were to write some properties
  98. // through IPropertyBag::Write, those properties would be avaialble through
  99. // the 'global' property group (PROPGROUP_GLOBAL) when using
  100. // ITaskSheetPropertyBag.
  101. //
  102. //
  103. // [brianau - 06/20/00]
  104. //
  105. //--------------------------------------------------------------------------
  106. #include "stdafx.h"
  107. #include "taskui.h"
  108. #include "cdpa.h"
  109. //
  110. // Size of each property group hash table. This should be a prime
  111. // number for best hashing distribution.
  112. //
  113. const int HASH_TABLE_SIZE = 13;
  114. //
  115. // The type of data used by the change notify map.
  116. // A ULONGLONG provides 64 bits which means we can have 64 registered
  117. // notification clients. Use a DWORD and this drops to 32.
  118. //
  119. typedef ULONGLONG NOTIFYMAPTYPE;
  120. //
  121. // The maximum number of unique change notify connections. It's just the
  122. // number of bits available in a notify map.
  123. //
  124. const int MAX_NOTIFY_CNX = sizeof(NOTIFYMAPTYPE) * 8;
  125. //
  126. // ----------------------------------------------------------------------------
  127. // CASE SENSITIVITY
  128. // ----------------------------------------------------------------------------
  129. // The following macro controls the case sensitivity of the property bag.
  130. // By default the macro is undefined and the property bag is case-insensitive
  131. // with respect to property names. To make the bag case sensitive, uncomment
  132. // this macro and recompile.
  133. //
  134. // #define TASKUI_PROPBAG_CASE_SENSITIVE
  135. //
  136. inline int ComparePropertyNames(LPCWSTR s1, LPCWSTR s2)
  137. {
  138. #ifdef TASKUI_PROPBAG_CASE_SENSITIVE
  139. return lstrcmpW(s1, s2);
  140. #else
  141. return lstrcmpiW(s1, s2);
  142. #endif
  143. }
  144. inline int CharValue(WCHAR c)
  145. {
  146. #ifdef TASKUI_PROPBAG_CASE_SENSITIVE
  147. return c;
  148. #else
  149. return towlower(c);
  150. #endif
  151. }
  152. //
  153. // Validate the writeability of an "out" pointer. This just saves
  154. // us from entering the sizeof() part, potentially reducing bugs.
  155. //
  156. template <typename T>
  157. inline bool IsValidWritePtr(T *p)
  158. {
  159. return (!IsBadWritePtr(p, sizeof(*p)));
  160. }
  161. //-----------------------------------------------------------------------------
  162. // CNotifyMap
  163. //
  164. // Represents a 64-bit bitmap for the purpose of representing notification
  165. // registrations in the property bag. There are 3 levels of notify maps
  166. // in the system.
  167. //
  168. // 1. Global - owned by the CPropertyBag object.
  169. // 2. Group - one owned by each CPropertyGroup object.
  170. // 3. Property - one owned by each CProperty object.
  171. //
  172. // Whenever a client registers for notification at one of these levels,
  173. // the appropriate map is located and the bit corresponding to that client's
  174. // connection cookie is set. When a property is set, the union of all three
  175. // maps represents the clients requesting notification.
  176. //
  177. //-----------------------------------------------------------------------------
  178. class CNotifyMap
  179. {
  180. public:
  181. CNotifyMap(void)
  182. : m_bits(0) { }
  183. bool IsSet(int iBit) const;
  184. HRESULT Set(int iBit, bool bSet);
  185. bool AnySet(void) const
  186. { return NOTIFYMAPTYPE(0) != m_bits; }
  187. void Clear(void)
  188. { m_bits = NOTIFYMAPTYPE(0); }
  189. void Union(const CNotifyMap& rhs)
  190. { m_bits |= rhs.m_bits; }
  191. private:
  192. NOTIFYMAPTYPE m_bits;
  193. bool _IsValidBitIndex(int iBit) const
  194. { return (iBit < (sizeof(m_bits) * 8)); }
  195. NOTIFYMAPTYPE _MaskFromBit(int iBit) const
  196. { return NOTIFYMAPTYPE(1) << iBit; }
  197. };
  198. //
  199. // Set or clear a bit corresponding to a client
  200. //
  201. // Returns:
  202. // S_OK - Bit was set or cleared.
  203. // TSPB_E_NOTIFYCOOKIE - Passed an invalid bit index.
  204. //
  205. HRESULT
  206. CNotifyMap::Set(
  207. int iBit,
  208. bool bSet // true == set bit, false == clear
  209. )
  210. {
  211. HRESULT hr = TSPB_E_NOTIFYCOOKIE;
  212. ASSERT(_IsValidBitIndex(iBit));
  213. if (_IsValidBitIndex(iBit))
  214. {
  215. if (bSet)
  216. {
  217. m_bits |= _MaskFromBit(iBit);
  218. }
  219. else
  220. {
  221. m_bits &= ~(_MaskFromBit(iBit));
  222. }
  223. hr = S_OK;
  224. }
  225. return hr;
  226. }
  227. //
  228. // Determines if a given bit is set in the map.
  229. // On 'free' builds, returns false if an invalid bit number
  230. // is specified. Asserts on 'check' builds.
  231. //
  232. bool
  233. CNotifyMap::IsSet(
  234. int iBit
  235. ) const
  236. {
  237. ASSERT(_IsValidBitIndex(iBit));
  238. return (_IsValidBitIndex(iBit) && (0 != (_MaskFromBit(iBit) & m_bits)));
  239. }
  240. //-----------------------------------------------------------------------------
  241. // CNotifier
  242. //
  243. // This class handles all of the duties of client notification including
  244. // client registration, client unregistration and client notification.
  245. //
  246. // A property bag has a notifier as a member. When a client wishes to
  247. // register for a change notification, the property bag calls CNotifier::Register
  248. // to create an entry in the client table. The cookie returned is merely the
  249. // index into the client table.
  250. //
  251. // When a "set prop" operation begins, the notifier's notification map is set
  252. // equal to the bag's global notify map and the prop group handle is set to that
  253. // of the group specified in the "set" operation. A reference to the
  254. // notifier is then passed down the chain of "set" calls
  255. // (bag -> group -> property). At each point the notify maps are merged
  256. // (union) so that the map in the notifier represents all requested
  257. // notifications. Finally, the CProperty::SetValue method sets the property
  258. // then calls CNotifier::Notify. The notifier then scans it's current notify
  259. // map and notifies each client corresponding to bits set.
  260. //
  261. // That sounds like a lot but it really is quite simple and happens very fast.
  262. // The best thing is that is cleanly handles dynamic registration and
  263. // unregstration of clients as well as addition and removal of properties
  264. // and property categories.
  265. //
  266. //-----------------------------------------------------------------------------
  267. class CNotifier
  268. {
  269. public:
  270. explicit CNotifier(ITaskSheetPropertyBag *pBag);
  271. ~CNotifier(void);
  272. HRESULT Register(ITaskSheetPropertyNotifySink *pClient, DWORD *pdwCookie);
  273. HRESULT Unregister(DWORD dwCookie);
  274. void Notify(LPCWSTR pszPropertyName);
  275. void MergeWithActiveNotifyMap(const CNotifyMap& map)
  276. { m_NotifyMapActive.Union(map); }
  277. void Reset(void)
  278. { m_NotifyMapActive.Clear(); m_hGroup = PROPGROUP_INVALID; }
  279. void SetPropertyGroup(HPROPGROUP hGroup)
  280. { ASSERT(PROPGROUP_INVALID != hGroup); m_hGroup = hGroup; }
  281. private:
  282. //
  283. // A single entry in the connection table.
  284. // Why use an array of structures rather than simply an array
  285. // of client pointers? Good question! While developing this property
  286. // bag I've twice stored something along with the client pointer in each
  287. // client table slot. Once a ref count, the other time a private data
  288. // ptr. As the final design solidified, neither were required. However,
  289. // I've left the structure just in case we need to again store something
  290. // with the client notification pointer. [brianau - 6/20/00].
  291. //
  292. struct Entry
  293. {
  294. ITaskSheetPropertyNotifySink *pClient; // Client's notification interface ptr.
  295. };
  296. ITaskSheetPropertyBag *m_pBag; // Don't addref. Will create circular reference
  297. Entry m_rgCnx[MAX_NOTIFY_CNX]; // The connection table.
  298. CNotifyMap m_NotifyMapActive; // The "active" notify map.
  299. HPROPGROUP m_hGroup; // Handle of group associated with prop.
  300. // with the property bag.
  301. HRESULT _FindEntry(ITaskSheetPropertyNotifySink *pClient, Entry **ppEntry, DWORD *pdwIndex);
  302. HRESULT _FindFirstUnusedEntry(Entry **ppEntry, DWORD *pdwIndex);
  303. //
  304. // Prevent copy.
  305. //
  306. CNotifier(const CNotifier& rhs);
  307. CNotifier& operator = (const CNotifier& rhs);
  308. };
  309. //-----------------------------------------------------------------------------
  310. // CNotifier
  311. //-----------------------------------------------------------------------------
  312. CNotifier::CNotifier(
  313. ITaskSheetPropertyBag *pBag
  314. ) : m_hGroup(PROPGROUP_INVALID),
  315. m_pBag(pBag)
  316. {
  317. ASSERT(NULL != m_pBag);
  318. ZeroMemory(m_rgCnx, sizeof(m_rgCnx));
  319. //
  320. // Note that we DON'T addref the property bag interface.
  321. // Since the CNotifier object is a member of CPropertyBag,
  322. // that would create a circular reference with the property bag.
  323. // We can be assured that the notifier's lifetime is contained
  324. // within the property bag's lifetime.
  325. //
  326. }
  327. CNotifier::~CNotifier(
  328. void
  329. )
  330. {
  331. for (int i = 0; i < ARRAYSIZE(m_rgCnx); i++)
  332. {
  333. if (NULL != m_rgCnx[i].pClient)
  334. {
  335. m_rgCnx[i].pClient->Release();
  336. }
  337. }
  338. //
  339. // DON'T Release m_pBag. See ctor above for details.
  340. //
  341. }
  342. //
  343. // Register a notifiation client. Returns Cookie in pdwCookie to be
  344. // used in call to Unregister if unregistration is desired.
  345. //
  346. // Returns:
  347. // S_OK - Registration successful.
  348. // S_FALSE - Already registered.
  349. //
  350. HRESULT
  351. CNotifier::Register(
  352. ITaskSheetPropertyNotifySink *pClient,
  353. DWORD *pdwCookie // Optional. May be NULL.
  354. )
  355. {
  356. ASSERT(NULL != pClient);
  357. Entry *pEntry = NULL;
  358. DWORD dwIndex = DWORD(-1);
  359. HRESULT hr = _FindEntry(pClient, &pEntry, &dwIndex);
  360. if (S_OK == hr)
  361. {
  362. //
  363. // Found existing entry for this client.
  364. //
  365. hr = S_FALSE;
  366. }
  367. else if (S_FALSE == hr)
  368. {
  369. //
  370. // Need to create a new entry for this client.
  371. // Find a slot for it.
  372. //
  373. hr = _FindFirstUnusedEntry(&pEntry, &dwIndex);
  374. if (SUCCEEDED(hr))
  375. {
  376. ASSERT(NULL == pEntry->pClient);
  377. pClient->AddRef();
  378. pEntry->pClient = pClient;
  379. hr = S_OK;
  380. }
  381. }
  382. if (SUCCEEDED(hr) && NULL != pdwCookie)
  383. {
  384. ASSERT(IsValidWritePtr(pdwCookie));
  385. ASSERT(DWORD(-1) != dwIndex);
  386. *pdwCookie = dwIndex;
  387. }
  388. return hr;
  389. }
  390. //
  391. // Unregister a client to cancel it's reception of all notifications.
  392. // The dwCookie parameter is the one received via the Register method.
  393. //
  394. // Returns:
  395. // S_OK - Connection unregistered.
  396. // TSPB_E_NOTIFYCOOKIE - Cookie references an invalid connection.
  397. //
  398. HRESULT
  399. CNotifier::Unregister(
  400. DWORD dwCookie
  401. )
  402. {
  403. HRESULT hr = TSPB_E_NOTIFYCOOKIE;
  404. if (int(dwCookie) >= 0 && int(dwCookie) < ARRAYSIZE(m_rgCnx))
  405. {
  406. Entry& entry = m_rgCnx[dwCookie];
  407. if (NULL != entry.pClient)
  408. {
  409. entry.pClient->Release();
  410. entry.pClient = NULL;
  411. hr = S_OK;
  412. }
  413. }
  414. return hr;
  415. }
  416. //
  417. // Called by CProperty::SetValue when a value has been altered.
  418. // This function scans the active notify map and notifies each
  419. // registered client specified by a bit set in the map.
  420. //
  421. void
  422. CNotifier::Notify(
  423. LPCWSTR pszPropertyName
  424. )
  425. {
  426. ASSERT(NULL != m_pBag);
  427. ASSERT(NULL != pszPropertyName);
  428. if (m_NotifyMapActive.AnySet())
  429. {
  430. //
  431. // We pass a pointer to the property bag in the notification
  432. // so we AddRef it to ensure it remains alive across the
  433. // notification.
  434. //
  435. m_pBag->AddRef();
  436. //
  437. // As we notify clients we'll clear the corresponding bit in
  438. // notify map. This way we examine the map only as long as
  439. // necessary.
  440. //
  441. for (int i = 0; i < ARRAYSIZE(m_rgCnx) && m_NotifyMapActive.AnySet(); i++)
  442. {
  443. if (m_NotifyMapActive.IsSet(i))
  444. {
  445. ITaskSheetPropertyNotifySink *pClient = m_rgCnx[i].pClient;
  446. ASSERT(NULL != pClient);
  447. if (NULL != pClient)
  448. {
  449. pClient->OnPropChanged(m_pBag, m_hGroup, pszPropertyName);
  450. m_NotifyMapActive.Set(i, false);
  451. }
  452. }
  453. }
  454. m_pBag->Release();
  455. }
  456. //
  457. // Reset our internal state in preparation for the next
  458. // property notify. The map should be clear at this point.
  459. //
  460. ASSERT(!m_NotifyMapActive.AnySet());
  461. Reset();
  462. }
  463. //
  464. // Locates an entry in the client table.
  465. // The pClient argument is the key.
  466. // Using COM identity rules, we QI for IUnknown on both the key
  467. // interface and each of the table entries. If IUnknown ptrs
  468. // match then we found the entry.
  469. //
  470. // Returns:
  471. // S_OK - Entry found.
  472. // S_FALSE - Entry not found.
  473. //
  474. //
  475. HRESULT
  476. CNotifier::_FindEntry(
  477. ITaskSheetPropertyNotifySink *pClient, // Looking for this client.
  478. Entry **ppEntry,
  479. DWORD *pdwIndexOut // Optional. Can be NULL.
  480. )
  481. {
  482. ASSERT(NULL != pClient);
  483. ASSERT(NULL != ppEntry);
  484. ASSERT(IsValidWritePtr(ppEntry));
  485. Entry *pEntry = NULL;
  486. DWORD dwIndex = DWORD(-1);
  487. IUnknown *pUnkClient;
  488. //
  489. // Get the IUnknown interface pointer from both the key and
  490. // each entry. When we find a match we've found the entry.
  491. //
  492. HRESULT hr = pClient->QueryInterface(IID_IUnknown, (void **)&pUnkClient);
  493. if (SUCCEEDED(hr))
  494. {
  495. for (int i = 0; i < ARRAYSIZE(m_rgCnx) && NULL == pEntry; i++)
  496. {
  497. if (NULL != m_rgCnx[i].pClient)
  498. {
  499. IUnknown *pUnkEntry;
  500. hr = m_rgCnx[i].pClient->QueryInterface(IID_IUnknown, (void **)&pUnkEntry);
  501. if (SUCCEEDED(hr))
  502. {
  503. if (pUnkEntry == pUnkClient)
  504. {
  505. pEntry = &m_rgCnx[i];
  506. dwIndex = i;
  507. }
  508. pUnkEntry->Release();
  509. }
  510. }
  511. }
  512. pUnkClient->Release();
  513. }
  514. *ppEntry = pEntry;
  515. if (NULL != pEntry)
  516. {
  517. //
  518. // Entry found.
  519. //
  520. ASSERT(DWORD(-1) != dwIndex);
  521. if (NULL != pdwIndexOut)
  522. {
  523. ASSERT(IsValidWritePtr(pdwIndexOut));
  524. *pdwIndexOut = dwIndex;
  525. }
  526. hr = S_OK;
  527. }
  528. else if (hr == S_OK)
  529. {
  530. //
  531. // Entry not found.
  532. //
  533. ASSERT(NULL == pEntry);
  534. ASSERT(DWORD(-1) == dwIndex);
  535. hr = S_FALSE;
  536. }
  537. return hr;
  538. }
  539. //
  540. // Search the table for the first unused entry.
  541. // Returns:
  542. // S_OK - Unused entry found.
  543. // TSPB_E_MAXNOTIFYCNX - Notify client table is full.
  544. //
  545. HRESULT
  546. CNotifier::_FindFirstUnusedEntry(
  547. Entry **ppEntry,
  548. DWORD *pdwIndexOut // Optional. May be NULL.
  549. )
  550. {
  551. ASSERT(NULL != ppEntry);
  552. ASSERT(IsValidWritePtr(ppEntry));
  553. HRESULT hr = TSPB_E_MAXNOTIFYCNX;
  554. Entry *pEntry = NULL;
  555. DWORD dwIndex = DWORD(-1);
  556. for (int i = 0; i < ARRAYSIZE(m_rgCnx); i++)
  557. {
  558. if (NULL == m_rgCnx[i].pClient)
  559. {
  560. pEntry = &m_rgCnx[i];
  561. dwIndex = i;
  562. hr = S_OK;
  563. break;
  564. }
  565. }
  566. *ppEntry = pEntry;
  567. if (SUCCEEDED(hr))
  568. {
  569. if (NULL != pdwIndexOut)
  570. {
  571. ASSERT(DWORD(-1) != dwIndex);
  572. ASSERT(IsValidWritePtr(pdwIndexOut));
  573. *pdwIndexOut = dwIndex;
  574. }
  575. }
  576. ASSERT(FAILED(hr) || DWORD(-1) != dwIndex);
  577. ASSERT(FAILED(hr) || NULL != pEntry);
  578. return hr;
  579. }
  580. //-----------------------------------------------------------------------------
  581. // CPropertyName
  582. //
  583. // This class handles the dual-nature of property names. A name can either be
  584. // a text string or a property ID created wtih the MAKEPROPID macro. Prop
  585. // IDs are used the same was as Windows Resource IDs thorugh the
  586. // MAKEINTRESOURCE macro.
  587. //-----------------------------------------------------------------------------
  588. class CPropertyName
  589. {
  590. public:
  591. CPropertyName(LPCWSTR pszName);
  592. ~CPropertyName(void);
  593. bool IsValid(void) const
  594. { return NULL != m_pszName; }
  595. bool CompareEqual(LPCWSTR pszName) const;
  596. operator LPCWSTR() const
  597. { return m_pszName; }
  598. private:
  599. LPWSTR m_pszName;
  600. //
  601. // Prevent copy.
  602. //
  603. CPropertyName(const CPropertyName& rhs);
  604. CPropertyName& operator = (const CPropertyName& rhs);
  605. static bool IsPropID(LPCWSTR pszName)
  606. { return IS_PROPID(pszName); }
  607. };
  608. CPropertyName::CPropertyName(
  609. LPCWSTR pszName
  610. ) : m_pszName(NULL)
  611. {
  612. ASSERT(NULL != pszName);
  613. if (!IsPropID(pszName))
  614. {
  615. m_pszName = StrDupW(pszName);
  616. }
  617. else
  618. {
  619. m_pszName = (LPWSTR)pszName;
  620. }
  621. }
  622. CPropertyName::~CPropertyName(
  623. void
  624. )
  625. {
  626. if (!IsPropID(m_pszName))
  627. {
  628. if (NULL != m_pszName)
  629. {
  630. LocalFree(m_pszName);
  631. }
  632. }
  633. }
  634. bool
  635. CPropertyName::CompareEqual (
  636. LPCWSTR pszName
  637. ) const
  638. {
  639. bool bEqual = false;
  640. //
  641. // The pszName (and m_pszName) values can be either a pointer to
  642. // a name string or a property ID.
  643. //
  644. const bool bLhsIsID = IsPropID(m_pszName);
  645. const bool bRhsIsID = IsPropID(pszName);
  646. if (bLhsIsID == bRhsIsID)
  647. {
  648. if (bLhsIsID)
  649. {
  650. bEqual = (m_pszName == pszName);
  651. }
  652. else
  653. {
  654. bEqual = (0 == ComparePropertyNames(m_pszName, pszName));
  655. }
  656. }
  657. return bEqual;
  658. }
  659. //-----------------------------------------------------------------------------
  660. // CProperty
  661. //
  662. // Represents a single property in the property bag.
  663. //-----------------------------------------------------------------------------
  664. class CProperty
  665. {
  666. public:
  667. static HRESULT CreateInstance(LPCWSTR pszName,
  668. const VARIANT *pVar,
  669. bool bConstant,
  670. CProperty **ppPropOut);
  671. ~CProperty(void);
  672. HRESULT SetValue(const VARIANT *pVar, CNotifier& notifier);
  673. HRESULT GetValue(VARIANT *pVarOut) const;
  674. LPCWSTR GetName(void) const
  675. { return m_Name; }
  676. bool CompareNamesEqual(LPCWSTR pszName) const
  677. { return m_Name.CompareEqual(pszName); }
  678. HRESULT Advise(int iClient)
  679. { m_NotifyMapProperty.Set(iClient, true); return S_OK; }
  680. HRESULT Unadvise(int iClient)
  681. { m_NotifyMapProperty.Set(iClient, false); return S_OK; }
  682. private:
  683. CProperty(LPCWSTR pszName, const VARIANT *pVar, bool bConstant);
  684. enum { CONSTANT = 0x00000001 };
  685. CPropertyName m_Name; // The property name or prop ID.
  686. CComVariant m_Value; // The value.
  687. DWORD m_dwFlags; // Flags like 'CONSTANT'.
  688. CNotifyMap m_NotifyMapProperty; // Clients who want notification.
  689. //
  690. // Prevent copy.
  691. //
  692. CProperty(const CProperty& rhs);
  693. CProperty& operator = (const CProperty& rhs);
  694. };
  695. //
  696. // Create a property.
  697. //
  698. HRESULT
  699. CProperty::CreateInstance( // [static]
  700. LPCWSTR pszName,
  701. const VARIANT *pVar,
  702. bool bConstant,
  703. CProperty **ppPropOut
  704. )
  705. {
  706. ASSERT(NULL != pszName);
  707. ASSERT(NULL != ppPropOut);
  708. ASSERT(IsValidWritePtr(ppPropOut));
  709. HRESULT hr = E_OUTOFMEMORY;
  710. *ppPropOut = NULL;
  711. CProperty *pProp = new CProperty(pszName, pVar, bConstant);
  712. if (NULL != pProp)
  713. {
  714. if (pProp->m_Name.IsValid())
  715. {
  716. *ppPropOut = pProp;
  717. hr = S_OK;
  718. }
  719. else
  720. {
  721. delete pProp;
  722. }
  723. }
  724. return hr;
  725. }
  726. CProperty::CProperty(
  727. LPCWSTR pszName,
  728. const VARIANT *pVar,
  729. bool bConstant
  730. ) : m_dwFlags(0),
  731. m_Name(pszName)
  732. {
  733. ASSERT(NULL != pszName);
  734. ASSERT(NULL != pVar);
  735. if (bConstant)
  736. {
  737. m_dwFlags |= CONSTANT;
  738. }
  739. m_Value.Copy(pVar);
  740. }
  741. CProperty::~CProperty(
  742. void
  743. )
  744. {
  745. }
  746. //
  747. // Set the property's value.
  748. //
  749. // Returns:
  750. // S_OK - Value set.
  751. // S_FALSE - New value same as old. Not changed.
  752. // TSPB_E_MODIFYCONST - Attempt to modify a const property.
  753. //
  754. HRESULT
  755. CProperty::SetValue(
  756. const VARIANT *pVar,
  757. CNotifier& notifier
  758. )
  759. {
  760. ASSERT(NULL != pVar);
  761. HRESULT hr = TSPB_E_MODIFYCONST;
  762. if (0 == (CONSTANT & m_dwFlags))
  763. {
  764. hr = S_FALSE; // Assume no change.
  765. //
  766. // Only set the value and notify clients if the value is
  767. // going to change.
  768. //
  769. if (m_Value != *pVar)
  770. {
  771. //
  772. // Copy the value. Must first clear the value to release
  773. // any VARIANT memory we might be holding.
  774. //
  775. m_Value.Clear();
  776. hr = m_Value.Copy(pVar);
  777. if (SUCCEEDED(hr))
  778. {
  779. //
  780. // Merge the property's notify map with the 'active'
  781. // notify map. The notifier's 'active' map is now the
  782. // union of the 'global' map, the 'group' map and the
  783. // 'property' map. It contains bits representing
  784. // all of the clients interested in this property change.
  785. //
  786. notifier.MergeWithActiveNotifyMap(m_NotifyMapProperty);
  787. //
  788. // Notify the clients whos bits are set in the 'active' notify
  789. // map.
  790. //
  791. notifier.Notify(m_Name);
  792. }
  793. }
  794. }
  795. return hr;
  796. }
  797. //
  798. // Retrieve the property's value. The value is copied
  799. // the output variant.
  800. //
  801. HRESULT
  802. CProperty::GetValue(
  803. VARIANT *pVarOut
  804. ) const
  805. {
  806. ASSERT(NULL != pVarOut);
  807. ASSERT(IsValidWritePtr(pVarOut));
  808. //
  809. // Must first initialize the output variant before copying.
  810. //
  811. ::VariantInit(pVarOut);
  812. return ::VariantCopy(pVarOut, (VARIANT *)&m_Value);
  813. }
  814. //-----------------------------------------------------------------------------
  815. // CPropertyBucket
  816. //
  817. // This is a simple container representing the chain of collisions in
  818. // a conventional 'linear-chaining' hash table. The bucket is just a
  819. // DPA of CProperty object ptrs.
  820. //-----------------------------------------------------------------------------
  821. class CPropertyBucket
  822. {
  823. public:
  824. static HRESULT CreateInstance(CPropertyBucket **ppBucketOut);
  825. ~CPropertyBucket(void);
  826. HRESULT SetProperty(LPCWSTR pszName, const VARIANT *pVar, CNotifier& notifier);
  827. HRESULT SetConstProperty(LPCWSTR pszName, const VARIANT *pVAr);
  828. HRESULT GetProperty(LPCWSTR pszName, VARIANT *pVarOut) const;
  829. HRESULT Advise(LPCWSTR pszPropertyName, int iClient);
  830. HRESULT Unadvise(int iClient);
  831. private:
  832. CPropertyBucket(void);
  833. CDpa<CProperty> m_dpaProp; // DPA of (CProperty *)
  834. CProperty *_FindProperty(LPCWSTR pszName) const;
  835. HRESULT _InsertProperty(CProperty *pProp);
  836. //
  837. // Prevent copy.
  838. //
  839. CPropertyBucket(const CPropertyBucket& rhs);
  840. CPropertyBucket& operator = (const CPropertyBucket& rhs);
  841. };
  842. //
  843. // Create a bucket.
  844. //
  845. HRESULT
  846. CPropertyBucket::CreateInstance( // [static]
  847. CPropertyBucket **ppBucketOut
  848. )
  849. {
  850. HRESULT hr = E_OUTOFMEMORY;
  851. CPropertyBucket *pBucket = new CPropertyBucket();
  852. if (NULL != pBucket)
  853. {
  854. if (pBucket->m_dpaProp.IsValid())
  855. {
  856. ASSERT(IsValidWritePtr(ppBucketOut));
  857. *ppBucketOut = pBucket;
  858. hr = S_OK;
  859. }
  860. else
  861. {
  862. delete pBucket;
  863. }
  864. }
  865. return hr;
  866. }
  867. CPropertyBucket::CPropertyBucket(
  868. void
  869. ) : m_dpaProp(4)
  870. {
  871. }
  872. CPropertyBucket::~CPropertyBucket(
  873. void
  874. )
  875. {
  876. //
  877. // Note that the m_dpaProp member is self-destructive.
  878. //
  879. }
  880. //
  881. // Set a named property in the bucket to a new value.
  882. // If the property doesn't exist it is created.
  883. //
  884. // Returns:
  885. // S_OK - Property value set.
  886. // S_FALSE - Property value unchanged (dup value).
  887. // E_OUTOFMEMORY - Insuffient memory to add property.
  888. //
  889. HRESULT
  890. CPropertyBucket::SetProperty(
  891. LPCWSTR pszName,
  892. const VARIANT *pVar,
  893. CNotifier& notifier
  894. )
  895. {
  896. ASSERT(NULL != pszName);
  897. ASSERT(NULL != pVar);
  898. HRESULT hr;
  899. CProperty *pProp = _FindProperty(pszName);
  900. if (NULL != pProp)
  901. {
  902. //
  903. // Found the property in the bucket. Set it's value.
  904. //
  905. hr = pProp->SetValue(pVar, notifier);
  906. }
  907. else
  908. {
  909. //
  910. // Must be a new property. Create a property object and
  911. // insert it into the bucket.
  912. //
  913. hr = CProperty::CreateInstance(pszName, pVar, false, &pProp);
  914. if (SUCCEEDED(hr))
  915. {
  916. hr = _InsertProperty(pProp);
  917. if (FAILED(hr))
  918. {
  919. delete pProp;
  920. }
  921. }
  922. }
  923. return hr;
  924. }
  925. //
  926. // Create a named constant property in the bucket and intialize
  927. // it with the specified value. Note that this property is now
  928. // a constant and cannot be modified. It will however be deleted
  929. // when it's parent property group is removed. If a property of the
  930. // specified name already exists, this method fails.
  931. //
  932. // Returns:
  933. // S_OK - Constant created.
  934. // TSPB_E_MODIFYCONST - Property of this name already exists.
  935. // E_OUTOFMEMORY - Insuffient memory to add property.
  936. //
  937. HRESULT
  938. CPropertyBucket::SetConstProperty(
  939. LPCWSTR pszName,
  940. const VARIANT *pVar
  941. )
  942. {
  943. ASSERT(NULL != pszName);
  944. ASSERT(NULL != pVar);
  945. HRESULT hr = TSPB_E_MODIFYCONST;
  946. CProperty *pProp = _FindProperty(pszName);
  947. if (NULL == pProp)
  948. {
  949. hr = CProperty::CreateInstance(pszName, pVar, true, &pProp);
  950. if (SUCCEEDED(hr))
  951. {
  952. hr = _InsertProperty(pProp);
  953. if (FAILED(hr))
  954. {
  955. delete pProp;
  956. }
  957. }
  958. }
  959. return hr;
  960. }
  961. //
  962. // Retrieve the value of a named property in the bucket.
  963. // Returns:
  964. // S_OK - Property retrieved.
  965. // TSPB_E_PROPNOTFOUND - Property not found.
  966. //
  967. HRESULT
  968. CPropertyBucket::GetProperty(
  969. LPCWSTR pszName,
  970. VARIANT *pVarOut
  971. ) const
  972. {
  973. ASSERT(NULL != pszName);
  974. ASSERT(NULL != pVarOut);
  975. ASSERT(IsValidWritePtr(pVarOut));
  976. HRESULT hr = TSPB_E_PROPNOTFOUND;
  977. CProperty *pProp = _FindProperty(pszName);
  978. if (NULL != pProp)
  979. {
  980. hr = pProp->GetValue(pVarOut);
  981. }
  982. return hr;
  983. }
  984. //
  985. // Register a client for a change notification on a property.
  986. // Returns:
  987. // S_OK - Change notify registered on the property.
  988. // TSPB_E_PROPNOTFOUND - Property not found.
  989. //
  990. HRESULT
  991. CPropertyBucket::Advise(
  992. LPCWSTR pszPropertyName,
  993. int iClient
  994. )
  995. {
  996. ASSERT(NULL != pszPropertyName);
  997. HRESULT hr = TSPB_E_PROPNOTFOUND;
  998. CProperty *pProp = _FindProperty(pszPropertyName);
  999. if (NULL != pProp)
  1000. {
  1001. hr = pProp->Advise(iClient);
  1002. }
  1003. return hr;
  1004. }
  1005. //
  1006. // Remove the change notification registration for a particular
  1007. // client. This cancels the registration on all properties in the
  1008. // bucket.
  1009. // Returns:
  1010. // Always returns S_OK.
  1011. //
  1012. HRESULT
  1013. CPropertyBucket::Unadvise(
  1014. int iClient
  1015. )
  1016. {
  1017. const int cProps = m_dpaProp.Count();
  1018. for (int i = 0; i < cProps; i++)
  1019. {
  1020. CProperty *pProp = m_dpaProp.Get(i);
  1021. if (NULL != pProp)
  1022. {
  1023. pProp->Unadvise(iClient);
  1024. }
  1025. }
  1026. return S_OK;
  1027. }
  1028. //
  1029. // Find a property (by name) in a bucket. This is simply
  1030. // a linear search. We don't do any sorting of items in the
  1031. // bucket.
  1032. //
  1033. // REVIEW: Sorting items could yield a slight performance
  1034. // improvement if collision chains become long.
  1035. //
  1036. // Returns NULL if the property is not found.
  1037. // If found, the pointer returned is for reference only and
  1038. // is not to be deleted by the caller.
  1039. //
  1040. CProperty *
  1041. CPropertyBucket::_FindProperty(
  1042. LPCWSTR pszName
  1043. ) const
  1044. {
  1045. ASSERT(NULL != pszName);
  1046. ASSERT(m_dpaProp.IsValid());
  1047. const int cProps = m_dpaProp.Count();
  1048. for (int iProp = 0; iProp < cProps; iProp++)
  1049. {
  1050. const CProperty *pProp = m_dpaProp.Get(iProp);
  1051. ASSERT(NULL != pProp);
  1052. if (pProp->CompareNamesEqual(pszName))
  1053. {
  1054. //
  1055. // This function is const because it doesn't modify
  1056. // the DPA. However, it returns a non-const ptr
  1057. // to a CProperty object because the caller may
  1058. // need to change the object.
  1059. //
  1060. return const_cast<CProperty *>(pProp);
  1061. }
  1062. }
  1063. return NULL;
  1064. }
  1065. //
  1066. // Insert a new property object into the bucket.
  1067. // This simply appends the pointer onto the DPA.
  1068. // It is assumed that the pProp argument is a heap address.
  1069. //
  1070. HRESULT
  1071. CPropertyBucket::_InsertProperty(
  1072. CProperty *pProp
  1073. )
  1074. {
  1075. ASSERT(NULL != pProp);
  1076. HRESULT hr = E_OUTOFMEMORY;
  1077. if (-1 != m_dpaProp.Append(pProp))
  1078. {
  1079. hr = S_OK;
  1080. }
  1081. return hr;
  1082. }
  1083. //-----------------------------------------------------------------------------
  1084. // CPropertyGroup
  1085. //
  1086. // This class represents a group of properties in the property bag.
  1087. // The point is to simulate "scopes" of properties so that clients can create
  1088. // separate namespaces within the property bag. This should help greatly with
  1089. // reducing bugs related to 'unexpected' changes in property values resulting
  1090. // in name collisions. Each group is identified in the application
  1091. // namespace by a GUID. GUID_NULL always represents the 'global' namespace.
  1092. // When a group is first created, we hand back a 'handle' that is used in
  1093. // all other accesses. The handle is merely an index into our group table
  1094. // providing direct access to a group. Helper functions are provided to
  1095. // return a handle from a property group ID (guid) if necessary.
  1096. //
  1097. //-----------------------------------------------------------------------------
  1098. class CPropertyGroup
  1099. {
  1100. public:
  1101. explicit CPropertyGroup(REFGUID id);
  1102. ~CPropertyGroup(void);
  1103. REFGUID GetID(void) const
  1104. { return m_id; }
  1105. HRESULT SetProperty(LPCWSTR pszName, const VARIANT *pVar, CNotifier& notifier);
  1106. HRESULT SetConstProperty(LPCWSTR pszName, const VARIANT *pVar);
  1107. HRESULT GetProperty(LPCWSTR pszName, VARIANT *pVarOut) const;
  1108. HRESULT Advise(LPCWSTR pszPropertyName, int iClient);
  1109. HRESULT Unadvise(int iClient);
  1110. private:
  1111. const GUID m_id; // Property group ID.
  1112. CNotifyMap m_NotifyMapGroup;
  1113. CPropertyBucket *m_rgpBuckets[HASH_TABLE_SIZE]; // Hash table buckets.
  1114. DWORD _HashName(LPCWSTR pszName) const;
  1115. CPropertyBucket *_GetBucket(LPCWSTR pszName) const;
  1116. //
  1117. // Prevent copy.
  1118. //
  1119. CPropertyGroup(const CPropertyGroup& rhs);
  1120. CPropertyGroup& operator = (const CPropertyGroup& rhs);
  1121. };
  1122. CPropertyGroup::CPropertyGroup(
  1123. REFGUID id
  1124. ) : m_id(id)
  1125. {
  1126. ZeroMemory(m_rgpBuckets, sizeof(m_rgpBuckets));
  1127. }
  1128. CPropertyGroup::~CPropertyGroup(
  1129. void
  1130. )
  1131. {
  1132. for (int iBucket = 0; iBucket < ARRAYSIZE(m_rgpBuckets); iBucket++)
  1133. {
  1134. delete m_rgpBuckets[iBucket];
  1135. }
  1136. }
  1137. //
  1138. // Set the value of a property in the group.
  1139. // Will fail if the named property is a constant.
  1140. //
  1141. // Returns:
  1142. // S_OK - Property value changed.
  1143. // S_FALSE - Property value unchanged (dup value).
  1144. // E_OUTOFMEMORY
  1145. // TSPB_E_PROPNOTFOUND - Property name not found.
  1146. // TSPB_E_MODIFYCONST - Attempt to modify a const property.
  1147. //
  1148. HRESULT
  1149. CPropertyGroup::SetProperty(
  1150. LPCWSTR pszName,
  1151. const VARIANT *pVar,
  1152. CNotifier& notifier
  1153. )
  1154. {
  1155. ASSERT(NULL != pszName);
  1156. ASSERT(NULL != pVar);
  1157. HRESULT hr = E_OUTOFMEMORY;
  1158. CPropertyBucket *pBucket = _GetBucket(pszName);
  1159. if (NULL != pBucket)
  1160. {
  1161. //
  1162. // Merging the group's notify map with the 'active'
  1163. // map adds those notifications registered at the
  1164. // group level for this group.
  1165. //
  1166. notifier.MergeWithActiveNotifyMap(m_NotifyMapGroup);
  1167. hr = pBucket->SetProperty(pszName, pVar, notifier);
  1168. }
  1169. return hr;
  1170. }
  1171. //
  1172. // Creates a new constant property and sets its value.
  1173. // Since constants are only initialized and cannot be changed,
  1174. // this operation does not generate a notification event.
  1175. //
  1176. // Returns:
  1177. // S_OK
  1178. // E_OUTOFMEMORY
  1179. // TSPB_E_PROPNOTFOUND - Property name not found.
  1180. // TSPB_E_MODIFYCONST - Attempt to modify a const property.
  1181. //
  1182. HRESULT
  1183. CPropertyGroup::SetConstProperty(
  1184. LPCWSTR pszName,
  1185. const VARIANT *pVar
  1186. )
  1187. {
  1188. ASSERT(NULL != pszName);
  1189. ASSERT(NULL != pVar);
  1190. HRESULT hr = E_OUTOFMEMORY;
  1191. CPropertyBucket *pBucket = _GetBucket(pszName);
  1192. if (NULL != pBucket)
  1193. {
  1194. hr = pBucket->SetConstProperty(pszName, pVar);
  1195. }
  1196. return hr;
  1197. }
  1198. //
  1199. // Retrieve the value of a property in the property group.
  1200. //
  1201. // Returns:
  1202. // S_OK
  1203. // E_OUTOFMEMORY
  1204. // TSPB_E_PROPNOTFOUND - Property name not found.
  1205. //
  1206. HRESULT
  1207. CPropertyGroup::GetProperty(
  1208. LPCWSTR pszName,
  1209. VARIANT *pVarOut
  1210. ) const
  1211. {
  1212. ASSERT(NULL != pszName);
  1213. ASSERT(NULL != pVarOut);
  1214. ASSERT(IsValidWritePtr(pVarOut));
  1215. HRESULT hr = E_OUTOFMEMORY;
  1216. CPropertyBucket *pBucket = _GetBucket(pszName);
  1217. if (NULL != pBucket)
  1218. {
  1219. hr = pBucket->GetProperty(pszName, pVarOut);
  1220. }
  1221. return hr;
  1222. }
  1223. //
  1224. // Register a client for change notifications on a single property
  1225. // or all properties in a group.
  1226. //
  1227. // Returns:
  1228. // S_OK
  1229. // E_OUTOFMEMORY
  1230. // TSPB_E_PROPNOTFOUND - Property name not found.
  1231. // TSPB_E_NOTIFYCOOKIE - iClient is invalid.
  1232. //
  1233. HRESULT
  1234. CPropertyGroup::Advise(
  1235. LPCWSTR pszPropertyName, // NULL == All properties in group.
  1236. int iClient
  1237. )
  1238. {
  1239. HRESULT hr = E_OUTOFMEMORY;
  1240. if (NULL == pszPropertyName || L'\0' == *pszPropertyName)
  1241. {
  1242. //
  1243. // Register for a change on any property in this group.
  1244. //
  1245. hr = m_NotifyMapGroup.Set(iClient, true);
  1246. }
  1247. else
  1248. {
  1249. //
  1250. // Register for a change on a specific property.
  1251. //
  1252. CPropertyBucket *pBucket = _GetBucket(pszPropertyName);
  1253. if (NULL != pBucket)
  1254. {
  1255. hr = pBucket->Advise(pszPropertyName, iClient);
  1256. }
  1257. }
  1258. return hr;
  1259. }
  1260. //
  1261. // Cancel change notifications for a given client on all the property
  1262. // group and all properties in the group.
  1263. // Always returns S_OK.
  1264. //
  1265. HRESULT
  1266. CPropertyGroup::Unadvise(
  1267. int iClient
  1268. )
  1269. {
  1270. m_NotifyMapGroup.Set(iClient, false);
  1271. for (int i = 0; i < ARRAYSIZE(m_rgpBuckets); i++)
  1272. {
  1273. CPropertyBucket *pBucket = m_rgpBuckets[i];
  1274. if (NULL != pBucket)
  1275. {
  1276. pBucket->Unadvise(iClient);
  1277. }
  1278. }
  1279. return S_OK;
  1280. }
  1281. //
  1282. // Retrieve the address of a bucket object containing a named property.
  1283. // The bucket number is calculated by a hash on the property name.
  1284. // If a bucket does not yet exist for the property, one is created.
  1285. // Returns NULL if a bucket doesn't exist and can't be created.
  1286. //
  1287. CPropertyBucket *
  1288. CPropertyGroup::_GetBucket(
  1289. LPCWSTR pszName
  1290. ) const
  1291. {
  1292. ASSERT(NULL != pszName);
  1293. const DWORD iBucket = _HashName(pszName);
  1294. if (NULL == m_rgpBuckets[iBucket])
  1295. {
  1296. //
  1297. // Create a new bucket.
  1298. //
  1299. CPropertyBucket *pBucket;
  1300. HRESULT hr = CPropertyBucket::CreateInstance(&pBucket);
  1301. if (SUCCEEDED(hr))
  1302. {
  1303. CPropertyGroup *pNonConstThis = const_cast<CPropertyGroup *>(this);
  1304. pNonConstThis->m_rgpBuckets[iBucket] = pBucket;
  1305. }
  1306. }
  1307. return m_rgpBuckets[iBucket];
  1308. }
  1309. //
  1310. // Generate a hash value from a property name.
  1311. // The number generated will be between 0 and HASH_TABLE_SIZE.
  1312. //
  1313. DWORD
  1314. CPropertyGroup::_HashName(
  1315. LPCWSTR pszText
  1316. ) const
  1317. {
  1318. LPCWSTR p = NULL;
  1319. DWORD dwCode = 0;
  1320. if (IS_INTRESOURCE(pszText))
  1321. {
  1322. //
  1323. // Property name is really an ID. In that case just
  1324. // use the (ID % tablesize) as the hash code.
  1325. //
  1326. dwCode = (DWORD)((ULONG_PTR)(pszText));
  1327. }
  1328. else
  1329. {
  1330. DWORD dwTemp = 0;
  1331. for (p = pszText; TEXT('\0') != *p; p++)
  1332. {
  1333. dwCode = (dwCode << 4) + CharValue(*p);
  1334. if (0 != (dwTemp = dwCode & 0xF0000000))
  1335. {
  1336. dwCode = dwCode ^ (dwTemp >> 24);
  1337. dwCode = dwCode ^ dwTemp;
  1338. }
  1339. }
  1340. }
  1341. dwCode %= HASH_TABLE_SIZE;
  1342. ASSERT(dwCode < HASH_TABLE_SIZE);
  1343. return dwCode;
  1344. }
  1345. //-----------------------------------------------------------------------------
  1346. // CPropertyBag
  1347. //-----------------------------------------------------------------------------
  1348. class CPropertyBag : public IPropertyBag,
  1349. public ITaskSheetPropertyBag
  1350. {
  1351. public:
  1352. ~CPropertyBag(void);
  1353. //
  1354. // IUnknown
  1355. //
  1356. STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
  1357. STDMETHOD_(ULONG, AddRef)();
  1358. STDMETHOD_(ULONG, Release)();
  1359. //
  1360. // IPropertyBag
  1361. //
  1362. STDMETHOD(Read)(LPCOLESTR pszPropName, VARIANT *pVar, IErrorLog *pErrorLog);
  1363. STDMETHOD(Write)(LPCOLESTR pszPropName, VARIANT *pVar);
  1364. //
  1365. // ITaskSheetPropertyBag
  1366. //
  1367. STDMETHOD(CreatePropertyGroup)(REFGUID idGroup, HPROPGROUP *phGroupOut);
  1368. STDMETHOD(RemovePropertyGroup)(HPROPGROUP hGroup);
  1369. STDMETHOD(Set)(HPROPGROUP hGroup, LPCWSTR pszName, VARIANT *pVar);
  1370. STDMETHOD(SetConst)(HPROPGROUP hGroup, LPCWSTR pszName, VARIANT *pVar);
  1371. STDMETHOD(Get)(HPROPGROUP hGroup, LPCWSTR pszName, VARIANT *pVarOut);
  1372. STDMETHOD(PropertyGroupIdToHandle)(REFGUID id, HPROPGROUP *phGroupOut);
  1373. STDMETHOD(PropertyGroupHandleToId)(HPROPGROUP hGroup, GUID *pidCatOut);
  1374. STDMETHOD(RegisterNotify)(ITaskSheetPropertyNotifySink *pSink, DWORD *pdwCookie);
  1375. STDMETHOD(UnregisterNotify)(DWORD dwCookie);
  1376. STDMETHOD(Advise)(DWORD dwCookie, HPROPGROUP hGroup, LPCWSTR pszPropName);
  1377. private:
  1378. CPropertyBag(void);
  1379. LONG m_cRef;
  1380. CNotifyMap m_NotifyMapGlobal; // Global notify map.
  1381. CNotifier m_Notifier; // Handles notifying clients on chg.
  1382. CDpa<CPropertyGroup> m_dpaGroups; // DPA of (CPropertyGroup *)
  1383. //
  1384. // Prevent copy.
  1385. //
  1386. CPropertyBag(const CPropertyBag& rhs);
  1387. CPropertyBag& operator = (const CPropertyBag& rhs);
  1388. HRESULT _FindPropertyGroupByID(REFGUID idGroup, HPROPGROUP *phGroup) const;
  1389. HRESULT _GetPropertyGroup(HPROPGROUP hGroup, CPropertyGroup **ppGroup);
  1390. bool _IsValidPropertyGroupHandle(HPROPGROUP hGroup) const;
  1391. //
  1392. // Friendship with instance generator is typical.
  1393. //
  1394. friend HRESULT TaskUiPropertyBag_CreateInstance(REFIID riid, void **ppv);
  1395. };
  1396. //
  1397. // Create a property bag.
  1398. //
  1399. HRESULT
  1400. TaskUiPropertyBag_CreateInstance(
  1401. REFIID riid,
  1402. void **ppv
  1403. )
  1404. {
  1405. HRESULT hr = E_OUTOFMEMORY;
  1406. CPropertyBag *pBag = new CPropertyBag();
  1407. if (NULL != pBag)
  1408. {
  1409. if (pBag->m_dpaGroups.IsValid())
  1410. {
  1411. //
  1412. // Create the 'global' property group.
  1413. //
  1414. HPROPGROUP hGroup;
  1415. hr = pBag->CreatePropertyGroup(PGID_GLOBAL, &hGroup);
  1416. if (SUCCEEDED(hr))
  1417. {
  1418. ASSERT(PROPGROUP_GLOBAL == hGroup);
  1419. hr = pBag->QueryInterface(riid, ppv);
  1420. pBag->Release();
  1421. }
  1422. }
  1423. if (FAILED(hr))
  1424. {
  1425. delete pBag;
  1426. }
  1427. }
  1428. return hr;
  1429. }
  1430. CPropertyBag::CPropertyBag(
  1431. void
  1432. ) : m_cRef(1),
  1433. m_dpaGroups(4),
  1434. m_Notifier(this)
  1435. {
  1436. }
  1437. CPropertyBag::~CPropertyBag(
  1438. void
  1439. )
  1440. {
  1441. //
  1442. // Note that the m_dpaGroups member is self-destructive.
  1443. //
  1444. }
  1445. STDMETHODIMP
  1446. CPropertyBag::QueryInterface(
  1447. REFIID riid,
  1448. void **ppv
  1449. )
  1450. {
  1451. static const QITAB qit[] = {
  1452. QITABENT(CPropertyBag, IPropertyBag),
  1453. QITABENT(CPropertyBag, ITaskSheetPropertyBag),
  1454. { 0 },
  1455. };
  1456. return QISearch(this, qit, riid, ppv);
  1457. }
  1458. STDMETHODIMP_ (ULONG)
  1459. CPropertyBag::AddRef(
  1460. void
  1461. )
  1462. {
  1463. return InterlockedIncrement(&m_cRef);
  1464. }
  1465. STDMETHODIMP_ (ULONG)
  1466. CPropertyBag::Release(
  1467. void
  1468. )
  1469. {
  1470. if (InterlockedDecrement(&m_cRef))
  1471. return m_cRef;
  1472. delete this;
  1473. return 0;
  1474. }
  1475. //
  1476. // Returns:
  1477. // S_OK - Group created.
  1478. // E_OUTOFMEMORY.
  1479. // TSPB_E_GROUPEXISTS
  1480. //
  1481. STDMETHODIMP
  1482. CPropertyBag::CreatePropertyGroup(
  1483. REFGUID idGroup,
  1484. HPROPGROUP *phGroupOut
  1485. )
  1486. {
  1487. ASSERT(NULL != phGroupOut);
  1488. ASSERT(IsValidWritePtr(phGroupOut));
  1489. if (NULL == phGroupOut || !IsValidWritePtr(phGroupOut))
  1490. {
  1491. //
  1492. // Parameter validation for public API.
  1493. //
  1494. return E_INVALIDARG;
  1495. }
  1496. HRESULT hr = _FindPropertyGroupByID(idGroup, phGroupOut);
  1497. if (S_OK == hr)
  1498. {
  1499. hr = TSPB_E_GROUPEXISTS;
  1500. }
  1501. else if (S_FALSE == hr)
  1502. {
  1503. hr = E_OUTOFMEMORY;
  1504. CPropertyGroup *pGroup = new CPropertyGroup(idGroup);
  1505. if (NULL != pGroup)
  1506. {
  1507. //
  1508. // First try to find an empty slot in the DPA.
  1509. //
  1510. int cSlots = m_dpaGroups.Count();
  1511. for (int i = 0; i < cSlots; i++)
  1512. {
  1513. if (NULL == m_dpaGroups.Get(i))
  1514. {
  1515. m_dpaGroups.Set(i, pGroup);
  1516. *phGroupOut = i;
  1517. pGroup = NULL;
  1518. break;
  1519. }
  1520. }
  1521. if (NULL != pGroup)
  1522. {
  1523. //
  1524. // DPA is full, extend it.
  1525. //
  1526. const int iGroup = m_dpaGroups.Append(pGroup);
  1527. if (-1 != iGroup)
  1528. {
  1529. *phGroupOut = iGroup;
  1530. hr = S_OK;
  1531. }
  1532. else
  1533. {
  1534. delete pGroup;
  1535. }
  1536. }
  1537. }
  1538. }
  1539. return hr;
  1540. }
  1541. //
  1542. // Removes a property group from the bag. This includes removing
  1543. // all of the properties associated with the group.
  1544. // Note that the 'global' group cannot be
  1545. // removed. It's scope is the lifetime of the property bag and
  1546. // is destroyed when the bag is destroyed.
  1547. //
  1548. STDMETHODIMP
  1549. CPropertyBag::RemovePropertyGroup(
  1550. HPROPGROUP hGroup
  1551. )
  1552. {
  1553. CPropertyGroup *pGroup;
  1554. HRESULT hr = _GetPropertyGroup(hGroup, &pGroup);
  1555. if (SUCCEEDED(hr) && PROPGROUP_GLOBAL != hGroup)
  1556. {
  1557. delete pGroup;
  1558. m_dpaGroups.Set(hGroup, NULL);
  1559. }
  1560. return hr;
  1561. }
  1562. //
  1563. // Converts a property group ID (guid) to a group handle.
  1564. // The handle returned is the same value that was returned
  1565. // in CreatePropertyGroup.
  1566. //
  1567. STDMETHODIMP
  1568. CPropertyBag::PropertyGroupIdToHandle(
  1569. REFGUID idGroup,
  1570. HPROPGROUP *phGroupOut
  1571. )
  1572. {
  1573. ASSERT(NULL != phGroupOut);
  1574. ASSERT(IsValidWritePtr(phGroupOut));
  1575. if (NULL == phGroupOut || !IsValidWritePtr(phGroupOut))
  1576. {
  1577. //
  1578. // Parameter validation for public API.
  1579. //
  1580. return E_INVALIDARG;
  1581. }
  1582. HRESULT hr = TSPB_E_GROUPNOTFOUND;
  1583. const int cGroups = m_dpaGroups.Count();
  1584. for (int i = 0; i < cGroups; i++)
  1585. {
  1586. CPropertyGroup *pGroup = m_dpaGroups.Get(i);
  1587. if (NULL != pGroup && IsEqualGUID(pGroup->GetID(), idGroup))
  1588. {
  1589. *phGroupOut = i;
  1590. hr = S_OK;
  1591. }
  1592. }
  1593. return hr;
  1594. }
  1595. //
  1596. // Converts a property group handle to a group ID (guid).
  1597. // Returns TSPB_E_GROUPNOTFOUND if the group handle is invalid.
  1598. // Cannot retrieve a handle to the pseudo-group handles
  1599. // PROPGROUP_ANY or PROPGROUP_INVALID.
  1600. //
  1601. STDMETHODIMP
  1602. CPropertyBag::PropertyGroupHandleToId(
  1603. HPROPGROUP hGroup,
  1604. GUID *pidGroupOut
  1605. )
  1606. {
  1607. ASSERT(NULL != pidGroupOut);
  1608. ASSERT(IsValidWritePtr(pidGroupOut));
  1609. if (NULL == pidGroupOut || !IsValidWritePtr(pidGroupOut))
  1610. {
  1611. //
  1612. // Parameter validation for public API.
  1613. //
  1614. return E_INVALIDARG;
  1615. }
  1616. CPropertyGroup *pGroup;
  1617. HRESULT hr = _GetPropertyGroup(hGroup, &pGroup);
  1618. if (SUCCEEDED(hr))
  1619. {
  1620. ASSERT(NULL != pGroup);
  1621. *pidGroupOut = pGroup->GetID();
  1622. }
  1623. return hr;
  1624. }
  1625. //
  1626. // Set a property value in the bag.
  1627. //
  1628. // Returns:
  1629. // S_OK - Property value changed.
  1630. // S_FALSE - Property value unchanged (dup value).
  1631. // E_INVALIDARG
  1632. // E_OUTOFMEMORY
  1633. // TSPB_E_GROUPNOTFOUND - Invalid group handle.
  1634. // TSPB_E_MODIFYCONST - Attempt to modify a const property.
  1635. //
  1636. STDMETHODIMP
  1637. CPropertyBag::Set(
  1638. HPROPGROUP hGroup,
  1639. LPCWSTR pszName,
  1640. VARIANT *pVar
  1641. )
  1642. {
  1643. ASSERT(NULL != pszName);
  1644. ASSERT(NULL != pVar);
  1645. if (NULL == pszName || NULL == pVar)
  1646. {
  1647. //
  1648. // Parameter validation for public API.
  1649. //
  1650. return E_INVALIDARG;
  1651. }
  1652. CPropertyGroup *pGroup;
  1653. HRESULT hr = _GetPropertyGroup(hGroup, &pGroup);
  1654. if (SUCCEEDED(hr))
  1655. {
  1656. ASSERT(NULL != pGroup);
  1657. //
  1658. // Initialize the active notify map to the content of the
  1659. // global notify map.
  1660. //
  1661. m_Notifier.Reset();
  1662. m_Notifier.MergeWithActiveNotifyMap(m_NotifyMapGlobal);
  1663. m_Notifier.SetPropertyGroup(hGroup);
  1664. hr = pGroup->SetProperty(pszName, pVar, m_Notifier);
  1665. }
  1666. return hr;
  1667. }
  1668. //
  1669. // Create a constant property value in the bag.
  1670. //
  1671. //
  1672. // Returns:
  1673. // S_OK
  1674. // E_INVALIDARG
  1675. // E_OUTOFMEMORY
  1676. // TSPB_E_GROUPNOTFOUND - Invalid group handle.
  1677. // TSPB_E_MODIFYCONST - Attempt to modify a const property.
  1678. //
  1679. STDMETHODIMP
  1680. CPropertyBag::SetConst(
  1681. HPROPGROUP hGroup,
  1682. LPCWSTR pszName,
  1683. VARIANT *pVar
  1684. )
  1685. {
  1686. ASSERT(NULL != pszName);
  1687. ASSERT(NULL != pVar);
  1688. if (NULL == pszName || NULL == pVar)
  1689. {
  1690. //
  1691. // Parameter validation for public API.
  1692. //
  1693. return E_INVALIDARG;
  1694. }
  1695. CPropertyGroup *pGroup;
  1696. HRESULT hr = _GetPropertyGroup(hGroup, &pGroup);
  1697. if (SUCCEEDED(hr))
  1698. {
  1699. ASSERT(NULL != pGroup);
  1700. hr = pGroup->SetConstProperty(pszName, pVar);
  1701. }
  1702. return hr;
  1703. }
  1704. //
  1705. // Retrieve property value from the bag.
  1706. //
  1707. // Returns:
  1708. // S_OK
  1709. // E_INVALIDARG
  1710. // E_OUTOFMEMORY
  1711. // TSPB_E_GROUPNOTFOUND - Invalid group handle.
  1712. // TSPB_E_MODIFYCONST - Attempt to modify a const property.
  1713. //
  1714. STDMETHODIMP
  1715. CPropertyBag::Get(
  1716. HPROPGROUP hGroup,
  1717. LPCWSTR pszName,
  1718. VARIANT *pVarOut
  1719. )
  1720. {
  1721. ASSERT(NULL != pszName);
  1722. ASSERT(NULL != pVarOut);
  1723. ASSERT(IsValidWritePtr(pVarOut));
  1724. if (NULL == pszName || NULL == pVarOut || !IsValidWritePtr(pVarOut))
  1725. {
  1726. //
  1727. // Parameter validation for public API.
  1728. //
  1729. return E_INVALIDARG;
  1730. }
  1731. CPropertyGroup *pGroup;
  1732. HRESULT hr = _GetPropertyGroup(hGroup, &pGroup);
  1733. if (SUCCEEDED(hr))
  1734. {
  1735. ASSERT(NULL != pGroup);
  1736. hr = pGroup->GetProperty(pszName, pVarOut);
  1737. }
  1738. return hr;
  1739. }
  1740. //
  1741. // Register an interface pointer for property change notifications.
  1742. //
  1743. STDMETHODIMP
  1744. CPropertyBag::RegisterNotify(
  1745. ITaskSheetPropertyNotifySink *pSink,
  1746. DWORD *pdwCookie
  1747. )
  1748. {
  1749. ASSERT(NULL != pSink);
  1750. ASSERT(NULL != pdwCookie);
  1751. if (NULL == pSink || NULL == pdwCookie || !IsValidWritePtr(pdwCookie))
  1752. {
  1753. //
  1754. // Parameter validation for public API.
  1755. //
  1756. return E_INVALIDARG;
  1757. }
  1758. return m_Notifier.Register(pSink, pdwCookie);
  1759. }
  1760. //
  1761. // Cancel all notifications associated with a given connection cookie.
  1762. //
  1763. // Returns:
  1764. // S_OK
  1765. // TSPB_E_GROUPNOTFOUND - Invalid group handle.
  1766. //
  1767. STDMETHODIMP
  1768. CPropertyBag::UnregisterNotify(
  1769. DWORD dwCookie
  1770. )
  1771. {
  1772. //
  1773. // Unegister the sink with the notifier.
  1774. //
  1775. HRESULT hr = m_Notifier.Unregister(dwCookie);
  1776. if (SUCCEEDED(hr))
  1777. {
  1778. //
  1779. // Unregister from the global notify map.
  1780. //
  1781. m_NotifyMapGlobal.Set(dwCookie, false);
  1782. //
  1783. // Unregister each property group.
  1784. //
  1785. const cGroups = m_dpaGroups.Count();
  1786. for (int i = 0; i < cGroups; i++)
  1787. {
  1788. CPropertyGroup *pGroup = m_dpaGroups.Get(i);
  1789. if (NULL != pGroup)
  1790. {
  1791. pGroup->Unadvise(dwCookie);
  1792. }
  1793. }
  1794. }
  1795. return hr;
  1796. }
  1797. //
  1798. // Request that a notification interface be notified when certain
  1799. // properties change. The interface is identified by the 'cookie' that
  1800. // was returned in RegisterNotify.
  1801. // The property(s) of interested are identified through the hGroup
  1802. // and pszPropertyName arguments as follows:
  1803. //
  1804. // hGroup pszPropName(*) Notify triggered on change
  1805. // ------------ ---------------- ------------------------------------
  1806. // ANYPROPGROUP <ignored> Any property in the bag.
  1807. // PROPGROUP_GLOBAL NULL Any property in the global group
  1808. // PROPGROUP_GLOBAL Non-NULL Named property in the global group
  1809. // Other NULL Any property in the group
  1810. // Other Non-NULL Named property in the group
  1811. //
  1812. // (*) For the prop name, NULL and L"" are equivalent.
  1813. //
  1814. // Returns:
  1815. // S_OK
  1816. // E_INVALIDARG
  1817. // E_OUTOFMEMORY
  1818. // TSPB_E_GROUPNOTFOUND - Invalid group handle.
  1819. //
  1820. STDMETHODIMP
  1821. CPropertyBag::Advise(
  1822. DWORD dwCookie,
  1823. HPROPGROUP hGroup,
  1824. LPCWSTR pszPropertyName
  1825. )
  1826. {
  1827. HRESULT hr;
  1828. if (PROPGROUP_ANY == hGroup)
  1829. {
  1830. //
  1831. // Register for changes in any property.
  1832. //
  1833. hr = m_NotifyMapGlobal.Set(dwCookie, true);
  1834. }
  1835. else
  1836. {
  1837. CPropertyGroup *pGroup;
  1838. hr = _GetPropertyGroup(hGroup, &pGroup);
  1839. if (SUCCEEDED(hr))
  1840. {
  1841. ASSERT(NULL != pGroup);
  1842. //
  1843. // Register for changes in either a group or a specific
  1844. // property.
  1845. //
  1846. hr = pGroup->Advise(pszPropertyName, dwCookie);
  1847. }
  1848. }
  1849. return hr;
  1850. }
  1851. //
  1852. // IPropertyBag implementation.
  1853. //
  1854. // Read a property from the 'global' namespace.
  1855. //
  1856. // BUGBUG: Do we need to do anything with pErrorLog?
  1857. //
  1858. STDMETHODIMP
  1859. CPropertyBag::Read(
  1860. LPCOLESTR pszPropName,
  1861. VARIANT *pVar,
  1862. IErrorLog * /* unused */
  1863. )
  1864. {
  1865. ASSERT(NULL != pszPropName);
  1866. ASSERT(NULL != pVar);
  1867. ASSERT(IsValidWritePtr(pVar));
  1868. if (NULL == pszPropName || NULL == pVar || !IsValidWritePtr(pVar))
  1869. {
  1870. //
  1871. // Parameter validation for public API.
  1872. //
  1873. return E_INVALIDARG;
  1874. }
  1875. return Get(PROPGROUP_GLOBAL, pszPropName, pVar);
  1876. }
  1877. //
  1878. // Write a property to the 'global' namespace.
  1879. //
  1880. STDMETHODIMP
  1881. CPropertyBag::Write(
  1882. LPCOLESTR pszPropName,
  1883. VARIANT *pVar
  1884. )
  1885. {
  1886. ASSERT(NULL != pszPropName);
  1887. ASSERT(NULL != pVar);
  1888. if (NULL == pszPropName || NULL == pVar)
  1889. {
  1890. //
  1891. // Parameter validation for public API.
  1892. //
  1893. return E_INVALIDARG;
  1894. }
  1895. return Set(PROPGROUP_GLOBAL, pszPropName, pVar);
  1896. }
  1897. //
  1898. // Locate a property group by it's ID (guid), returning it's handle.
  1899. // Returns:
  1900. // S_OK - Found group.
  1901. // S_FALSE - Didn't find group.
  1902. //
  1903. HRESULT
  1904. CPropertyBag::_FindPropertyGroupByID(
  1905. REFGUID idGroup,
  1906. HPROPGROUP *phGroup
  1907. ) const
  1908. {
  1909. ASSERT(NULL != phGroup);
  1910. ASSERT(IsValidWritePtr(phGroup));
  1911. const int cCategories = m_dpaGroups.Count();
  1912. for (int i = 0; i < cCategories; i++)
  1913. {
  1914. const CPropertyGroup *pGroup = m_dpaGroups.Get(i);
  1915. if (NULL != pGroup && IsEqualGUID(pGroup->GetID(), idGroup))
  1916. {
  1917. *phGroup = i;
  1918. return S_OK;
  1919. }
  1920. }
  1921. return S_FALSE;
  1922. }
  1923. //
  1924. // Determine if a group handle is valid.
  1925. // Note that the pseudo handle PROPGROUP_GLOBAL is valid.
  1926. //
  1927. bool
  1928. CPropertyBag::_IsValidPropertyGroupHandle(
  1929. HPROPGROUP hGroup
  1930. ) const
  1931. {
  1932. return (PROPGROUP_ANY != hGroup &&
  1933. PROPGROUP_INVALID != hGroup &&
  1934. hGroup < m_dpaGroups.Count());
  1935. }
  1936. //
  1937. // Retrieve the address of the CPropertyGroup object associated
  1938. // with a particular group handle. The function performs handle
  1939. // validation.
  1940. //
  1941. HRESULT
  1942. CPropertyBag::_GetPropertyGroup(
  1943. HPROPGROUP hGroup,
  1944. CPropertyGroup **ppGroup
  1945. )
  1946. {
  1947. ASSERT(NULL != ppGroup);
  1948. ASSERT(IsValidWritePtr(ppGroup));
  1949. HRESULT hr = TSPB_E_GROUPNOTFOUND;
  1950. if (_IsValidPropertyGroupHandle(hGroup))
  1951. {
  1952. *ppGroup = m_dpaGroups.Get(hGroup);
  1953. hr = S_OK;
  1954. }
  1955. return hr;
  1956. }