This tiny little tool is a small C++ utility that makes creating and accessing application configuration variables a breeze. In most cases, storing these values in the registry may be more efficient, but for this particular project, the configuration files had to be generated off-site and uploaded onto a remote machine running the application.
During development, each time we needed a new variable, we had to generate the code to declare it, initialize it, save it, and access it. In other words, there was a routine to the whole process which, if a step was left out, could produce unnecessary debugging work.
Wouldn’t it be great if all that I needed to do to generate a new variable was write a single line of code?
E.G.:
//…somewhere in the project
// ("One –line-of-code")DECL_APPVAR_GET(const char*,
// MyVar, "I have no imagination.")
// …elsewhere in the project
void func()
{
MyVar var;
// compiler error – read-only (GET)
var = "Wait a minute… I felt something.";
// compiler error – wrong type
BOOL eg = var;
// OK to read const char*
AfxMessageBox(var);
}
I began by looking at the syntax that we would like to use for accessing the variable. I saw two possibilities:
CAppVar::MyVar var; // or
CAppVar<MyVar> var;
At first, I thought the second syntax looked the most appealing. However, because it’s a template, serialization would be a little more difficult. I wanted to keep the solution as simple as possible, so I opted for the first syntax.
Next came the “one-line-of-code” macro that would generate the bulky output, which I wanted to avoid.
Obviously, this is going to need some thinking. In terms of C++, thinking comes in classes so we need:
Our “one-line-of-code” macro will generate most of this code for us and distribute the information accordingly.
Warning: Some programmers believe that this style of macro-coding is hard to read and maintain. While I don’t completely disagree, I do believe that if you understand the underlying code and why it is implemented using macros, then there is no reason not to use them in this fashion. Macros were designed just for that and hence named “MACROS”, not “DEFINES” ;)
Let's start by looking at the wrapper class and what kind of information we can get to and from it:
The wrapper class is called var_info and maintains information about a particular variable. This class mimics a template class, where the data can be accessed using two methods:
var_info
Simple
String
The reason this class is not a template is briefly mentioned above and can be summarized as a slight increase in the complexity of RAM data storage for a much reduced complexity in streaming and disk storage.
The class’ data members are as follows:
class var_info : public CObject
{
DECLARE_SERIAL(var_info)
friend class CAppVar;
protected:
// used for lookups
CString name;
// just for show
CString type; // this is only used for offsite reading
BOOL write; // this is only used for offsite reading
// real info and data
int size;
void* data;
Currently, any lookups are done by name. This is not the most efficient way, but for this example, I did not want to have to provide another piece of information (key) to the declaration macro. Feel free to change this, if necessary. Also, the list is implemented using CObList. Don't laugh, this is left-over from thinking I will be requiring its serialization services. It will be removed.
CObList
The class takes advantage of MFC’s serialization techniques (future projects include a port to WTL – when required).
Access to the data is managed by the following members and operators:
// operators
inline operator int()
{ return _simple(int, data); }
inline operator const char*()
{ return _string(const char*, data); }
inline void operator=(UINT d)
{ copy((void*)&d, sizeof(UINT)); }
inline void operator=(int d)
{ copy((void*)&d, sizeof(int)); }
void operator=(const char* d)
{ data = (d) ? copy((void*)d, strlen(d)+1):NULL; size = (data) ? size:0; }
…
// general access
inline const char* GetName()
{ return name; }
inline const char* GetType()
{ return type; }
inline int GetSize()
{ return size; }
inline BOOL AllowWrite()
{ return write; }
…
// serialization
void Serialize(CArchive& ar)
{
if(ar.IsStoring())
{
ar << name << type << write << size;
ar.Write(data, size);
}
else
{
ar >> type >> write >> size;
del(data);
data = alc(size);
ar.Read(data, size);
}
}
…
// helper
CString ToString();
// generates a string rep. for the value (needs a better approach)
void ToValue(const char* val);
// as above but opposite direction.
protected:
void* copy(void* d, int s)
{
if(s > size)
{ del(data); data = alc(s); }
// del() == free() && alc() == malloc()
memcpy(data, d, s);
size = s;
return data;
}
The class is fairly straight forward with minimal functionality. Normally, no one accesses this class directly. Any access is managed through the Accessor classes which would in turn access this class according to the access rights specified for that variable. In my example, I have made access to this class available to any one who needs it (an example would be enumerating all the available application variables, as opposed to specific variable access).
Next was the container class:
The container class acts as a wrapper for all the application variables. It maintains an internal static list of var_info structures and is able to match these variables with streaming data. It also serves as both a namespace for our Accessor classes and as the invoking mechanism for the creation and initialization of the application variables. In other words, by creating an instance of this object, our data becomes immediately available. Note: only one instance per application, please.
For this to be achieved, any new application variable must have its “one-line-of-code” added to the body of this class. Here is its declaration for two variables:
class CAppVar : public CObject
{
// {{ Global Config Variables
DECL_APPVAR_SETGET(BOOL, MyVar1, FALSE)
DECL_APPVAR_GET(const char*, MyVar2, _T("I have no imagination."))
// }}
public:
CAppVar() {}
virtual ~CAppVar();
DECLARE_SERIAL(CAppVar)
void Serialize(CArchive& ar);
public:
static CObList m_variableList;
static var_info* Fetch(const char* name, bool assert = true);
};
This particular example has two config variables:
MyVar1
MyVar2
The application is responsible for calling the serialization function when needed. All the variables are stored in the m_variableList member upon the container's creation.
m_variableList
Warning: You cannot enter the application with the CAppVar object mapped onto the top stack frame, i.e., it cannot be a direct member of the application object or be declared as a global or static object. In such instances, heap allocation must be used (i.e., a pointer and the new operator). This is due to the order of construction between the static list and the Initializer members.
CAppVar
new
The other two classes, Accessor and Initializer, are generated by the macros. For the above example, the code would expand to reveal the following CAppVar class (with the exception of some possible copying mistakes). The bold-italic items represent the macro parameters.
As you can see from the above, the concept is quite simple. There is nothing to it but wrapping a type declaration around the var_info object with some default parameters and operators. The read-only declaration is missing the assignment operator and will therefore generate a compile error if any attempt was made.
The Accessor is just a public type declaration with the same name as the config variable, while the Initializer class is a member declaration whose constructor is invoked upon the creation of the CAppVar object. This constructor creates a new var_info object with the information provided in the macro and appends it to the CAppVar object's internal list. In my example, no one can access this variable or its type definition – as there is no requirement to.
The Initializer object contains a dummy variable, in case the compiler attempts to optimize the code.
I hope you can see how you are able to access the variable's contents using local declarations of the variable in question. I.e.:
CAppVar::MyVar1 var;
var = TRUE; // This will set the config variable MyVar1 to TRUE
const char* szTemp = var; // This will generate a compile error
//…
The provided source code includes an example where the same mechanism was used to create a simple editor for these variables. There is a function called OnExampleCode() which has no entry point but illustrates accessor class usage. I have tried to keep the code as simple as possible without embedding too much in MFC.
OnExampleCode()
This code and example are minimalist implementations. They were created as proto-type code and nothing more. It is not tested extensively and is intended as a 'blank sheet'.
The example allows you to change the values of config variables, save them, or open them - try it and let me know of the problems you encounter.
It’s a relatively simple solution that has no real value other than the time it may save in the creation of new application variables (which can be serialized), and perhaps, as in my case, debugging time from errors in alternate code. It helped us dramatically with both. The only problem is that there usually needs to be a complete re-compilation with each new set of variables added – so plan ahead.
There is probably also a solution like this already out there. I did not see any others nor heard of any, so if there is, then I’m sure all of us - even the most generically inclined – have, at some point, re-invented something.
Also, for my solution, I will be adding an encryption layer before serialization. I may also port this to read registry keys as well, or add some functionality to declare a variable as registry or file.
Written By Alon Azar.
... [F5]
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
#pragma init_seg(lib)
General News Suggestion Question Bug Answer Joke Praise Rant Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.