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 stringstreams 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.

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(unsignedint precision)

unsigned int precision

Used to set the number of digits that appear after the decimal point when the default implementation of TS takes a float or double. The default constructor sets this value to six digits.

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 a TCHAR string that the std::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 the MakeMessage function by first running it throught the TS 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 a std::tostringstream stream in order to build a std::tstring that can be used by the MakeString function. Native C++ data types can be handled by the supplied default TS function. Users of the code can use explicit template overrides of the TS function, or write their own stream manipulators if the basic TS function does not do what they want. Any class can have it's data formatted by having it's own std::tostream & operator << defined and TS 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.

unsignedint SetPrecision(unsignedint precision)

unsigned int precision

Used to set the number of digits that appear after the decimal point when the default implementation of TS takes a float or double.

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') then MakeMessage 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.

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".

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.

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

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 and RemoveMarker 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.

If I did that then every occurance of '%%' would be converted to a single '%', That is not what I wanted. I only wanted the conversion from a double % to single % to take place when dealing with potential insertion markers.

ie '%%1' becomes '%1' in the output, while '%%bob' would stay '%%bob'

And the code does handle '%%%1' correctly, the output is '%%1' which is what I wanted.

Yes that's exactly what i've been thinking about. Similar to the way vou're dealing with the backslash in context with escape sequences. I guess one has to decide which of the two possibilities is preferable depending on the application.By the way, i was just looking for a solution to replace sprintf for building xpath-querystrings, so you're CFormat class is really handy to do this job.Thanx!

The Boost Format library has similar functionality. It's doesn't support using strings from a resource but a simple class could bridge this gap. Here's some examples of uses of the Boost Format library: