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


Comments

  • Serializing UNICODE CString's fails

    Posted by Legacy on 12/19/2001 12:00am

    Originally posted by: Andreas Busse

    Hi there!
    
    

    Serializing UNICODE-CString's with your code doesn't work.
    The reason is to be found in using just lstrlen, which
    returns only the length of an LPCTSTR counted in TCHAR's.

    By definition you have to pass the size of an LPCTSTR in
    bytes to RegSetValueEx (including the terminating NULL
    TCHAR - which is a short 0 in UNICODE version).

    Using (::lstrlen(lpctstr)+1) * sizeof(TCHAR) removes this
    problem.

    Bye,
    Andreas

    Reply
  • excellent work!

    Posted by Legacy on 03/22/2001 12:00am

    Originally posted by: Jamie Band

    excellent work! I have already used your registry classes extensively and now they are even more useful. I'll let you know if I experience any issues... Thanks alot. Jamie

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • 10 Rules that Make or Break Enterprise App Development Projects In today's app-driven world, application development is a top priority. Even so, 68% of enterprise application delivery projects fail. Designing and building applications that pay for themselves and adapt to future needs is incredibly difficult. Executing one successful project is lucky, but making it a repeatable process and strategic advantage? That's where the money is. With help from our most experienced project leads and software engineers, …

  • Live Event Date: August 19, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT You deployed your app with the Bluemix PaaS and it's gaining some serious traction, so it's time to make some tweaks. Did you design your application in a way that it can scale in the cloud? Were you even thinking about the cloud when you built the app? If not, chances are your app is going to break. Check out this upcoming webcast to learn various techniques for designing applications that will scale successfully in Bluemix, for the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds