MakeMessage - An STL 'replacement' for the FormatMessage API
A function that uses STL strings and streams in an effort to mimic and improve on the FormatMessage API.
Introduction
In an app I am working on, I had placed all my UI strings in an ini file. I had done this to make it easy for anyone to translate the app, all they would need is a simple text editor like Notepad. What I needed now was a simple way of swapping out the variable place holders with the program generated text. I had looked at the FormatMessage
API but I found it rather limited. It would only take strings or integers as its input. Anything else would have to be converted to strings before it could be passed to FormatMessage
. Also, if the person who rewrites the formatting strings mismatches the number or type of parameters, the FormatMessage
function could crash with an access violation. Not a very nice situation to try and debug if you can not remember the correct format.
So I came up with this solution. It is an STL based function that returns a std::string
and uses stringstream
s to input the variables. Because it uses streams for input, it can take as its parameters any of the standard C++ data types as well as any class that you may define that has an std::ostream
insertion operator defined for it. This makes this function much more versatile than FormatMessage
, in my humble opinion. That being said, this function is not intended to replace FormatMessage
but to compliment it.
A note about tstl.h
This code makes use of the file tstl.h which can be found here[^]. The tstl.h file allows this code to use the STL string
and stringstream
classes, and be compiled in either ANSI or UNICODE builds with no errors.
The MakeMessage function
The MakeMessage
function has two helper functions: TS
and SetPrecision
. Because these functions are tied together, I debated about whether to wrap them up as members in a class or leave them in the global namespace. In the end, I decided to wrap them up as functions inside a class.
class CFormat { private: unsigned int precision; std::map<TCHAR, std::tstring> markermap; std::tstring _make(std::tstring FormatString, unsigned int ParameterCount, va_list valist); public: CFormat(); CFormat(unsigned int Precision); unsigned int SetPrecision(unsigned int p); std::tstring AddMarker(TCHAR Marker, std::tstring ReplaceWith); std::tstring RemoveMarker(TCHAR Marker); #ifdef _WINDOWS_ // The file windows.h declares the HMODULE type std::tstring MakeMessage(HMODULE hModule, unsigned long FormatStringID, unsigned int ParameterCount = 0, ...); #endif // _WINDOWS_ std::tstring MakeMessage(std::tstring FormatString, unsigned int ParameterCount = 0, ...); // default implementation of the TS function // convert any type to a string using the inserter << operator template <class T> const std::tstring TS (const T &t) { std::tostringstream s; s.setf(std::tios::fixed | std::tios::boolalpha); s.precision(precision); s << t; return s.str(); } };
The private _make
function is where the actual work is done, and is called by the overridden MakeMessage
functions. The variable precision
is used to set the default number of digits after the decimal point when floating point numbers are entered into the TS
function. AddMarker
and RemoveMarker
are used to manage custom insertion markers.
Parameter Information
CFormat() CFormat(unsigned int precision)
unsigned int precision
Used to set the number of digits that appear after the decimal point when the default implementation of
TS
takes afloat
ordouble
. The default constructor sets this value to six digits.- Returns
There is no return value.
std::tstring MakeMessage(HMODULE hModule, unsigned long FormatStringID, unsigned int ParameterCount = 0, ...) std::tstring MakeMessage(std::tstring FormatString, unsigned int ParameterCount = 0, ...)
HMODULE hModule
Module handle of the module that contains the string table resource of the string that is to be used as the format string. Pass
NULL
to use the handle to the file used to create the calling process.unsigned long FormatStringID
The identifier of the format string to be loaded from the string table.
std::tstring FormatString
The format string. It can be either a
std::tstring
or a pointer to aTCHAR
string that thestd::tstring
constructor can convert.unsigned int ParameterCount
This is an optional parameter that is used to specify the number of optional
std::tstring
parameters that follow. If this parameter is not specified it defaults to zero. The maximum value for this parameter is nine. If you use a value greater then nine you will get an assertion when running in debug mode, and in release mode the value will default to nine....
A variable number of
std::tstring
parameters. You can pass any type of variable to theMakeMessage
function by first running it throught theTS
function. See the example code below.- Returns
MakeMessage returns the newly formatted string.
template<class T> const std::tstring TS(T &t)
template <class T> t
The parameter
t
can be of any type.TS
uses the stream insertion operator<<
to enter this parameter into astd::tostringstream
stream in order to build astd::tstring
that can be used by theMakeString
function. Native C++ data types can be handled by the supplied defaultTS
function. Users of the code can use explicit template overrides of theTS
function, or write their own stream manipulators if the basicTS
function does not do what they want. Any class can have it's data formatted by having it's ownstd::tostream & operator <<
defined andTS
will use that operator. The possibilities are endless, see the examples below for some possibilities.- Returns
A
tstring
containing the supplied parameter in text format.
unsigned int SetPrecision(unsigned int precision)
unsigned int precision
Used to set the number of digits that appear after the decimal point when the default implementation of
TS
takes afloat
ordouble
.- Returns
SetPrecision returns the previous value.
std::tstring AddMarker(TCHAR Marker, std::tstring ReplaceWith)
TCHAR Marker
The character that is used together with a preceding percentage sign to mark the location in the formatting string where the
ReplaceWith
text is to be inserted. ie. If this parameter is _T('A') thenMakeMessage
will look for and understand the insertion marker '%A' in the formatting string. You can use any character for this parameter except the percentage sign (%), the lower case letters 'n' and 't', and the digits 1, 2, 3, 4, 5, 6, 7, 8, and 9.std::tstring ReplaceWith
This is the string that gets inserted into the final formatted string in place of the insertion marker specified by the
Marker
parameter.- Returns
If successful
AddMarker
returns theReplaceWith
string.AddMarker
returns an empty string on failure
std::tstring RemoveMarker(TCHAR Marker)
TCHAR Marker
A character that was previous set as an insertion marker by the
AddMarker
function.MakeMessage
will no longer recognize this insertion marker after it has been removed.- Returns
If successful
RemoveMarker
returns the string that was previously to be inserted in place of this marker.RemoveMarker
returns an empty string on failure.
The formatting string
The format string is similar to that which is used by the FormatMessage
message function in that it uses a percentage sign followed by a number as a parameter insertion marker. '%1' would be an insertion marker that marks the point where the first variable argument parameter is inserted into the format string, '%2' marks the second, and so on. There is however a limit of nine parameter insertion markers: %1, %2, %3, %4, %5, %6, %7, %8, and %9.
There are also three other special markers, one is the newline marker '%n' that used to insert a carriage return / line feed pair into the string. Another is horizontal tab marker '%t' that is used to insert a tab (ASCII 9) character into the string. And the third is the percent sign '%%' that is used to have a single percent sign in the final output. Any single percentage signs in the formatting string will be combined with character that immediately follows it and be interpreted as an insertion marker. If the insertion marker is unknown then it will be dropped from the final string. The printf
style formatting that is supported by FormatMessage
is not supported by MakeMessage
.
For example if the first variable parameter is the number 25, and the format string is "%1 = %1
" and the final text will be "25 = 25
", but if the format string is "%%1 = %1
" the final text will be " %1 = 25
".
Examples
I will start with a simple example, the output of an integer number.
std::tstring FormatString = "The number is %1"; int Number = 15; CFormat cf; std::tcout << cf.MakeString(FormatString, 1, cf.TS(Number));
The output of this piece of code will be: "The number is 15
"
To output a real number is not much different. You just have to set the number of digits you want after the decimal point using the SetPrecision
function. If you do not set the precision it will default to six digits after the decimal point.
std::tstring FormatString = "The number is %1"; double Number = 15.536; CFormat cf; cf.SetPrecision(2); std::tcout << cf.MakeString(FormatString, 1, cf.TS(Number));
The output of this piece of code will be: "The number is 15.54
"
If the default implementation of TS
does not do what you want for a specific data type, you can create an explicit override either in the CFormat
class or in a class derived from CFormat
.
You could also apply special formatting to the output by creating a custom modifier. A custom modifier would be a class that takes your data type as an input into it's constructor and has an tostream &operator <<
stream insertion operator defined
// declare a custom modifier class class Hex { unsigned int _h; public: Hex(unsigned int h) { _h = h; } friend std::tostream &operator << (std::tostream &, const Hex &) }; std::tostream &operator << (std::tostream &s, const Hex &h) { s.unsetf(std::tios::dec); s.setf(std::tios::hex | std::tios::uppercase); s << _T("0x") << std::setfill(_T('0')) << std::setw(8) << h._h; return s; } // use the modifier std::tstring FormatString = "The Number is %1"; unsigned int number = 15; CFormat cf; std::tcout << cf.MakeMessage(FormatString, 1, cf.TS( Hex(number) ));
The output of this piece of code will be: "The Number is 0x0000000F
"
Be sure to check out the demo applications as they contain all the examples listed here.
Conclusion
The two main advantages of the MakeMessage
function are that it uses the STL string and stream classes for formatting the data and that the programmer sets the number and type of parameters. Using the STL classes makes it very simple for any data type to be properly formatted into the final message string as all the type or class needs is an ostream
insertion operator defined.
Having the number and type of parameters set by the formatting string is prone to errors. If the formatting string mismatches the data type or calls for more parameters than the programmer passes to the FormatMessage
function, FormatMessage
will crash with an access violation. MakeMessage
is immune to this type of problem.
History
- Feb 24, 2006
Released into the wild
- March 4, 2006
Because of a query from steff2003 I revised how the
MakeMessage
function handled the insertion of the percent sign. It now works the same way string literals handle escape characters. A double percent sign is now needed in order to have a single percent sign in the final string.As a result of the changes made I was able to add the
AddMarker
andRemoveMarker
functions, and I was able to add the '%t' insertion marker. I was also able to stream line how the_make
function works, it now does all it's work in a single pass instead of the multiple passes it took previously.