Back to Amir Israeli Homepage

[CRegistrySerialize : Serialize your data in/out Registry]

This article was contributed by Amir Israeli Amir Israeli.
Amir Israeli Web Site
Updated (August 2000)

Environment: Win 95/98 VC6 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) {
	strLastInsertedPath = lpszRegistryPath; //saving the lase path being used (active path)
	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

History

Date Posted: July 20, 2000

Updated August 25'th (Amir Israeli)

Home  |  Links  |  About