|
it was a short & simple explanation
|
|
|
|
|
First, I have experienced quirky behavior from FormatMessage and it is no doubt from the added new-line constant you mention. I will look for this in the future, thanks.
I also have an article posted, "AMX – Application Message and eXception Handling", that uses FormatMessage to extract information from user defined message files. The question that has come to mind is this: Does FormatMessage use any type of indexing or offset reference when looking up message data based on an error id or does it simply perform a sequential search/match operation?
Thanks in advance.
Ross
|
|
|
|
|
Since you don't define "quirky" behavior, it is hard to guess what might be happening, so I can't offer advice.
I have no idea how FormatMessage does the lookup; it is unspecified, and unless I had source code access I couldn't tell (and if I did have source code access, I wouldn't be allowed to tell...)
Generally, the performance will not matter in the slightest. If you had 1000 messages in the file, it would take on the average 500 compare-and-test operations to find the message. Assuming a loop that does this takes < 10ns, this means that on the average, it will take 5 microseconds to do linear lookup. Rotational latency of the disk drive in the open of the message file will dominate this time by orders of magnitude (3-4 orders of magnitude, perhaps 5-6 orders of magnitude), and you are about to either present a message to the user (tens of milliseconds to seconds) or log it to a file (tens of milliseconds) so the time of linear search, binary search, or hash search disappears completely from any measurement you could make.
|
|
|
|
|
I just found this article via a google search for FormatMessage.
This is an update, a minor rant, and a question.
Environment Visual Studio 2008, Windows XP Home, C++
Fufnction FormatMessage() has changed since this article was written. Here is the comment I sent to Microsoft via their link at the bottom of the help page.
(Knowing that it will never ever be read and probably go right straight to their bit bucket, but trying to be a good user, I sent it anyway.)
In regards to FormatMessage help page found here:
ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/debug/base/formatmessage.htm
Defines FormatMessage as follows:
DWORD WINAPI FormatMessage(
__in DWORD dwFlags,
__in LPCVOID lpSource,
__in DWORD dwMessageId,
__in DWORD dwLanguageId,
__out LPTSTR lpBuffer, // NOTE argument 5, type LPTSTR
__in DWORD nSize,
__in va_list* Arguments
);
This code:
DWORD dwFlags = FORMAT_MESSAGE_FROM_SYSTEM;
LPCVOID lpSource = 0;
DWORD dwMessageId = m_Open_Error_Number;
DWORD dwLanguageId = 0;
LPTSTR lpBuffer; // NOTE: used for argument 5
DWORD nSize = 0;
va_list* Arguments = 0;
// look here for an article about FormatMessage
// http://www.codeproject.com/KB/tips/formatmessage.aspx
//
m_Format_Message_Return = FormatMessage(
dwFlags,
lpSource,
dwMessageId,
dwLanguageId,
&lpBuffer, // NOTE: argument 5
nSize,
Arguments );
Solicits this error:
f:\b_code\visual_studio\stars\test1\cstarendpoints.cpp(297) : error C2664: 'FormatMessageW' : cannot convert parameter 5 from 'LPTSTR *' to 'LPWSTR'
per the help page, parameter 5 is type LPTSTR and therefore should not be converted to LPWSTR per the error message. Either the help page or the error message generated by the compiler is wrong.
FURTHER
When the argument is changed to LPWSTR, the error message becomes:
f:\b_code\visual_studio\stars\test1\cstarendpoints.cpp(297) : warning C4700: uninitialized local variable 'lpBuffer' used
Look at the definition of function FormatMessage where it has “__out” in the definition. This is an OUTPUT from the function and does not need to be initialized.
The declaration was changed to:
LPWSTR lpBuffer = 0;
Resolving the warning. But if it is an __out, and that use is present before anything else, initialization should not be necessary.
QUESTION / REQUEST
First, to Newcomer and all other article writers: Stop scrimping on variable names. Don’t use “s” when you can spell out a decent variable name. There is no need to be stingy with your names. An explanatory name is quite beneficial. In my example, I use the same names that are found in the help file. That way I can refer back and forth between my code and the text of the help file.
Almost all writers do this. Even writers of notable merit such as Petzold and and McConnel. That doesn’t make it OK. Please use descriptive names.
Second: I need some detailed instructions on the use of that last argument. Please tell me the basic purpose, then provide a simple example.
If I leave it 0, will I get back a valid message in lpbuffer?
All in all, the article is good, thanks for writing and posting it.
Thanks for your time
|
|
|
|
|
The parameter should have been written as
(LPTSTR)&lpBuffer
because that is how you have to write it to get it to compile. Note that is how I wrote my code, so you have no reason to expect that doing it some other way and using an illegal type should compile, or, as you have done, making an erroneous change so it compiles and performs a call using illegal values. Therefore, the "problem" you are reporting is a coding error on your part, not an issue with the definition of FormatMessage, which hasn't changed since it was created. So Microsoft will correctly ignore your complaint, since it is clear that you failed to read the documentation and wrote bad code.
The declaration should NOT be changed as you indicate; it should remain as
LPTSTR buffer. Certainly, your code that does not cast is simply erroneous code. If you change the declaration as you have shown, your code is now nonfunctionally incorrect, because you are passing a NULL pointer to the buffer.
You have additionally said that the pointer is NULL and the size of the buffer is 0, which means that FormatMessage will NOT return any data. If you had set the size to > 0, you would take an access fault because you passed a NULL pointer to the buffer. You must either pass a valid buffer pointer and a valid size, or a *pointer* to an LPTSTR and use FORMAT_MESSAGE_ALLOCATE_BUFFER and free it with LocalFree. That's why the value has to be an LPTSTR, and why you have to do the cast (and that's why I wrote my code the way I wrote it).
You would NOT declare va_list * args = NULL; that is plain silly. If you want NULL, you use NULL; you don't need to declare a variable for that purpose. If you want to pass 0 for the nSize, you write a 0 in that parameter position; it is extremely silly to declare a variable, initialize it to 0, and use the name of the variable. What part of "constant" have you failed to understand in the C language? If you want the default language, you would use LANG_USER_DEFAULT or some other constant (or 0), but to declare a variable for the sole purpose of setting it to a constant value so you can use a variable name in the call is absolutely absurd.
Perhaps the reason you feel compelled to write long variable names is a proclivity to use unnecessary variable declarations that serve no useful purpose. So there is no need to use variable names at all. The purpose of the declarations in the description is to tell you the TYPE of the argument, not to insist that there be a variable of that name. It is a design error to declare tons of variables that serve no useful purpose. And I would NEVER use the names from the documentation, because they all use the silly "Hungarian Notation", which is one of the worst ideas to have been foisted off on the world since FORTRAN decided that all variables that started with I through O would be integers.
In addition, you must NEVER, EVER, UNDER ANY IMAGINABLE CIRCUMSTANCES, use the m_ prefix for a parameter or local variable. If you are going to adopt these meaningless and burdensome conventions, you are OBLIGED to use them correctly, and m_ means it is a class member. You declared these as local variables, and in that case, m_ is completely and absolutely incorrect by ANY standard of programming. If they *are* class members, your coding style needs a recalibration, so I'm assuming they are, as they should be, local variables, and therefore must NOT be called m_.
If you want to emulate poor programming styles, understand them first. If you don't understand them, don't try to follow them because you are not actually following the styles, just blindly copying naming conventions with no understanding, and getting them completely wrong.
I would have written that call much more simply--and correctly-- as
DWORD result = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL,
msgid,
LANG_USER_DEFAULT,
(LPTSTR)&lpBuffer,
0, NULL);
I don't need to use fancy names for temporary variables that have a lifetime of a few lines of code. I don't need to declare variables just to pass a constant to a function.
All strings are called s. All formatting strings are called fmt. All temporary messages are called msg. If you limit the scope, you don't need to worry about conflicts. The smaller the scope of your declaration, the less you need to worry about the significance of the name.
In C++, you do not declare all the variables at the top of the block; this is actually poor style. You don't declare the variable until you need it.
The best thing you can do about any style Petzold teaches is to unlearn it as quickly as possible. I can always tell someone who learned from Petzold: their code is really poor, and the organization of their program is abysmal. It took me two years to unlearn all the style errors that Petzold teaches. both at the lines-of-code level and the program-organization level.
Using a variable which has the same name as the documentation means that you can't do two calls of the same API in the same scope with different arguments, which clearly makes no sense. I can't figure out how it could EVER make sense.
You would typically write NULL for the last argument of FormatMessage. If you don't, it is either a pointer to a va_list of arguments that will be used to provide parameters for the %1, %2, etc. formatting requests in the formatting string, or a pointer to an array of LPTSTRs, depending on the setting of one of the flags.
If you don't use %1, %2, etc. then you don't need the last parameter and you can write NULL for it.
For example
CString FormatMyMessage(UINT id, ...)
{
va_list args;
va_start (args, id);
CString fmt;
LPTSTR s;
fmt.LoadString(id);
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING,
fmt,
0,
LANG_USER_DEFAULT,
(LPTSTR)&s,
0,
args);
CString result(s);
LocalFree(s);
va_end(args);
return result;
}
This is a sketch; error checking is left as an Exercise For The Reader.
IDS_DEVICE_ERROR "There is a problem %1 with device %2"
CString s = FormatMyMessage(IDS_DEVICE_ERROR, ProblemDescription, DeviceName);
Note this call works correctly even if you change the message to say
IDS_DEVICE_ERROR "Device %2 has a problem: %1"
|
|
|
|
|
Can you pls suggest how to invoke the messages from a resource-only DLL (created by mc) for different languages.
Language id specified in FormatMessage syntax says not suppported. Hence irrespective of having messages of different langauges, only the default langauage is selected.
regards,
Yuva
|
|
|
|
|
I have not done an internationalized app in many years (we let our overseas distributors handle this), but I'd suggest getting the Schmidt book on Programming International APplications from Microsoft Press, which goes into this in great detail. Essentially, you have to do the #include of the various language output files from mc into different sections of the .rc file, based on what language is being covered.
|
|
|
|
|
I have a message file with this:
;//...
MessageId = 0x40
Severity = Success
Facility = Application
SymbolicName = MSG_SVC_OPERATION
Language = English
The %1 service has been %%%2.
.
;//...
MessageId = 0x43
SymbolicName = PARAM_SVC_INSTALL
Language = English
installed
.
When I use ReportEvent, eg. with message string 0x40 ( The %1 service has been %%%2. ) and pass to it ppstrings "MySvc" and "0x43", it performs complex expansion, replaces %%%2 with 0x43, so we get %0x43 and at last replaces %0x43 with the message string with same ID, so we get the following string: "The MySvc service has been installed.". But when I use FormatString it gives me "The MySvc service has been 0x43." or "...%0x43" I do not remember.
Could you help, please ?
#ifndef
#define __ARMEN_H__
#endif
|
|
|
|
|
I've never tried this, I have no insights to offer.
|
|
|
|
|
Joe, you're a great programmer and I love your essays, but you give some very bad advice in this one:
> Note that even if you think the message code is of no use to the user, display it anyway. For example, a message of the form is actually very useful. Here is a simulated display of an AfxMessageBox. Your tech support people will be greatly aided.
This is sompletely backwards. You're putting the needs of the tech support people ahead of the needs of the user! This is one reason that so much software is hard to use.
I suggest you read Chapter 28 in Alan Cooper's book, "About Face". This chapter, entitled "The End of Errors", describes why eror messages are a bad idea and should be avoided whenever possible. Cooper sums it up as: "Error message boxes stop the proceedings with idiocy." Great reading.
|
|
|
|
|
IMNHO:
The opposite is the "operation failed. We are sorry. please try again" style that gives no clue - neither to the user, nor to the developer. often enough error messages are crippled down in the way of making it "more friendly". Users hate message boxes anyway. And most of them don't read it.
I actually like the user mode exception dialog on Win 9x (page fault, invalid op,...): a place where the manager can put his/her fluffy message, and a "details" area where you can dump the innards. File, Line, Error code -
but - an example for your side.
Tried to import an old mail folder into Outlook. Error "Before using this service, you must configure access rights for it" (something along hese lines). Reason? The mail folder file was r/o.
|
|
|
|
|
There are a lot of ways to do bad message boxes. One is to write uninformative messages (as in your first example). Another is to write informative messages that are hard to read. Another is to write incorrect or misleading informative messages (as in your Outlook example). And yet another is to present messages that provide information that the user does not care about.
A really good, and funny, discussion of bad error messgaes can be found at http://www.iarchitect.com/errormsg.htm. The page also links to some tips for creating good error messages
|
|
|
|
|
Unfortunately the iarchitect.com site appears to have been taken over by some type of domain name squatter/pseudo-search service.
|
|
|
|
|
The problem is a balance between tech support and users. Failing to put the error code in hurts the ability of the tech support people, but adding it in no way interferes with the ability of the user to comprehend what is going on because the textual message appears. Without the number, there is a possibility that you could not see an error string at all, if the error was the consequence of some SetLastError done by the application or a 3rd party library. By including both, I help the tech support people, which helps the user when the user calls for support.
Note that I despise the DDV mechanism of dialogs, because it is preemptive, intrusive, inhumane, unsuited for users, and overall one of the most inappropriate mechanisms ever invented for validating fields. It happens at the wrong time, and is obnoxious and offensive. However, if an error notification is to be presented at all, it needs to carry both user-oriented information and tech-support-oriented information. There is nothing worse than sitting on the end of a phone line and having the user say, "it says 'bad data'" or it says "internal error, call tech support".
I have not read the book (it sounds interesting) but how does he propose to handle errors that actually DO stop the proceedings, that is, conditions which arise which require the user take an explicit action?
I've actually produced one application that is so modeless that it has no dialog boxes at all. This is actually quite difficult, particularly because it violates users' expectations that dialogs *will* pop up!
|
|
|
|
|
You didnt mention that the FormatMessage supports so called Insertion Strings. Which is nothing else than numbering the parameters.
This is very useful when you are using multiple parameters to show an error (or even a information) like in the following sample:
The file 'whatever.txt' could not be copied to 'A:\'
Here we have two parameters: the filename and the target. Now, when you are going to translate this error to another language it WILL happen that in a particular language the order of this two parameters has to be reversed.
With Insertion Strings you are out of trouble because FormatMessage takes care of the order.
The only difference you must make is the syntax of the parameter string: add a parameter number after the % sign and wrap the printf specifier in exclamation marks.
The string "The file '%s' could not be copied to '%s'" becomes "The file '%1!s!' could not be copied to '%2!s!'"
Its that simple. See the SDK documentation for the whole information on this VERY helpful function.
btw, Joseph: FormatMessage DOES support suppressing the newline - just add a %0 to the message. Unfortunately it doesnt do that when using FORMAT_MESSAGE_FROM_SYSTEM.
cheers
Andreas
|
|
|
|
|
The !s! is redundant; the assumption is that the insertion string is, as the name suggests, a string.
While %0 will suppress the newline, this is true only for messages YOU create and put this format request into (IBM made the same blunder in 1968 in TSS/360; you think people would learn...). It does NOT help you deal with the gratuitous newline that is provided by all the built-in messages, so the code I gave *is* required for handling any message not so prepared.
I don't always specify *everything* in my essays; I leave some of the work up to the reader! (My course, for example, goes into the string insertion feature in some considerable detail)
|
|
|
|
|
Joseph M. Newcomer wrote:
I don't always specify *everything* in my essays; I leave some of the work up to the reader!
No offense. Just had the feel that the readers might miss this small detail and just use your article as an "advanced GetLastError()" function.
|
|
|
|
|