Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

FormatMessage LoadString from string table

167 views
Skip to first unread message

mfc

unread,
Sep 13, 2010, 9:35:47 AM9/13/10
to
Hi,

do you know if there`s a simple / fast solution to add several strings
from the string-table to one CString using FormatMessage?

CString test ("%1%, %2% some other comments %3% and %4%");
CString test2("");
for(int i=0; i<MAX_NBR_OF_STR; i++)
{
test2.LoadString(IDS_TEST_START + i);

test.FormatMessage(test2);
}
I know this solution will not work - but maybe it is possible with a
few hints.

I don`t want to use 40 CStrings test2[1-40] to load all strings from
the string-table and add them by using FormatMessage.

best regards
Hans

Joseph M. Newcomer

unread,
Sep 13, 2010, 11:43:45 AM9/13/10
to
See below...

On Mon, 13 Sep 2010 06:35:47 -0700 (PDT), mfc <mfc...@googlemail.com> wrote:

>Hi,
>
>do you know if there`s a simple / fast solution to add several strings
>from the string-table to one CString using FormatMessage?
>
>CString test ("%1%, %2% some other comments %3% and %4%");

****
I have no idea what you are doing initializing this string, since normally you would load
it from the STRINGTABLE. And why are you using obsolete 8-bit characters?
****
>CString test2("");
***
Yout do not need the "" (more correctly, _T("")) in the constructor because all strings
are created with the empty string. In any case, since you are going to replace its
contents, why in the world would you need to initialize it?

>for(int i=0; i<MAX_NBR_OF_STR; i++)
>{
> test2.LoadString(IDS_TEST_START + i);
>
> test.FormatMessage(test2);
>}
>I know this solution will not work - but maybe it is possible with a
>few hints.

****
Of course it won't work. And you haven't even said what the problem is that you are
trying to solve!

You can't add strings using FormatMessage. You can add strings using the concatenation
operator, +., but you have not explained why you even think it makes sense to add multiple
strings fromthe STRINGTABLE? Did you perhaps mean,

"I have a string I wish to apply FormatMessage to, and I want to supply, as parameters,
multiple strings from the STRINGTABLE?"
***


>
>I don`t want to use 40 CStrings test2[1-40] to load all strings from
>the string-table and add them by using FormatMessage.

****
Why not? You have stated this as your problem. Note that the literal CString you gave
above makes no sense because it contains English text, so you would want to load it from
the STRINGTABLE also. And it only has 4 parameters. Did you mean your formatting string
has %1..%40? If so, say so.

Typically, you would do

CString fmt;
fmt.LoadString(IDS_MY_DESIRED_FORMAT);
CString s;
s.FormatMessage(fmt, p1, p2, p3, p4);

I think you might be talking about

CStringArray sa;
sa.SetSize(MAX_STRING);
for(int i = 0; i < MAX_STRING; i++)
sa[i].LoadString(IDS_TEST_START + i);

Then call raw ::FormatMessage passing sa.GetData as the string count parameter; but if so,
your formatting string MUST have %1 through %40 defined.

Note that at times you must move outside the realm of MFC and use the raw API. MFC
contains a completely uninteresting subset of the FormatMessage capabilities.

See my essay on using FormatMessage to format errors for more details. It is on my MVP
Tips site.
joe
****
>
>best regards
>Hans
Joseph M. Newcomer [MVP]
email: newc...@flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm

Joseph M. Newcomer

unread,
Sep 13, 2010, 1:54:28 PM9/13/10
to
In re-reading your question, and still trying to figure out what you are asking...

If you want to concatenate a series of strings (separated by spaces, for example) the code
is

CString sep;
CString result;
for(...)
{
CString s;
s.LoadString(IDS_STRING_START + i);
result += sep;
result += s;
sep = _T(" ");
}

Pretty basic programming here.
joe

On Mon, 13 Sep 2010 06:35:47 -0700 (PDT), mfc <mfc...@googlemail.com> wrote:

mfc

unread,
Sep 13, 2010, 3:16:33 PM9/13/10
to
thanks for your answers;

I`m loading one string, which is stored as a html-resource, including
the empty html page. After that I add the specific strings from the
string table by using FormatMessage. I think, that your solution using
the CStringArray would be the best one.

CStringArray sa;
sa.SetSize(MAX_STRING);
for(int i = 0; i < MAX_STRING; i++)
sa[i].LoadString(IDS_TEST_START + i);

I`m using the string table because I want to add some more languages
to the project (adding satelite dlls), so that the html page can
appear in different languages.

mfc

unread,
Sep 13, 2010, 3:31:18 PM9/13/10
to

by the way, do you know a suitable solution to get all the field-
values from a received post-message (http) - which looks like
Field1=Value1&Field2=Value2&Field3=Value3? Is there a similar function
like FormatMessage?

mfc

unread,
Sep 13, 2010, 3:44:32 PM9/13/10
to
> by the way, do you know a suitable solution to get all the field-
> values from a received post-message (http) - which looks like
> Field1=Value1&Field2=Value2&Field3=Value3? Is there a similar function
> like FormatMessage?

CString test("Feld1=Wert1&Feld2=Wert2&Feld3=Wert3");
CString item;
CStringList strList;
UINT start = 0;
UINT end= 0;


for(int i=0; i<NBR_OF_FIELDS; i++)
{
start= test.Find('=', start);
end = test.Find('&', end);

item.Mid(start,end-start+1);
strList.Add(item);

start = end+1;
}

Joseph M. Newcomer

unread,
Sep 13, 2010, 3:44:55 PM9/13/10
to
See below...,

****
Let Fields be a CString which has the above string in it

while(true)
{ /* parser loop */
int n = Fields.Index(_T("&"));
if(n < 0)
{ /* last one */
ParseField(Fields);
} /* last one */
else
{ /* not last one */
CString field = Fields.Left(n);
ParseField(field);
Fields = Fields.Mid(n+1);
} /* parser loop */

ParseField then searches for the =, splits the field input into two subfields, and enters
them in a structure, such as a std::map or a CMap so they can be retrieved by name.

Do not assume there is an MFC class that solves every single programming problem.
Sometimes, you just have to write code.
joe
****
>like FormatMessage?

Joseph M. Newcomer

unread,
Sep 13, 2010, 4:51:58 PM9/13/10
to
See below...

On Mon, 13 Sep 2010 12:44:32 -0700 (PDT), mfc <mfc...@googlemail.com> wrote:

>> by the way, do you know a suitable solution to get all the field-
>> values from a received post-message (http) - which looks like
>> Field1=Value1&Field2=Value2&Field3=Value3? Is there a similar function
>> like FormatMessage?
>
>CString test("Feld1=Wert1&Feld2=Wert2&Feld3=Wert3");
>CString item;
>CStringList strList;
>UINT start = 0;
>UINT end= 0;
>
>

>for(int i=0; i<NBR_OF_FIELDS; i++)

****
Wrong approach. while(true). There is no reason to assume that there is a fixed number
of fields, that the fields are in order, that all fields will be present, or more fields
may not be added.
****


>{
> start= test.Find('=', start);
> end = test.Find('&', end);

****
End is not initialized, so cannot be used here. You probably mean 'start'
****
>
> item.Mid(start,end-start+1);
> strList.Add(item);
****
this puts the field values in a list without knowing what name they are. Not what I
consider a robust approach. There is no reason to assume the field names are in any
particular order; the standard behavior does not require that they be in any particular
order. This is therefore an incorrect assumption that can cause serious failures. Ditch
it.
****
>
> start = end+1;
****
I would never have considered this as a remotely acceptable solution. The code I wrote
works no matter how many fields there are, and each field is stored as a <name, value>
pair, which is the most robust approach. To do otherwise is to take serious, and to me
unacceptable, risks. I would not deem any of the above code to be a viable solution.
joe
****

mfc

unread,
Sep 14, 2010, 8:24:19 AM9/14/10
to
> Then call raw ::FormatMessage passing sa.GetData as the string count parameter; but if so,
> your formatting string MUST have %1 through %40 defined.

CStringArray strArray; //values already included
CString str ("%1, %2, %3, %4");
str.FormatMessage(str, strArray.GetAt(0), strArray.GetAt(1)....);

but how can I obtain str.GetData() to pass the strings to CString str
by using FormatMessage()? At the moment it is working but if you have
40 items it is not very clean or readable....

Goran Pusic

unread,
Sep 14, 2010, 8:29:46 AM9/14/10
to

Note that you are tying number of %1/2/3/4 format specifiers with the
size of the array. That's IMO a bigger problem than readability.

BTW, if you purpose is to "translate" parts of the text by replacing
stuff, you might want to consider simply having separate completely
"translated" texts. You also might want to use a tool to create them.
Don't push a good idea to far, that often turns a good idea into a bad
one ;-).

Goran.

Goran.

Hans-J. Ude

unread,
Sep 14, 2010, 8:58:14 AM9/14/10
to
mfc schrieb:

>for(int i = 0; i < MAX_STRING; i++)
> sa[i].LoadString(IDS_TEST_START + i);

I would better write something like

int stringID[] = { IDS_TEST_START, ... };
for(int i = 0; i < _countof(stringID); i++)
sa[i].LoadString(stringID[i]);

That makes it easier to maintain when new trings are possibly added at
a later time and it doesn't need the IDs to be in numerical order.

Hans

mfc

unread,
Sep 14, 2010, 10:32:10 AM9/14/10
to


I think you`re right, FormatMessage is not the right purpose here.
Because it seems there is no possibility using one function where I
can add x-string-items by FormatMessage to the specific string. X-
string-items means that the number of strings adding via FormatMessage
could be different from time to time. I`ve several strings with
different number of format specifiers (%1, %2 and so on)...


void AddItemsToStr(UINT number, CStringList& strList)
{

CString str;
str.FormatMessage(str, strList.GetAt(0).......,
strList.GetAt(number));

}

Joseph M. Newcomer

unread,
Sep 14, 2010, 12:19:42 PM9/14/10
to
See below...

*****
It's called "concatenation" and is implemented in C with strcat/wcscat/_tcscat and in MFC
by CString::operator + and CString::operator +=. You seem to be taking a simple problem
and making it hard for no particular reason.
*****


>
>
>void AddItemsToStr(UINT number, CStringList& strList)
>{
>
> CString str;
> str.FormatMessage(str, strList.GetAt(0).......,
>strList.GetAt(number));

****
Why write a function, when you could have written instead a simple + operator on the
CString values?

I've already given you the code for this.
joe
****

Joseph M. Newcomer

unread,
Sep 14, 2010, 12:22:56 PM9/14/10
to
See below...

On Tue, 14 Sep 2010 14:58:14 +0200, Hans-J. Ude <ne...@s237965939.online.de> wrote:

>mfc schrieb:
>
>>for(int i = 0; i < MAX_STRING; i++)
>> sa[i].LoadString(IDS_TEST_START + i);
>
>I would better write something like
>
>int stringID[] = { IDS_TEST_START, ... };
>for(int i = 0; i < _countof(stringID); i++)
> sa[i].LoadString(stringID[i]);

****
Buy why assume there is a fixed count? What I do in such cases is reserve a range for
the strings, and just load strings until I get an empty string, and that's my
end-of-sequence indicator. I usually reserve a band of 100 strings, and go from
IDS_TEST_START to IDS_TEST_END, but an empty string aborts the loop early. This allows
for growth.

Why does everyone keep trying to make a trulty trivial problem difficult? Adding all thse
different features just adds complexity without actually contributing to the solution of
the problem.
joe

*****


>
>That makes it easier to maintain when new trings are possibly added at
>a later time and it doesn't need the IDs to be in numerical order.
>
>Hans

mfc

unread,
Sep 14, 2010, 12:41:38 PM9/14/10
to
> Why write a function, when you could have written instead a simple + operator on the
> CString values?


I`ve two CStrings which looks like

CString str3items ("here should be the first item %1, here should be
the second item %2, here should be the third item %3 and so on");
CString str2items ("here should be the first item %1, here should be
the second item %2);

CString firstItem ("first");
CString secondItem ("second");
CString thirdItem ("third");

Now I want to use FormatMessage to add these items to str2items or
str3items;

CString str could be str3items or str2items depending on some
instructions during the code....


void function()
{

if(loadFirstString)
{
str = str3items;
} else
{
str = str2items;
}

str.FormatMessage(str, add specific items);

}

Is it possible to use this FormatMessage() for each CString??? With
different %1.. format specifier?


And the second question is or was: is there a short form to add all
these items to the CString without using GetAt(0), GetAt(1), GetAt(2)
and so on? Maybe installing a while loop?

CStringList strList (contains all items);

str.FormatMessage(str, strList.GetAt(0), strList.GetAt(1),
strList.GetAt(2), strList.GetAt(3));

I hope both things were clear now...

best regards
Hans


Joseph M. Newcomer

unread,
Sep 14, 2010, 2:26:14 PM9/14/10
to
See below...

On Tue, 14 Sep 2010 09:41:38 -0700 (PDT), mfc <mfc...@googlemail.com> wrote:

>> Why write a function, when you could have written instead a simple + operator on the
>> CString values?
>
>
>I`ve two CStrings which looks like
>
>CString str3items ("here should be the first item %1, here should be
>the second item %2, here should be the third item %3 and so on");
>CString str2items ("here should be the first item %1, here should be
>the second item %2);
>
>CString firstItem ("first");
>CString secondItem ("second");
>CString thirdItem ("third");
>
>Now I want to use FormatMessage to add these items to str2items or
>str3items;

*
If you wish to be able to re-order the sequences of items using %n, then, indeed, you are
going to need some number of %i values. So make this formatting string the first string
in the sequence: IDS_TEST_FIRST. Start your loop at IDS_TEST_FIRST+1. Then your
formatting string and the strings it handles are all in the resource STRINGTABLE and easy
to maintain!

Or, if it is some kind of XML template, then it might say

<%1:%2 value="%3"
<%1:%4 name="%5"....

and so on, which is how I create the templates for my Locale Explorer. I allow the user
to specify template files. Again, this is not Rocket Science, just using the obvious
mechanisms in the obvious way. And if you need to have an array of 40 strings, well,
that's just tough, because what you want to do REQUIRES an array of 40 strings (or 42, or
47, or 51, so if if you hardwire this value you have made a very deep and fundamental
programming error)

CString DoSubstitution(const CString & template)
{
CStringArray Parameters;
for(int i = IDS_START; i < IDS_MAX; i++)
{ /* data loop */
CString s;
s.LoadString(IDS_START+i);
if(s.IsEmpty())
break;
Parameters.Add(s);
} /* data loop */
LPTSTR msg;
::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING,
...provide various parmeters (Exercise for the
Reader )
(LPTSTR)&msg,
....more parameters
Parameters.GetSize(),
Parameters.GetData());
CString result(msg);
LocalFree(msg);
return result;
}

Error checking is left as an Exercise for the Reader. I think the last two parameters are
the string count and the string array pointer, but this is an EFTR.

Note that at NO POINT does this code presume there is a fixed number of strings, just a
maximum upper bound (e.g., 100, whereas I might only be using 27 of those strings)

My string table normally looks like
IDS_TEST <...This is the start of the sequence of strings used for...>
IDS_MAX <..All strings must fit in this range>

Note I set IDS_MAX to be IDS_TEST+100, e.g., I define IDS_TEST=10000 and set IDS_MAX to be
10100, and start my loop at IDS_TEST+1, so I have actual DOCUMENTATION in my string table.

NEVER assume that because MFC doesn't have some capability you need that is possessed by
the raw API that you cannot use the raw API! Just use it!

Take a look at my FormatMessage example, or look at the numerous calls on FormatMessage in
my Locale Explorer source.
joe
*****


>
>CString str could be str3items or str2items depending on some
>instructions during the code....
>
>
>void function()
>{
>
>if(loadFirstString)
>{
> str = str3items;
>} else
>{
> str = str2items;
>}
>
>str.FormatMessage(str, add specific items);
>
>}
>
>Is it possible to use this FormatMessage() for each CString??? With
>different %1.. format specifier?
>
>
>And the second question is or was: is there a short form to add all
>these items to the CString without using GetAt(0), GetAt(1), GetAt(2)
>and so on? Maybe installing a while loop?
>
>CStringList strList (contains all items);
>
>str.FormatMessage(str, strList.GetAt(0), strList.GetAt(1),
>strList.GetAt(2), strList.GetAt(3));
>
>I hope both things were clear now...
>
>best regards
>Hans
>
>
>

mfc

unread,
Sep 14, 2010, 3:15:22 PM9/14/10
to

thanks for your help. The part with the CStringArray is totally clear
to me but the FormatMessage() call is not clear to me...

Only a small demo code;

LPTSTR msg ("%1% and %2% and %3%"); //represents the string which
should include all itmes from the CStringArray after the
FormatMessage()-call -> see out-string

LPWSTR out = NULL; //output-string which includes the msg-string
with all arguments from the CStringArray

::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_STRING,
(LPTSTR)&msg, //lpSource (in - Pointer to a
string that consists of unformatted message text)
0,
0,
(LPWSTR ) &out, //lpBuffer(out A pointer to
a buffer that receives the null-terminated string that specifies the
formatted message)
0,
(va_list *)0
};

the (va_list*) argument seems to me that it should include the
Parameter.GetData() CStringArray - but that doesn`t work...

va_list args = NULL;
va_start(args, Parameters.GetData());


also (va_list *)Parameters.GetData() in the function call
FormatMessage() won`t work.


Joseph M. Newcomer

unread,
Sep 14, 2010, 4:30:35 PM9/14/10
to
See below...

****
Did you RTFM? The parameters to ::FormatMessage seem pretty simple, and if you are
unsure, you can use parts of my code as examples. What part is not clear?
*****


>
>Only a small demo code;
>
>LPTSTR msg ("%1% and %2% and %3%"); //represents the string which
>should include all itmes from the CStringArray after the
>FormatMessage()-call -> see out-string
>
>LPWSTR out = NULL; //output-string which includes the msg-string
>with all arguments from the CStringArray

****
Why is this declared as an LPWSTR instead of an LPTSTR? If you are not using LPWSTR and
FormatMessageW, then you should stick with LPTSTR.
****
>
>::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
>FORMAT_MESSAGE_FROM_STRING,
****
| FORMAT_MESSAGE_ARGUMENT_ARRAY
because your va_list isn't really a va_list
****


> (LPTSTR)&msg, //lpSource (in - Pointer to a
>string that consists of unformatted message text)
> 0,
> 0,
> (LPWSTR ) &out, //lpBuffer(out A pointer to
>a buffer that receives the null-terminated string that specifies the
>formatted message)

****
(LPTSTR)&out, since you are not calling FormatMessageW explicitly
****
> 0,
> (va_list *)0
****
But where did you fail to understand that this is the count of the number of string
arguments and the string argument array pointer? The documentation is pretty explicit
about this, and I said that the last two parameters were the count and pointer, and even
remembered them in the right order!
*****


>};
>
>the (va_list*) argument seems to me that it should include the
>Parameter.GetData() CStringArray - but that doesn`t work...

****
Why not? Of course, you have to cast it to a (va_list *) to make it work, but so what?
RTFM. Add the FORMAT_MESSAGE_ARGUMENT_ARRAY flag, as documented.
****


>
>va_list args = NULL;
>va_start(args, Parameters.GetData());

****
No, this is not correct, because this would not create a valid va_list. RTFM.
****


>
>
>also (va_list *)Parameters.GetData() in the function call
>FormatMessage() won`t work.

****
And what does it do? There is no such thing as "doesn't work". There is an actual
manifestation of the error. Probably, you failed to include the
FORMAT_MESSAGE_ARGUMENT_ARRAY flag as the documentation states is required.

I believe CStringArray::GetData returns a pointer to an array of LPCTSTR values. But I'm
trusting memory here.
joe
****

mfc

unread,
Sep 14, 2010, 4:49:36 PM9/14/10
to
I already see that I have to use FORMAT_MESSAGE_ARGUMENT_ARRAY but
there must be a further mistake in my code

LPTSTR msg ("%1% and %2% and %3%");

LPTSTR out = NULL;

DWORD rtn = ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_STRING |
FORMAT_MESSAGE_ARGUMENT_ARRAY,
(LPTSTR)&msg,
0,
0,
(LPTSTR) &out,
Parameters.GetSize(),
(va_list *)Parameters.GetData()
);


The rtn value is 0x0d after calling FormatMessage(). But the out
string contains only characters I couldn`t interpret (maybe something
wrong with the encoding or anything else)...

Joseph M. Newcomer

unread,
Sep 14, 2010, 7:37:07 PM9/14/10
to
See below...

On Tue, 14 Sep 2010 13:49:36 -0700 (PDT), mfc <mfc...@googlemail.com> wrote:

>I already see that I have to use FORMAT_MESSAGE_ARGUMENT_ARRAY but
>there must be a further mistake in my code
>
>LPTSTR msg ("%1% and %2% and %3%");

****
What is the meaning of %<space>? The correct formatting is not "%1% and" but "%1 and"

Where is it documented that there is a terminating % on each number? RTFM. The
documenation is very explicit and precise as to what is required.
****


>LPTSTR out = NULL;
>
>DWORD rtn = ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
>FORMAT_MESSAGE_FROM_STRING |
> FORMAT_MESSAGE_ARGUMENT_ARRAY,
> (LPTSTR)&msg,
> 0,
> 0,
> (LPTSTR) &out,
> Parameters.GetSize(),
> (va_list *)Parameters.GetData()
>);
>
>
>The rtn value is 0x0d after calling FormatMessage(). But the out
>string contains only characters I couldn`t interpret (maybe something
>wrong with the encoding or anything else)...

****
Note that if you are calling FormatMessageW, then the formatting string must be Unicode
(and the above declaration erroneously constrains it to 8-bit characters) and the
parameters must be Unicode. So it would

CString msg(_T("%1 and %2 and %3");

There is no need to write
(LPTSTR)&msg
because (a) msg is already an LPTSTR and (b) you want the address of the array, not the
address of the address of the array, so it would be
msg
Note also that it would be (LPCTSTR) if you were casting (e.g., a CString), because the
argument is an LPCVOID (pointer to const void), so having a non-const cast doesn't make
sense,

So make sure you are using consistent strings everywhere. If a parameter were an 8-bit
string (guaranteed), then you would use the format for parameter 3 as %3!hs! which is the
indicator (%hs in printf) that the input string is 8-bit characters. If the strings are
Unicode but you are calling FormatMessageA, then you would specify %3!ls! which is
lower-case-L-s, which states explicitly that the argument is a Unicode string.

0 new messages