Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Replacing the CSettingsStore Class in MFC to Store Settings in a File

0.00/5 (No votes)
9 Feb 2009 1  
Replacing the CSettingsStore class in MFC to store settings in a file

Introduction 

The default MFC CSettingsStore class stores all settings (including UI customization) in the registry. It is also very read- and write-happy, leading to an amazing amount of traffic to the registry, and a large amount of data stored.

This contribution (written by me and placed in the public domain) allows you to replace that behavior with a class that stores settings in a single file in the user's Application Data folder, and keeps the settings in memory for optimal reading speed.

Using the Code

To activate it, just add the two files to your project and add the following line to
your CMyApplication::InitInstance() function:

#include "LocalSettingsStore.h"

BOOL CMyApplication::InitInstance()
{
  //  store settings in a file in local appdata, not in the registry
  CSettingsStoreSP::SetRuntimeClass(RUNTIME_CLASS(CLocalSettingsStore));		

Also, you need to change the APPLICATION_NAME define in the file LocalSettingsStore.cpp
and remove the line that says #undef APPLICATION_NAME, to store the settings in a file
that's specific to your application.

The code has been tested with MFC 9.0 and Visual Studio 2008 SP1, but ought to work with earlier/later versions that use the same basic CSettingsStore class.

How Does it Work?  

MFC uses a wrapper for accessing the registry, called the CSettingsStore. In your application, you use the CSettingsStoreSP class to actually access this wrapper, and then you get/set your preference items (window position, preferred color, etc.). The internal framework uses this a lot to store and read settings for toolbars, menu customization, etc. In fact, about 20 kilobytes of registry data goes into a single do-nothing MFC application!

There are a large number of virtual functions in this class, allowing you to override the way that storage is done. Unfortunately, there is no documentation for how to actually do this, so I went spelunking inside the source code that ships with Microsoft Visual C++ 2008 SP1.

virtual BOOL CreateKey(LPCTSTR lpszPath);
virtual BOOL Open(LPCTSTR lpszPath);
virtual void Close();

virtual BOOL DeleteValue(LPCTSTR lpszValue);
virtual BOOL DeleteKey(LPCTSTR lpszPath, BOOL bAdmin = FALSE);

virtual BOOL Write(LPCTSTR lpszValueName, int nValue);
virtual BOOL Write(LPCTSTR lpszValueName, DWORD dwVal);
virtual BOOL Write(LPCTSTR lpszValueName, LPCTSTR lpszVal);
virtual BOOL Write(LPCTSTR lpszValueName, const CRect& rect);
virtual BOOL Write(LPCTSTR lpszValueName, LPBYTE pData, UINT nBytes);
virtual BOOL Write(LPCTSTR lpszValueName, CObject& obj);
virtual BOOL Write(LPCTSTR lpszValueName, CObject* pObj);

virtual BOOL Read(LPCTSTR lpszValueName, int& nValue);
virtual BOOL Read(LPCTSTR lpszValueName, DWORD& dwValue);
virtual BOOL Read(LPCTSTR lpszValueName, CString& strValue);
virtual BOOL Read(LPCTSTR lpszValueName, CRect& rect);
virtual BOOL Read(LPCTSTR lpszValueName, BYTE** ppData, UINT* pcbData);
virtual BOOL Read(LPCTSTR lpszValueName, CObject& obj);
virtual BOOL Read(LPCTSTR lpszValueName, CObject*& pObj);

Where do you start? Well, it turns out that the only functions you need to override are the DWORD, LPCTSTR and BYTE/UINT read/write functions, plus the create/delete management functions:  

virtual BOOL CreateKey(LPCTSTR lpszPath);
virtual BOOL Open(LPCTSTR lpszPath);
virtual void Close();
virtual BOOL DeleteValue(LPCTSTR lpszValue);
virtual BOOL DeleteKey(LPCTSTR lpszPath, BOOL bAdmin = FALSE);

virtual BOOL Write(LPCTSTR lpszValueName, DWORD dwVal);
virtual BOOL Write(LPCTSTR lpszValueName, LPCTSTR lpszVal);
virtual BOOL Write(LPCTSTR lpszValueName, LPBYTE pData, UINT nBytes);

virtual BOOL Read(LPCTSTR lpszValueName, DWORD& dwValue);
virtual BOOL Read(LPCTSTR lpszValueName, CString& strValue);
virtual BOOL Read(LPCTSTR lpszValueName, BYTE** ppData, UINT* pcbData);

Internally, the CSettingsStore class will re-formulate all the other requests into one of these three requests. For example, the CSettingsStore supports marshaling of CObject objects into and out of the registry, but at the I/O level, that goes through BYTE**, UINT*.

In the implementation, everything is stored as a std::vector<char>, which I find is a convenient "resizable buffer" class. Keys are stored as std::wstring, downcased through the C locale to provide the case insensitive search that usually happens in the registry. This all goes into a std::map<std::wstring, std::vector<char>> which implements the full key/value mapping. (If you know your Win32, you'd know that registry keys have multiple named values; I implement this as a single long path without special handling. For example, the key FOO\bar\baz with the value 64 named "Bits" would be stored as foo\bar\baz\bits in the map, pointing to a vector of 4 bytes storing the DWORD 64.)

The implementation only loads the settings once, to avoid unnecessary I/O to the disk. After loading, it stays in memory until program termination, which means that performance is very good. To make sure that changes are committed to file when expected, the file is written each time the CLocalSettingsStore object is destroyed. Unfortunately, this happens a lot (the MFC implementation is very object create/destroy happy), but to optimize this case, it keeps a dirty bit, and only actually writes to disk when something has been changed.

The file format itself is fairly straightforward, with an optimization to save disk space. Because many key paths have a common prefix, a key is stored as a count of characters to use from the previous key in the file, plus a string to append to get the next key path. This simple change actually made the default settings file go from about 30 kB to about 20 kB!

Points of Interest

The documentation for CSettingsStore is pretty weak. It turns out that the marshaling of objects needs to use the particular format used by the base implementation, or it won't work right. Also, it turns out that you should only override three of the virtual functions for writing data, not all of them -- see the implementation for details. 

History

  • Version 1.0 -- February 4, 2009
  • Version 1.1 -- February 7, 2009

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here