There’s more to calling a function than just getting the types to match


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 new DWORD. When you do (for example, when chasing the memory leak in that code sequence), don't tear your hair out trying to find some deep meaning for the seemingly roundabout way of accomplishing a simple task. If you can think like a novice, you will merely recognize it as a classic beginner mistake, fix it, and get on with your life.

[While Raymond was on vacation, the autopilot stopped working due to a power outage. This entry has been backdated.]

Comments (15)
  1. James says:

    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.

  2. Rosyna says:

    Indeed, why is the prototype not GetBinaryType(LPCTSTR lpApplicationName, DWORD* dwBinaryType) ? That would seem to make far more sense.

  3. CornedBee says:

    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.

  4. Isaac Lin says:

    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.

  5. Jonathan says:

    Which is why MSDN should give a sample on the page.

  6. Vincent says:

    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.

  7. Rick C says:

    Is this function really not supported under NT4, or is that a documentation failure?

  8. Jerry Pisk says:

    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.

  9. Leif says:

    "don’t tear your hair out trying to find some deep meaning"

    This line resonated with my experience. I’ve observed myself and others trying to find "deep meaning" behind the peculiar way a certain piece of code was written… but at least half the time, this is like second-guessing an idiot: the deep meaning simply isn’t there.

    When reading code, one must always consider the possibility that the author was inexperienced or lazy (or both). As an example of laziness, I’ve seen coders who knew better introduce global variables when implementing a fix/feature, simply because that minimized the number of lines of code which had to be changed… and then years later, people would invent pseudo-sound reasons for doing it that way ("using a global, rather than passing an extra parameter around to all those functions, yields faster code").

    I love code archeology :-)

  10. Derek says:

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

  11. Stu says:

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

  12. ping? says:

    This is precisely why Pascal has a "var" modifier for subroutine arguments…

  13. SuperKoko says:

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

  14. Norman Diamond says:

    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?

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

Comments are closed.

Skip to main content