Stingray® Foundation : Chapter 14 Persistence Framework : SFL Property Bags
SFL Property Bags
SFL introduces two property bag implementations to be used generically by custom applications. One uses the Windows registry as data storage; the other uses the Microsoft XML Document Object Model. These two media are prime candidates for property bag storage, due to their flexible API and their hierarchical nature.
The SFLPropBags sample, which is in the COMServers\Persistence folder in your Stingray Foundation Library installation, is an ATL-based COM DLL that publishes the two SFL property bag implementations as COM objects. The property bags can be used from any COM-enabled programming language or development tool, like Visual Basic, Visual J++, or Visual C++. The property bag classes can also be used in a C++ application at a source code level.
Which approach to use depends on the specific characteristics of your project. The benefits and drawbacks of each approach are the same that you face when deciding whether to use source code-based components or binary COM objects. If the objects you want to persist in your application are already COM-enabled, or if you are using a language other than C++, you should use the property bags as independent COM objects. If you want less dependence on external libraries, or if you want to maintain the COM requirements on your objects as an implementation detail of your code, you are better off using the implementations at the source code level.
Data Types
The property bag specification supports every VARIANT-compatible type to be stored and retrieved in a property bag.
SFL’s implementation supports the following types. The specific storage characteristics depend on what property bag class is being used.
Basic types (integer, boolean, long, and so on)
Strings
Objects. If the object is persistable, it is asked to save itself as a sublevel of the property bag tree. Objects are saved as composite properties, where the entire set of the object properties is contained under the name-value pair that identifies the object within its parent. If the object is not persistable, it cannot be manipulated by the property bag, so an error occurs.
Safe arrays. Elements are saved and retrieved one by one, recursively applying the rules described above depending on the type of the element, with one exception: if the safe array element type is INT1 (byte), the safearray is treated as a binary stream and manipulated as such.
SFL’s property bag implementations do not support user-defined types (structures). If you want to store a structure, you have to decompose it explicitly into its constituent data fields and store these individually. Then at read time, you must restore them explicitly.
IPersistenceStrategy Interface
The IPropertyBag and IPropertyBag2 interfaces offer methods to read and write data to and from the persistent media. This is enough functionality for the object being persisted, which needs only those methods to load or save its information.
However, an application that uses the property bag to serialize its objects needs more functionality. The application must control where the data is going to be persisted, for example, and needs to launch the serialization process. This functionality is not standardized by any of the COM persistence interfaces.
The SFL implementation defines an additional interface that is implemented by all the property bag concrete classes. It is the IPersistenceStrategy interface.
The IPersistenceStrategy::Init() method initializes the property bag. It takes a VARIANT parameter, whose semantics are dependent on the actual implementation. For example, for a property bag whose media is a file in the file system, the required initialization parameter could be the file name. For an implementation that uses a relational database, it could be the connection information for the database.
The Save() and Load() methods start the corresponding operation on a given persistable object. This object becomes the root of the property bag. Given the hierarchical nature of property bags, multiple persistable objects can be stored as subobjects of this root object.
The Commit() method is used to commit to persistent media a save operation. The data is not guaranteed to be persistent until the Commit() method has been invoked.
Registry Property Bag
The registry property bag implementation stores the information in a given key in the registry. The implementation assumes that the user executing the application has read/write rights in the HKEY_CURRENT_USER key in the registry.
The Init() method in this IPersistenceStrategy implementation expects the name of the registry key where the data will be stored. The string passed must be the relative path of the key within the windows registry database, assuming HKEY_CURRENT_USER as the starting point. For example:
 
pPropBagInit->Init (“Software\\MyCompany\\MyApp\\Data”)
This directs all input and output queries to the property bag to the registry key HKEY_CURRENT_USER\Software\MyCompany\MyApp\Data.
The Commit() method does not have any effect in the case of Registry property bags, but it is a good idea to use it always after Load() and Save() operations to enhance the transparency of the media.
XML Property Bag
The XML property bag implementation makes use of the Microsoft XML document model (DOM) provided as part of Internet Explorer 5.0 or later. Previous versions of the XML DOM are incompatible with this one, and therefore this implementation will not work in such systems.
Microsoft’s DOM always manages the underlying XML document in memory until it is told to save it to permanent media. Therefore, you always need to call Commit() after a saving operation so the SFL implementation can appropriately save the XML document to disk.
The IPersistenceStrategy::Init() method can take two possible parameters in the XML property bag:
A string, that represents the name of a file on disk that contains the XML document.
A COM stream instance (object that implements IStream). The XML document will be written to this stream, regardless of what its media is, adding one more level of indirection to the persistence operation. This option is useful for memory-only persistence operations like clipboard interaction or drag-and-drop.
A proprietary XML document format is used to represent the property bag. This is an example of the resulting XML file:
Example 123 – XML document representing a property bag
<SflPropBag>
<PersistableObject PropName="BooksCollection"
CLSID="{395FF86C-0AD5-4B1D-A317-4AB3A8FD3370}">
<BasicType PropName="BooksCount" ValueType="2">2</BasicType>
<PersistableObject PropName="Book1"
CLSID="{445E7451-7261-11D2-9D33-00C04F91E286}">
<BasicType PropName="Title"
ValueType="8">The C++ Programming Language</BasicType>
<BasicType PropName="AuthorsCount" ValueType="3">1</BasicType>
<PersistableObject PropName="Author1"
CLSID="{445E744F-7261-11D2-9D33-00C04F91E286}">
<BasicType PropName="FirstName" ValueType="8">Bjarne</BasicType>
<BasicType PropName="LastName" ValueType="8">Stroustrup</BasicType>
</PersistableObject>
</PersistableObject>
</PersistableObject>
</SflPropBag>
Examples
The following is some code in Visual Basic that illustrates the usage of the SFL property bag implementations.
Example 124 – Initializing and loading an object from an XML property bag
Set bag = New XMLPropertyBag
bag.Init "books.xml"
bag.Load "BooksCollection", Books
bag.Commit
 
Example 125 – Saving an existing object to a registry property bag
Set bag = New RegistryPropertyBag
bag.Init "Software\Stingray\SFL\Persistence"
bag.Save "BooksCollection", Books
bag.Commit
 
The implementation of the persistable object in Visual Basic requires the class to be marked as Persistable, by assigning the corresponding value to the class properties. This adds two methods to the class, ReadProperties() and WriteProperties(), which correspond to the Load() and Save() methods of the IPropertyBag interface. Example 126 shows the implementation of these methods for a book collection class.
Example 126 – Implementing Load() and Save() methods for a collection class
Private Sub Class_ReadProperties(PropBag As PropertyBag)
Dim count As Integer
count = PropBag.ReadProperty("BooksCount")
ReDim Books(1 To count)
Dim i As Integer
For i = 1 To count
Set Books(i) = PropBag.ReadProperty("Book" & i)
Next i
End Sub
 
Private Sub Class_WriteProperties(PropBag As PropertyBag)
Dim count As Integer
count = UBound(Books)
PropBag.WriteProperty "BooksCount", count
Dim i As Integer
For i = 1 To count
PropBag.WriteProperty "Book" & i, Books(i)
Next i
End Sub
 
To implement a persistable object in C++, you need to implement one of the COM interfaces IPersistPropertyBag or IPersistPropertyBag2. How to do this depends on the framework you are using to develop your components. Example 127 shows what it might look like if you used ATL.
Example 127 – Implementing a persistable C++ object using ATL
class CPersistableComponent:
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CPersistableComponent, &__uuidof(CPersistableComponent)>,
public IMyComponent,
public IPersistPropertyBag
{
public:
BEGIN_COM_MAP(CPersistableComponent)
COM_INTERFACE_ENTRY(IPersistPropertyBag)
COM_INTERFACE_ENTRY2(IPersist, IPersistPropertyBag)
COM_INTERFACE_ENTRY(IMyComponent)
END_COM_MAP()
º
};
It is important to notice that persistable COM objects need to be creatable, since the property bag needs to create a new instance using the standard COM mechanisms whenever it is retrieving an object of that class. To be created, an object requires a CLSID and a registered class factory.
Example 128 shows the implementation of the Save() and Load() methods of a persistable object. The implementation makes use of the Load() and Save() methods in the IPropertyBag (or IPropertyBag2) interface pointer it receives as a parameter to retrieve or store individual pieces of data the object considers to be part of its persistent internal state.
Example 128 – Implementing Save() and Load() methods for a persistable C++ object
STDMETHOD(Load)(IPropertyBag* pPropBag, IErrorLog* pErrorLog)
{
HRESULT hr = S_OK;
_variant_t vaProp;
hr = pPropBag->Read(OLESTR("Number"), &vaProp, pErrorLog);
if (FAILED(hr)) return E_FAIL;
m_nANumber = static_cast<long>(vaProp);
vaProp.Clear();
 
CPoint pt;
hr = pPropBag->Read(OLESTR("PositionX"), &vaProp, pErrorLog);
if (FAILED(hr)) return E_FAIL;
pt.x = static_cast<long>(vaProp);
hr = pPropBag->Read(OLESTR("PositionY"), &vaProp, pErrorLog);
if (FAILED(hr)) return E_FAIL;
pt.y = static_cast<long>(vaProp);
 
return S_OK;
}
 
STDMETHOD(Save)(IPropertyBag* pPropBag, BOOL fClearDirty,
BOOL fSaveAllProperties)
{
HRESULT hr = S_OK;
_variant_t vaProp;
vaProp = static_cast<long>(m_nANumber);
hr = pPropBag->Write(OLESTR("Number"), &vaProp);
if (FAILED(hr)) return E_FAIL;
vaProp = static_cast<long>(m_rc.left);
hr = pPropBag->Write(OLESTR("PositionX"), &vaProp);
if (FAILED(hr)) return E_FAIL;
vaProp = static_cast<long>(m_rc.top);
hr = pPropBag->Write(OLESTR("PositionY"), &vaProp);
if (FAILED(hr)) return E_FAIL;
return S_OK;
}