Date: | November 3, 2006 / year-entry #373 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20061103-07/?p=29133 |
Comments: | 32 |
Summary: | Everybody knows that you can use the HRESULT_FROM_WIN32 macro to convert a Win32 error code to an HRESULT, but how do you do the reverse? Let's look at the definition of HRESULT_FROM_WIN32: #define HRESULT_FROM_WIN32(x) \ ((HRESULT)(x) <= 0 ? ((HRESULT)(x)) \ : ((HRESULT) (((x) & 0x0000FFFF) | (FACILITY_WIN32 << 16) | 0x80000000))) If the value... |
Everybody knows that you can use the
Let's look at the definition of #define HRESULT_FROM_WIN32(x) \ ((HRESULT)(x) <= 0 ? ((HRESULT)(x)) \ : ((HRESULT) (((x) & 0x0000FFFF) | (FACILITY_WIN32 << 16) | 0x80000000)))
If the value is less than or equal to zero, then the macro returns
the value unchanged.
Otherwise, it takes the lower sixteen bits and combines them with
How do you reverse this process?
How do you write the function
It's impossible to write that function since the mapping provided
by the
Notice that the values in the range 1 through 0x7FFFFFFFF
are impossible results from the But let's try to write the reverse function anyway: BOOL WIN32_FROM_HRESULT(HRESULT hr, OUT DWORD *pdwWin32) { if ((hr & 0xFFFF0000) == MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32)) { // Could have come from many values, but we choose this one *pdwWin32 = HRESULT_CODE(hr); return TRUE; } if (hr == S_OK) { *pdwWin32 = HRESULT_CODE(hr); return TRUE; } // otherwise, we got an impossible value return FALSE; } Of course, we could have been petulant and just written BOOL WIN32_FROM_HRESULT_alternate(HRESULT hr, OUT DWORD *pdwWin32) { if (hr < 0) { *pdwWin32 = (DWORD)hr; return TRUE; } // otherwise, we got an impossible value return FALSE; }
because the
Now that you understand how the
You will have to use some psychic powers, but I think you're up to it.
One unfortunate aspect of both Many teams have prevailed upon the kernel team to reserve a chunk of error codes just for them.
There is room for only 65535 Win32 error codes, and over an eighth of them have already been carved out by these "block assignments". I wonder if we will eventually run out of error codes prematurely due to having given away error codes in too-large chunks. (Some sort of analogy with IPv4 could be made here but I'm not going to try.) |
Comments (32)
Comments are closed. |
Oh, surely you could make an analogy with IPV4, if you really try! :-)
Reserving ranges of error codes has a cultural solution. As long as everyone on the inside of the firewall buys in, they can go to a central authority to request blocks as needed. 20 seems to be a good default block size. The central authority keeps track of which technologies have which blocks but doesn’t care about the individual values within each block. The values can be declared in any header that’s convenient at the time and moved into a central header once they’ve stabilized (shipped). The trick is in establishing the culture.
Did VML kick that commenter’s dog and run off with his wife? Please don’t let a few bad apples spoil the use of a good teaching aid… Besides, if they’re reading this blog, they should have "it’s for backwards compatibility!" pounded into their head by now.
Speaking of error codes, the Exchange error lookup utility http://www.microsoft.com/downloads/details.aspx?familyid=be596899-7bb8-4208-b7fc-09e02a13696c&displaylang=en is worth its weight in gold…
It is not at all Exchange specific, just created by the Exchange team.
I agree, please don’t remove your diagrams because a few people are whining about them. Something is better than nothing.
Why not just use an <img> for diagrams?
"Why not just use an <img> for diagrams?"
Because he can’t upload to the blog. If he really wanted he could host them on Photobucket or some such, but meh.
Well when that day comes, they’ll just add an GetLastErrorEx() function to get an extended error code. GetLastError() would then be deprecated and will return a fixed value for all the errors that don’t fit in the old range of error values.
No matter how many problems there are with exceptions this is one area where it beats error codes totally:
Win32Exception
|
+– NetworkException
+– ClusterException
+– TrafficControlException
+– ADException
+– DNSException
+– WinsockException
+– IPSecException
+– SoSException
(Guess you were expecting a comment like this ;)
Btw: I’ll happily startup Internet Explorer (even download the newest version) if it is required to read this blog. There are Firefox-extensions available to quickly open a page in Internet Explorer… so please do not worry so much about the anti-VML crowd!
> I get the error "The directory cannot be removed." What does this mean?
It very likely means something is using WIA instead of TWAIN, and trying to map a WIA error HRESULT to a Win32 error code to get an error description string out of it. (Because AFAIK there’s no way to get *any* description strings out of a general HRESULT, unless the class that generated the error supports IErrorInfo.)
The reverse mapping is colliding, and they chose whatever Win32 error code maps to "the directory cannot be removed".
What they should probably do is provide the full HRESULT value instead, which can then be looked up in the WIA docs (hopefully) or the information provided by the WIA provider (hah, yeah right!). Of course this is probably "confusing" to the user — but I’d say it’s no more confusing than "the directory cannot be removed"!
-1 for andy
Yes, exceptions are mostly superior for error handling, but what
about code in languages without them (C)? Also, changing behaviour of
existing APIs would break too much existing code. (That said, I like
that giving bad HTREEITEM to TreeCtrl takes me down!)
+1 for kokorozashi
In fact, the whole process could be 100% automated. Warning: little
Peter’s business process follows! One sends a mail (to:
“WinError.h@kerneldevs.ms.com” subject: “New Error Code” body:
“MY_ERROR_CODE”) to a server; server modifies WinError.h and responds
to you (“ready, MY_ERROR_CODE is 1234567”); one gets WinError.h from
source control!
though, as I noted in the article. There’s also more to adding an error
code to the system than just getting a number assigned. -Raymond]
Take a look at the way Vax / VMS managed error codes. It was way cool. A 32 bit number encoded all manner of goodies, including the sub-system, severity, error, etc.
AND it was extensible by a user… they had some magic method of loading new error definitions from a file into a running system, and it applied on a per-user basis.
Way superior to anything I have ever seen, before or since.
Yes, Andy, there’s an extension for Firefox (IETab) which opens
pages in IE. Now I just need to find the URL for the Mac version of IE
… oh wait. It seems to have disappeared for some reason…
The update ‘problem’ isn’t a real problem, though: nothing even the
most trivial versioning system wouldn’t solve. When I install Foo SDK,
which installs winerror.h version 1437; installing Bar SDK the next
day, which included version 1293, detects it’s an older version and
doesn’t override.
Having said that, there’s a better reason for using blocks: as
Ashleigh mentions about VMS, it gives you an identity of the subsystem
which returned an error, even without looking up the specifics of that
particular error code.
The aliasing problem rears its ugly head quite often (that, or lazy
error handling!): a certain word processing application happens to give
rather a lot of “out of memory” errors when printing goes wrong, even
when there’s plenty of memory…
have version resources. Besides, you create the new problem that a base
header file (winerror.h) is being shipped “out of band”. Managing out
of band releases is a major headache. -Raymond]
VxDs had an ID code range issue too. (ID codes are used by 16-bit user-level code to interface to VxDs). Originally, MS handed out ranges of IDs, but MS had to stop because they were going to run out. Instead, you had to request each ID individually.
“And as I noted in the posting, text files don’t have version resources.”
True, for now; on the other hand, a decade ago Windows didn’t have anti-aliasing either. Adding a version number to a text file isn’t exactly the halting problem.
“Besides, you create the new problem that a base header file (winerror.h) is being shipped “out of band”. Managing out of band releases is a major headache.”
Utterly trivial, given a dependency tracking system: Foo SDK v2 depends on Core SDK v1.3. Now you just need some sort of Update facility for Microsoft products…
(Indeed, I seem to recall one specifically for the various SDKs available, although I haven’t seen it recently.)
Hi Raymond,
I don’t see why anyone should run out of numbers, because it is not only the lower word and the error bit that makes up error codes found in winerror.h. You still have the facility bits and for me the facility bits seem to be a natural choice for error codes of different teams. This is also what the error codes in winerror.h suggest, where there are at least 14 facility codes defined. Or are even the facility bits owned by the kernel team?
(At least this is how we did it in the company I am working for: Our error codes all have bit 29 set and every team gets a facility assigned. Within that per-team-facility, the team is free to assign error codes on their own in the low word of the error code.)
A unified error numbers file causes other problems. Every module in your system will have a dependency on it, so whenever you add a new error number (which may be for something peripheral) you need to rebuild the entire system. This is usually impracticable.
You can cheat by pretending the dependency doesn’t really exist (which is what vc++ used to do with resource files). This works most of the time, but can result in some very nasty bugs if you actually did need to rebuild everything.
I’d say the way you’d implement the "versioned header file" thing would be that the file itself wasn’t versioned, but that the package it was included in was – and in the case of a "global error code" file, it would be included in a package all of its own.
So ODBC v3.45.2 might depend on "global header file" v12.34.5 and Windows Media SDK v9.6.3 might depend on "global header file" v12.43.2. Your package management program would ensure that you always have the required version of "global header file" when you go to download its dependent SDK.
A sample on MSDN might say, "this sample requires version 9.32 of the Windows Media SDK to compile" which would, in turn, require a certain version of the "global header file" package.
Of course, that’s a pretty big effort just so you can have one place to look for error codes, and I’d say there’s plenty of BETTER reasons for developing an SDK update site than this one…
“But where do you put the version number? You can’t put it in the file name (obviously since that makes it a different file), and you can’t put it in the content since there is nowhere to put it without changing the semantics of the file.”
Er – no comments in your source code?
/* winerror.h v 3.14 (05 Nov 2006) */
Or define a preprocessor symbol, so any code using the header can check versions for itself.
“I don’t see how a dependency system “solves” the out-of-band release problem. “I downloaded some source code from MSDN and it doesn’t build.”
Or: “I downloaded some source from MSDN which said it needs winerror.h version 3.14, and the comment in my copy of winerror.h says it’s only version 3.11. Do you think that means I need an update?” Run the updater, end of problem.
“The dependency tracker says I need to download and install version 22.1.5.1 of the ODBC SDK. What the heck is that all about?”
Well, that hypothetical update site would help out there. Or a search for ‘odbc sdk’. Apart from which: does all your ODBC code compile just fine with a very old SDK, or none at all? If so, how? Of course, I was assuming said tracker would do the downloading, or explain it.
And that’s how the NT kernel, and some OS-provided usermode stuff, handles error codes. For fairly obvious reasons :-)
Here’s a snippet from ntstatus.h, which is output by the message compiler (I hope it survives posting with format intact).
// Values are 32 bit values layed out as follows:
//
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
// +—+-+-+———————–+——————————-+
// |Sev|C|R| Facility | Code |
// +—+-+-+———————–+——————————-+
The trouble comes from Win32 error codes, which are just plain integers (I assume for back-compatibility with 16 bit code, which was in turn back-compatible with DOS).
And then there’s the fact that HRESULTs are sort-of similar to NT status codes, but different (there’s some detail in the high bits that I’ve forgotten about).
I was referring to header files specifically, rather than text files in general, although comments should also work for versioning HTML or XML files in a similar way. I’d say that even among users who still have the mouse on the floor and wonder why the foot pedal doesn’t do anything could generally figure out that the error ‘this software requires a newer version of …’ means they need to update something.
Dean: Whatever the reasons, presumably they seemed good enough at one point: http://www.microsoft.com/msdownload/platformsdk/sdkupdate/
James: I know there’s plenty of good reasons for having one. It’s just not THIS reason ;)
RCS, a standard source control system for Unix, has largely solved the ‘put a version number in a text file’ problem. The ID string starts off like this:
$Id$
and is converted by RCS to look like this:
$Id:zzzzzzz$
where zzzzz is the id number. In normal practice, the $Id$ is put into a comment:
/* For C and C++: $Id$ */
C FOR FORTRAN IV $Id$
REM For Basic $Id$
Some of the downsides: humans have to be clever enough to find a space that is properly compatible, the length of the ID string is variable (so no putting it into a Hollerith constant, Fortran fans!).
And to give Raymond the biggest reason against the system that I can think of: if Microsoft picked this system blindly, they would be “polluting” RCS: they would be shipping files that can’t easily be put into an end-user’s RCS system.
Hmm, Raymond, I am not sure what you are trying to say here.
As far as I understand, HRESULT_FROM_WIN32(x) should be used to get
a HRESULT from a windows error code – or more specifically a value
returned by GetLastError().
Are you saying that GetLastError() sometimes return a value > 0xffff ?
Or, are you saying that the values returned by GetLastError() can overlap between different parts of windows ?
Or, are you saying that some developers mistakenly use HRESULT_FROM_WIN32(x) with non-windows error codes?
Or, are you saying that some developers assume that the lower 16
bits of an arbitrary HRESULT can always be passed to FormatMessage
regardless of the Facility code ?
The way I have interpreted the situation is that GetLastError()
always returns a unique 16-bit value for each error, and you can pass
that code to FormatMessage to get the error message text.
If you use HRESULT_FROM_WIN32(x) as it is intended – by inserting a
16-bit windows error code, it is very possible to perform the reverse
operation. That is exactly what your WIN32_FROM_HRESULT function does.
Would this be the solution?
* all error codes are collected into a single versionable binary file, like a TLB or a database;
* each SDK installer first updates the database, then generates winerror.h from the updated database.
I’m sorry, but this time you are really not making yourself very clear.
In your post, you where talking about implementing an inverse for the macro HRESULT_FROM_WIN32 – this is no problem at all if the macro is used as it is intended (with 16-bit values)
Now it seems that your real point is that it is impossible to retrieve a win32 error code from an arbitrary HRESULT. Of course this is true. You can only ever hope to retreive a win32 error code if the facility of the HRESULT is FACILITY_WIN32.
The facility defines how to interpret the error code. If the facility is FACILITY_ITF for example, the error code is private to the interface that returned the error, which means that the only way to get any information about the interpretation about the code is to consult the documentation for the interface you are calling.
All of this has nothing to do with implementing an inverse for HRESULT_FROM_WIN32.
A HRESULT with another facility than FACILITY_WIN32 was never created with that macro in the first place.
It is true that it is not possible to extract a win32 error from any arbitrary HRESULT, but the reason for this is NOT that HRESULT_FROM_WIN32 maps multiple different error codes to the same HRESULT.
The answer to the question in the title: "How do I convert an HRESULT to a Win32 error code?" is very simple:
If the facility code is FACILITY_WIN32, the win32 error code should be in the lower 16 bits, if any other facility – it is impossible to get a win32 error code.
You are confusing the issue by talking about how HRESULT_FROM_WIN32 maps multiple different error codes to the same HRESULT. This is not true if the macro is used as intended, and in any case it is simply not relevant for what you are trying to say.
Here’s that mapping diagram.
Sorry if I’ve overlooked this one, but why does Win32 use error codes instead of HRESULTs?
Because Win32 isn’t COM, and HRESULTs are a COM thing.
;-)
PingBack from http://addref.wordpress.com/2007/08/20/error-codes-win32-vs-hresult-vs-ntstatus/
PingBack from http://jpassing.wordpress.com/2007/08/20/error-codes-win32-vs-hresult-vs-ntstatus/