Introduction
First I'll assume you know COM. It wouldn't hurt to understand the concepts of serialization, nor does it hurt to have used the interfaces IPropertyBag
and IPersistPropertyBag
.
This is a port of Don Box' excellent XML property bag implementation. It's a COM class which implements the IPropertyBag
interface, meaning that it can serialize and deserialize COM objects implementing the IPersistPropertyBag
interface.
Property bags are essentially a collection of name/value pairs. When an object is serialized, the property bag basically asks the object to give it all its properties along with their names. Conversely, when an object is deserialized, the property bag asks the object to repopulate its properties using what's available in the property bag.
This information exchange is done through the interfaces IPropertyBag
and IPersistPropertyBag
. The property bag implements IPropertyBag
and the serialized/deserialized object implements IPersistPropertyBag
.
A serialization call sequence might look like this:
IPropertyBag::SaveToFile("file", pUnkObject);
|
v
IPersistPropertyBag* pPersist;
pUnkObject->QueryInterface(
IID_IPersistPropertyBag,
&pPersist);
pPersist->Save(this, ...) --> IPersistPropertyBag::Save(IPropertyBag* pPropBag, ...)
|
v
foreach(p in properties) {
IPropertyBag::Write(BSTR name, <---- pPropBag->Write(p.bstrName, &p.varValue);
VARIANT* value) }
|
v
Make data persistant (e.g. save to file)
Ok, first I apologize for not drawing a proper picture, but I don't own a decent drawing program. Second, I'd like to inform you that it's in IPropertyBag::Write
where the magic happens. Typically, it's here where data is made persistent. In this implementation, it generates an XML element inside an XML document which is written to disk after all properties have been saved.
IPropertyBag::Write
can do more magic than meets the eye. If the variant it's been passed contains an interface pointer, it will be queried for the IPersistPropertyBag
interface. If the query yields an interface pointer, it will be used recursively. Thus it is possible to serialize trees of objects.
Deserialization works similarly to serialization. IPersistPropertyBag::Read()
is called instead of IPersistPropertyBag::Write()
. The object being deserialized calls IPropertyBag::Read()
instead of IPropertyBag::Write()
. Not really hard at all.
IXMLPropertyBag
The implemented interface, besides IPropertyBag
, contains only two methods:
[
object,
uuid(FC34FA47-86F7-4B19-88FA-43E073F29E14),
dual,
nonextensible,
helpstring("IXMLPropertyBag Interface"),
pointer_default(unique)
]
interface IXMLPropertyBag : IDispatch {
[id(1)] HRESULT SaveToFile([in] BSTR bstrFileName, [in] IUnknown* pObject);
[id(2)] HRESULT LoadFromFile([in] BSTR bstrFileName,
[in] IErrorLog* pErrorLog, [out,retval] IUnknown** ppObject);
};
The function LoadFromFile
takes not only a file name and returns an object, it also takes an interface pointer to an error log object. This log is used when the deserialization process encounters errors.
Handled VARIANTs
This implementation can handle any variant except these:
- which are by reference -
VT_BYREF
- which are arrays -
VT_ARRAY
- which cannot be transformed into strings -
BSTR
Please note that user data types, VT_RECORD
, are supported provided that type library information exists for that type.
Why use my code instead of Don Box'?
- My code is compile and go. You get a ready to use DLL, while Don Box' implementation is really just a CPP file showing "This is how it can be done".
- I believe I use less resources. Don Box' allocated a new property bag for each property which implemented
IPersistPropertyBag
. I don't do that, I use a stack where I store the XML element nodes instead, so that I can "backtrack".
Caveat Emptor
This software is far from complete and perfect. Here's a list of issues you should be aware of:
Property names cannot contain anything that would syntactically interfere with XML
Property names are now mangled so that they never interfere with the XML syntax.
Property values cannot contain anything that would syntactically interfere with XML
Maybe I should get a clue. Special XML characters are in fact escaped in text.
- Source code looks like crap in other editors than mine.
The reason is that Mr. Box uses spaces for tabbing, while I use the real McCoy. I may get around to beautify the code at some point.
- Error log is not used
Mr. Box never implemented error logging. I will most likely do it too once I find some implementation of IErrorLog
.
- XML is verbose
XML may inflate your data quite a lot, and there is little you can do about it. The benefits of storing data as XML though is that you can use other tools to manipulate it and you can edit it by hand using a text editor.
License Grant
You are granted a license to use the code for whatever purpose, if and only if the following conditions are met: You may expect no warranties from my part. If you break something, you fix it. If we meet some day in the flesh, you may buy me a beer of my choice. This is not a requirement, but it would be a very nice thing to do and I would appreciate it. If your religion prohibits you from buying any alcohol, even if not for yourself, a coke is fine or whatever well testing beverage which you find morally and ethically acceptable.
Also, remember that portions of this code is copyright Don Box.
By the way, many thanks goes to Mr. Don Box for this excellent piece of code.
Revision history
- 2002-12-02
Initial version.
- 2002-12-03
Typos, bloody typos.
- 2002-12-03
XML text is escaped automagically with MSXML and most likely ActiveDOM too. So no special considerations are needed for property values.
- 2002-12-05
The property bag now mangle the property names so that they never mess up the XML syntax. Two new interface functions added: SaveToStream
and LoadFromStream
. Useful if you keep your XML property bags in structured storages for example.