CRegistry : Serialize Data In/Out of the Registry

Environment: Windows 95/98, Visual C++ 6 SP4

Persisting your data requires a true effort : a full blown application
that persists (even just) 20 values of any types (strings , ints …)
,with Installation and uninstallation capabilities requires a considerable
amountof effort (and a hundred lines at least) . I have already introduced a class
“CRegistry” (also within the source and project but not described) see my site
Web Site
that wrapps the standard API’s to ease your effort and let you focus in your main work.
Although using this class was easy : to get/set values etc.
persisting a huge number of values within multiple keys and places
requires a different approach than working key after key.
This object was build in mind of mimic the Serialize support found in all
classes derived from CObject , use it as you Serialize your data
in CDocument::Serialize .

The project include several side-objects :

* A subclassed object for multiple-line EditCtrls

* An extension to CMessage class (a function that does the oposite of printf functions:
wsprintf , sprintf … : formats a string back to components)(I couldn’t find such function …)

* CMemFileEx -memory mapped file extending CMemFile (to work with resources)

* DataTips object : very generic (needs many improvements)

* CRegistry class already presented in my site.

You find description of each one of them within the source code..


The provided class is made of 3 classes :

* The main object ‘CRegistrySerialize’ through which you make all your calls.
such as Install (Un), Load ,Save … (and some other functions)

2 protected classes:

* ‘CSerializedKeyPath’ that represent a path of a key : holds a pull path of a key
and a flag that determines whether to delete this key during uninstallation .
(Maybe you would like to leave it there ??? some sofware do that … not very polite …)

* ‘CSerializedValue’ represent a single value : saves its’ name:
(value name) type of data (theres an internal enumeration type you can
use , instead you can use string representations of standard types (“int”, “DWORD” …)

How It Works

CRegistrySerialize class builds a data structure that represents
a description of all the keys and values that take part in the game …
You build this data structure by inseting paths of keys , and data
associated with them (whether to delete during uninstall :
Whenever you are inserting path to the map , it becomes the active path and
whenever you insert values , they be inserted under that path. (but still not installed)
(the main object stores each key path in a CSerializedKeyPath (array of CSerializedKeyPath* in the main
object) and each value in an array within the proper CSerializedKeyPath.

// functions to be used when building the map 'bDelete' means  
// exactly as bDeleteOnUnInstall
BOOL InsertPath( LPCTSTR lpszRegistryPath, BOOL bDelete = TRUE);

BOOL InsertValue( LPCTSTR lpszRegistryPath,
                  LPCTSTR lpszValueName,
                  REGDATA iDataType = REGSTRING ,
                  BOOL bDeleteOnUnInstall = TRUE);

//If you find using enumeration hard use bare strings of types
//supported types are those supported in REGDATA
BOOL InsertValue( LPCTSTR lpszRegistryPath ,LPCTSTR lpszValueName,
		LPCTSTR lpszDataType = _T("CString"),
		BOOL bDeleteOnUnInstall = TRUE);

When you call install (un) or any other action : each entry in every array is
checked and action performed for example: Install( BOOL)
if BOOL == FALSE then UnInstall is performed (for those of you) who
want explicitely call UnInstall , I supplied such function (does very little calls : Install(FALSE); )

BOOL CRegistrySerialize::Install( BOOL bInstall)
{
 int nCount = keys.GetSize();

 BOOL bResult = TRUE;
 for (int i=0 ; i< nCount ;i++)
 {
  CSerializedKeyPath *pPath = keys.GetAt(i);

  if ((pPath) && (!pPath->Install(bInstall)))
   bResult = FALSE;//some problem occured
 }

 return bResult;
}

//Here are parts of a function in CSerializedKeyPath protected object
// TRUE = Install  FALSE = UnInstall
BOOL CSerializedKeyPath::Install( BOOL bInstall)
{
 if (strPath.IsEmpty())
  return FALSE;

 if (bInstall) {
  if (!pReg->CreateKey(strPath))
   return FALSE;

  //creating the values
  int nCount = values.GetSize();

  //...

  return TRUE;
 }

 // here we remove

 //if we're here delete value
 if (bDeleteOnUnInstall)
  return pReg->DeleteKey(strPath);
 else { //uninstalling only values
  //deleting the values : but not the key

 //...

 return TRUE;
}

How It Works

You need to create 2 functions in Your Application :
one function where you build a structure that describes your map
This is where you insert paths and values (If you choose to do it programmaticly , but
you can also load a map from a resource , or load an external file)
A good place for this function to be called is from the WinMain
(before reaching the message loop) or within the InitInstance (When you register
other objects , Shell types …).

Since provided app is very humble I inserted it in the end of the
handler of WM_INITDIALOG : (persistObj is a member variable (of type CRegistrySerialize)
of that dialog;
As you see I insert path to the map , whether I should be deleted (during uninstall .. -you remember)
and then I call ‘InsertValue ‘ with value names , types , and BOOL (if delete in uninstall)
(of course if you delete a keys there’s no meaning to FALSE in its’ values)

persistObj.InsertPath(_T("HKEY_CURRENT_USER\\Software\\Amir Israeli"
 "\\Registry Persistence"),FALSE);

persistObj.InsertValue( NULL, _T(""), _T("CString") );//default value

persistObj.InsertValue( NULL, _T("Name"), _T("CString") );

persistObj.InsertValue( NULL, _T("Age"), _T("int"));//default value

persistObj.InsertPath(_T("HKEY_CURRENT_USER\\Software\\Amir Israeli"
 "\\Registry Persistence\\Some SubKey"),FALSE);

persistObj.InsertValue( NULL, _T("rect"), _T("CRect") );

persistObj.InsertValue( NULL, _T("point"), _T("CPoint") );

persistObj.InsertPath(_T("HKEY_CURRENT_USER\\Software\\Amir Israeli"
 "\\Registry Persistence\\Some SubKey\\Subkey2Delete"),TRUE);

persistObj.InsertPath(_T("HKEY_CURRENT_USER\\Software\\Amir Israeli"
 "\\Registry Persistence\\Some SubKey\\Another SubKey"),TRUE);

You may save your map to external file now , and then insert it to your project
(It takes some space : this map “weights” 390 bytes.

How to Persist your Data(Load and Save)

Persisting data is very similar to CDocument::Serialize . Instead of CArchive&
you send BOOL (an internal function accepts a boolean .

I defined a Function name ‘PersistObject(BOOL bLoading)’ to do the persistence of
data in and out of registry :

void CPersistDlg::PersistObject(BOOL bLoad) //TRUE= loading
{
 // coping the whole section from WM_INITDIALOG function handler
 // and modifying the Insert... functions (used in Installation) to
 // Path/Value

 //each path marks the begining on a new search path for values
 //the value names that come after each path are under that path
 persistObj.Path( _T("HKEY_CURRENT_USER\\Software\\Amir Israeli"
                 "\\Registry Persistence"));

 persistObj.Value( _T("") , &m_strDefault ,bLoad); //default value
 persistObj.Value( _T("Name"), &m_strName ,bLoad);
 persistObj.Value( _T("Age"), &m_iAge ,bLoad); //default value

 //setting a new path
 persistObj.Path(_T("HKEY_CURRENT_USER\\Software\\Amir Israeli"
  "\\Registry Persistence\\Some SubKey"));

 //such values are a bit problematic (need to transform from CString 2 
 // that type and oposite) (problem : there's no ddx-action between the 
 // member variable attached to edit-ctrl to the variable that the data 
 // entered inside is going to be (MFC has built in ddx 
 //conversions from edit-ctrls to int's strings DWORD's time_t ... 
 //(but not for rects or points which are demonstrated
 CRect tmpRect;
 CPoint tmpPoint;
 if (bLoad) { //in load
  persistObj.Value( _T("rect"), &tmpRect  ,bLoad);
  persistObj.Value( _T("point"), &tmpPoint  ,bLoad);
  m_strPoint.Format("%d %d",tmpPoint.x,tmpPoint.y);

  m_strRect.Format("%d %d %d %d",tmpRect.left,tmpRect.top,
  tmpRect.right,tmpRect.bottom);
 }
 else { //in save (look into the function)

  //converting string to rect and point
  persistObj.FormatElements( m_strRect, _T("%d%d%d%d"),&tmpRect.left,
  &tmpRect.top,&(tmpRect.right),&(tmpRect.bottom));

  persistObj.FormatElements( m_strPoint, _T("%d%d"),&tmpPoint.x,&tmpPoint.y);

  //saving those converted rect and point
  persistObj.Value( _T("rect"), &tmpRect  ,bLoad);
  persistObj.Value( _T("point"), &tmpPoint  ,bLoad);
 }

 //these converions are done since I convert a string(member of dialog)
 //to required variables (look at source how it's done)

 //values that are not contain values are not needed to persist
 //	persistObj.Path(_T("HKEY_CURRENT_USER\\Software\\Amir Israeli\\"
 //  "Registry Persistence\\Some SubKey\\Subkey2Delete"), bLoad);

 //	persistObj.Path(_T("HKEY_CURRENT_USER\\Software\\Amir Israeli\\"
 //  "Registry Persistence\\Some SubKey\\Another SubKey"), bLoad);
}

Notice: ‘FormatElements’ isnt related directly to persistence
but to formating the elements in the editctrl , so the be suitable to persist
to a very different types : RECT’s and POINT’s here.

A brief view at the code of reseting a new path :

//during loading or saving
BOOL CRegistrySerialize::Path( LPCTSTR lpszRegistryPath) {
 //saving the lase path being used (active path)
 strLastInsertedPath = lpszRegistryPath;
 return TRUE;
}

//using a pointer in both cases (needed in caswe of loading)
BOOL CRegistrySerialize::Value( LPCTSTR lpszValueName,
                                CString *pstrDest,
                                BOOL bLoading ) {
 CSerializedValue *pValue = GetValueObject(lpszValueName);
 if (!pValue)  return FALSE;
 return pValue->Persist( pstrDest,bLoading);
}
BOOL CRegistrySerialize::Value( LPCTSTR lpszValueName,
                                DWORD *dwDest,
                                BOOL bLoading ) {
 CSerializedValue *pValue = GetValueObject(lpszValueName);
 if (!pValue)  return FALSE;
 return pValue->Persist( dwDest, bLoading);
}

//...

//A look into persist function (overloaded functions)
BOOL CSerializedValue::Persist( CString *pstrDest , BOOL bLoading ) {
 if (iDataType == REGSTRING) { //persist a string
 CString path = pathOwner->GetPath();
 return (bLoading) ? pReg->GetValue( path , strValueName , pstrDest)
  : pReg->SetValue( path , strValueName , (LPCTSTR) *pstrDest);
 }
 return FALSE;
}

BOOL CSerializedValue::Persist( DWORD *dwDest , BOOL bLoading ) {
 if (iDataType == REGDWORD) { //persist DWORD
  CString path = pathOwner->GetPath();
  return (bLoading) ? pReg->GetValue( path, strValueName, dwDest)
   : pReg->SetValue( path, strValueName, dwDest);
 }
 return FALSE;
}

//A low level for binary types : you specify the size of buffer
BOOL CSerializedValue::Persist( LPBYTE *lpByte,
                                DWORD cbBuffLen,
                                BOOL bLoading) {
 if (iDataType == REGPBYTE) {
  CString path = pathOwner->GetPath();
  return (bLoading) ? pReg->GetValue( path, strValueName, *lpByte, cbBuffLen)
   : pReg->SetValue( path, strValueName, *lpByte, cbBuffLen);
 }
 return FALSE;
}

Some other issues :
(a) Whenever a new map is loaded the previous map is destroyed.

CRegistrySerialize defines several more functions:

LoadFromResource : used to load a map into memory from a resource
(very similar function to that of CHtmlView)

FormatElements (look in source of CMessage and in description of CRegistry class)

Please inform me whats your impressions and what other features
will be helpfull.

Downloads

Download demo project – 106 Kb

Download source – 19 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read