Date: | January 3, 2006 / year-entry #3 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20060103-36/?p=32833 |
Comments: | 15 |
Summary: | Here's a classic novice error. You want to call a function, say GetBinaryType. void sample() { if (GetBinaryType(TEXT("explorer.exe"), ????)) { ... } } What should you write for those question marks? Well, the prototype says that the second parameter is an LPDWORD, so let's pass it one. void sample() { if (GetBinaryType(TEXT("explorer.exe"), (LPDWORD)NULL)) { ...... |
Here's a classic novice error. You want to call a function, say GetBinaryType. void sample() { if (GetBinaryType(TEXT("explorer.exe"), ????)) { ... } } What should you write for those question marks? Well, the prototype says that the second parameter is an LPDWORD, so let's pass it one. void sample() { if (GetBinaryType(TEXT("explorer.exe"), (LPDWORD)NULL)) { ... } } Hm, but that crashes. Well, maybe we can pass it an LPDWORD this way: void sample() { LPDWORD lpdw; if (GetBinaryType(TEXT("explorer.exe"), lpdw)) { ... } } Hm, that still crashes. Oh wait, it's because of the uninitialized variable. void sample() { LPDWORD lpdw = NULL; if (GetBinaryType(TEXT("explorer.exe"), lpdw)) { ... } } No, that still crashes. Hang on, I know where I can get an LPDWORD. void sample() { LPDWORD lpdw = new DWORD; if (GetBinaryType(TEXT("explorer.exe"), lpdw)) { ... } } This code finally works! Okay, on to the next problem... Of course, the seasoned programmer still shakes his head. Just because the function prototype says that the parameter is an LPDWORD doesn't mean you have to have a variable whose type is LPDWORD. You merely need an expression whose type is LPDWORD. void sample() { DWORD dw; if (GetBinaryType(TEXT("explorer.exe"), &dw)) { ... } } Why am I telling you this?
No, I'm not trying to insult your intelligence.
I'm trying to get you to think like a novice.
Sometimes you'll be reading a chunk of code and
find something bizarro, like the fragments above
with the [While Raymond was on vacation, the autopilot stopped working due to a power outage. This entry has been backdated.] |
Comments (15)
Comments are closed. |
I think it might be slightly clearer to novices if it weren’t for the LPxxx notation that obfuscates standard pointer types. LPDWORD looks like an opaque type, a novice user probably doesn’t know what it means or how to decipher its name, so what else are they going to do? The function expects an LPDWORD; if they don’t know that is, it’s not surprising if they declare an LPDWORD and pass it in directly.
Indeed, why is the prototype not GetBinaryType(LPCTSTR lpApplicationName, DWORD* dwBinaryType) ? That would seem to make far more sense.
I believe the pointer typedefs were introduced to hide platform differences (e.g. LPDWORD was a FAR* in 16-bit), but I agree that they tend to confuse newbies.
I have seen plenty of buggy Unix code where the programmer blindly passed in uninitialized pointers, and inefficient code where the "new" solution was used (or even leaky, if the corresponding delete was forgotten). Then later programmers, seeing the precedent set by using new, proceed to do the same, causing headaches in making sure the data is deleted at the right time.
Which is why MSDN should give a sample on the page.
Actually, I often made the mistake when I started programming with Win32.
Obfuscating types only make the programmer so distant from the reality that you forget all about common rules. The second parameter is only a return value parameter, there is some tools in programming language to show that, out in IDL, using a reference (&) in C++ and using a pointer (*) in C.
Using a LPDWORD instead of a DWORD* only hide the fact that this second parameter is actually a return value.
Is this function really not supported under NT4, or is that a documentation failure?
Maybe the newbies should read the docs instead of just looking at the prototype? It clearly says it’s a pointer to a variable to receive the information.
I was under the impression that the FAR modifier is simply ignored on platforms with pointers larger than 16 bits, so couldn’t they have simply required that the function recieve a "DWORD FAR *"? Seems that should have worked cross-platform.
When I started programming and learned about the Windows API typedefs, it seemed like a great idea. Everything is defined to have a specific size. But now I’m not so sure. Because Windows has changed, some of them now lie (LPARAM and WPARAM are the same size). Plus, the P, C, etc. prefixes all define things for which keywords already exist. At this point, I’ve decided that whoever originally designed the API was simply in love with typedefs.
I think it would be a great thing if Microsoft began to phase out the typedefs and switch over to more standard C++. The typedefs could stay, for compatibility, but the examples and API could specify the standard types whenever possible, using typedefs only when a specific size is needed (e.g. DWORD/INT32), but not carrying any additional info (e.g. LPC *blech*).
I also think the TCHAR thing needs to go away. UNICODE should be used universally. The TCHAR thing is a very weak patch. It most definitely doesn’t make the differences invisible. If it did, I wouldn’t have to pepper my code with "#IFDEF UNICODE".
If you think that’s bad, I’m currently using a graphics library where the only documentation for most of it is the prototypes!
In most cases its fairly obvious, but the bezier curve function still eludes me… It takes arrays of x and y co-ordinates, the number of co-ordinates, the color and a mysterious "int s"…
This is precisely why Pascal has a "var" modifier for subroutine arguments…
"I think it would be a great thing if Microsoft began to phase out the typedefs and switch over to more standard C++. The typedefs could stay, for compatibility, but the examples and API could specify the standard types whenever possible, using typedefs only when a specific size is needed (e.g. DWORD/INT32), but not carrying any additional info (e.g. LPC *blech*). "
For a specific version of Windows (for example Win32), every parameter has a specific size.
typedefing types allow to compile code with almost all compilers (not only C or C++ compilers, but also other languages which support type aliasing).
The idea, is that the headers, containing typedefs can be specific to the language or compiler.
The ISO C++ standard (and, C++ is not the only language which can use Win32 API), only says that sizeof(short)<=sizeof(int)<=sizeof(long), and, as the C89 standard does, requires that short integers are at least 16 bits and long integers are at least 32 bits.
Tuesday, January 03, 2006 10:19 AM by Derek
> I was under the impression that the FAR
> modifier is simply ignored on platforms with
> pointers larger than 16 bits, so couldn’t
> they have simply required that the function
> recieve a "DWORD FAR *"? Seems that should
> have worked cross-platform.
FAR doesn’t work cross-platform. FAR is ignored on many WINDOWS platforms.
Wednesday, January 04, 2006 5:56 AM by SuperKoko
> The ISO C++ standard (and, C++ is not the
> only language which can use Win32 API),
> only says that sizeof(short)<=sizeof(int)<=
> sizeof(long),
I’m too lazy to look it up, but I’ll take your word for it. Do you know offhand if such a requirement ever got into the second C standard?
You know, a few hours ago I wouldn’t have thought anyone would actually try that (getting the call to work by process of elimination). But just now I got a PM on a forum asking a question about why a call was crashing, and the problem was obviously due to them doing exactly that.