Stingray® Foundation : Chapter 14 Persistence Framework : Using Property Bags in C++ Code
Using Property Bags in C++ Code
Although the SFL property bags implementation are intended to be used as an independent COM library, it is also possible to use the individual classes directly from source code in a C++ application when a flexible persistence mechanism needs to be put in place.
First, it is important to note that, even if used at the source code level, SFL’s property bag implementations require some degree of COM support in order to work correctly. For example, the persistable objects need to implement the IUnknown interface and the corresponding IPersistXXX interface. Also, the data that will be stored in a property bag needs to be representable as a set of name-value pairs, where the values are of VARIANT-compatible types.
You can adapt your C++ objects by partially enabling just the COM needed to work with the property bags at a source code level. For example, Example 129 illustrates a C++ class CHybrid that combines the C++ interface mechanism based on the IQueryGuid interface, with an IPersistPropertyBag implementation put together using ATL as the COM framework.
Example 129 – Enabling COM support for property bags within a C++ class
class __declspec(uuid("B348A7BB-8573-4979-8E9E-40387CC80D29")) CHybrid:
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass< CHybrid, &__uuidof(CHybrid)>,
public IPersistPropertyBag,
// IHybrid derives from both IUnknown and IQueryGuid
public IHybrid // Access to C++ class functionality from COM
{
public:
 
DECLARE_NO_REGISTRY() // Internal object
DECLARE_PROTECT_FINAL_CONSTRUCT()
 
// COM interface map
BEGIN_COM_MAP(CHybrid)
COM_INTERFACE_ENTRY(IPersistPropertyBag)
COM_INTERFACE_ENTRY2(IPersist, IPersistPropertyBag)
COM_INTERFACE_ENTRY(IHybrid)
END_COM_MAP_NO_PURE()
ATL_IUNKNOWN_IMPL()
 
// SFL interface map
BEGIN_GUID_MAP(CHybrid)
GUID_ENTRY(IHybrid)
END_GUID_MAP
 
<...>
};
The IQueryGuid interface is used widely in SFL. See Chapter 3, “Interface‑Based Programming,” for more information. The class CHybrid utilizes the QueryGuid mechanism for C++ interface-based programming. However, when persistence was added, an IUnknown implementation was needed, as well as the interface querying based on the QueryInterface function used in COM. Since this is not a true COM class, it is not available for external instantiation and it doesn’t have to deal with issues like apartments or contexts, thread models, or other COM-isms. Its lifetime does not even need to be managed by the reference counting mechanism. It needs only enough functionality to satisfy the expectations of the property bag:
An IPersistPropertyBagX implementation
An IUnknown implementation that knows how to respond to requests for that interface
Optionally, a registered class factory that allows the property bag to create new instances of the class
Persistable objects do not need to have their COM class factory registered in the system. The property bag implementation is adjusted to always look at the local _Module variable first for “internal” class factories corresponding to C++ objects disguised as COM objects for persistence purposes only.
If you want to use an altogether different creation mechanism for your persistable objects, the IPropertyBag2 interface offers a method LoadObject(). It allows you to load a persisted state in an instance already created, as in Example 130.
Example 130 – Loading a persisted state in an existing instance
CHybrid* pHybrid = GetNewHybridInstanceBySomeOtherMechanism();
HRESULT hr = pBag2->LoadObject(OLESTR("ExistingInstance"), 0,
static_cast<IUnknown*>(pHybrid), NULL);
MVC Integration
Usually, when your application uses the Model-View-Controller architecture, the model is the place from where the data you want to persist is accessible. You should save a model instance to a disk file as a response to the Save or Save as command, and you should retrieve an existing model from a disk file when your application receives the Open command.
In order to facilitate this typical scenario, SFL includes a class that merges the Model concept from the MVC architecture with the persistence capabilities of the property bags implementation.
The CMvcPersistableModel class is defined in the header file MvcPersist.h, located in the include\foundation\mvc folder of your SFL installation. This class defines only two virtual methods: Load() and Save(), both of which receive, as their only parameter, a string that represents the name of the file to which the disk operation will be directed. Both methods are declared as abstract, and no implementation is given.
Model objects derived from CMvcPersistableModel can Load() and Save() themselves to disk, but there is no assumption on how this process is actually going to be accomplished. The CMvcPropertyBagPersistableModel translates that abstract defined behavior to an actual implementation that uses SFL’s XML property bag as the output of the Save() operation and input of the Load() process.
The CMvcPropertyBagPersistableModel offers the following services:
Derives from the basic ATL classes in order to provide an IUnknown implementation for your model.
Implements the IPersistPropertyBag interface.
Responds to the Load() and Save() methods inherited from CMvcPersistableModel, initializing a property bag associated to the file name given.
Model classes incorporating persistence in this fashion must include CMvcPropertyBagPersistableModel among their base classes and override two methods: WriteToPropertyBag() and ReadFromPropertyBag(). Both of these methods receive a pointer to an IPropertyBag interface, which will be used for all the input/output operations of the model. Both methods return a boolean value, which should be set to FALSE to indicate the occurrence of some error condition (TRUE otherwise).
Example 131 shows how to override these methods.
Example 131 – Overriding read and write methods to enable persistence
class __declspec(uuid("7C540CD2-3B5C-46be-885E-0B82E13A28A6")) CMyModel:
public CMvcPropertyBagPersistableModel<CMyModel>
{
<...>
 
virtual bool WriteToPropertyBag (
IPropertyBag* pPropBag
)
{
_variant_t vaProp = static_cast<long>(m_size);
hr = pPropBag->Write(OLESTR("Size"), &vaProp);
if (FAILED(hr)) return false;
 
vaProp(static_cast<IUnknown*>(&m_NestedObject));
HRESULT hr = pPropBag->Write(OLESTR("NestedObject"), &vaProp);
if (FAILED(hr)) return false;
return true;
}
 
virtual bool ReadFromPropertyBag (
IPropertyBag* pPropBag
)
{
// All our property bags are guaranteed
// to implement this interface
IPropertyBag2Ptr spBag2 = pPropBag;
if (spBag2 == 0) return false;
 
hr = pPropBag->Read(OLESTR("Size"), &vaProp, pErrorLog);
if (FAILED(hr)) return false;
long m_size = static_cast<long>(vaProp);
 
// LoadObject does not exist in IPropertyBag
HRESULT hr = spBag2->LoadObject(OLESTR("NestedObject"), 0,
static_cast<IUnknown*>(&m_NestedObject), NULL);
if (FAILED(hr)) return false;
return true;
}
 
<...>
};
 
It is important to notice that the IUnknown implementation provided in CMvcPropertyBagPersistableModel makes no assumption about the allocation of the model. The instance is not destroyed when the reference counter is decremented past zero, since there is no assurance the model is allocated on the heap. You can manage the lifetime of your model independently of this IUnknown implementation, or you can take advantage of reference counting for lifetime management by overriding Release() and performing the necessary deallocation process there.