Why was MoveTo replaced with MoveToEx?


Commenter Ulric asks, "Where did MoveTo(HDC, int, int) go?"

Back in the 16-bit days, the function to move the current point was called MoveTo, and its return value was a DWORD which encoded the previous position, packing two 16-bit coordinates into a single 32-bit value. As part of the transition to 32-bit Windows, GDI switched to using 32-bit coordinates instead of the wimpy 16-bit coordinates of old. As a result, it was no longer possible to encode the original position in a single DWORD. Something new had to be developed.

That new thing was the MoveToEx function. Instead of returning a single DWORD, it accepted a final parameter which received the previous coordinates. If you didn't care about the previous coordinates, you could just pass NULL. All of the GDI functions which used to pack two 16-bit coordinates into a single DWORD got Ex-ified in this way so they could accommodate the new 32-bit coordinate system.

But why did the old MoveTo function go away? Why not keep it around for source code compatibility?

I find this an interesting question, since most people seem to think that maintaining source code compability between the 32-bit and 64-bit versions of Windows was an idea whose stupidity rivals prosecuting a land war in Asia. (If we had followed this advice, people would just be asking, why did you replace WinExec with the much harder-to-use CreateProcess?) By the same logic, source code compatibility between 16-bit and 32-bit Windows is equally absurd. According to these people, porting 16-bit code to to 32-bit Windows is the best time to introduce these sorts of incompatibilities, in order to force people to rewrite their programs.

Anyway, the reason we lost MoveTo was that there was no way to return 64 bits of information in a 32-bit integer. Now it's true that in many cases, the caller doesn't actually care about the previous position, but of course the MoveTo function doesn't know that. It returns a value; it doesn't know whether the caller is going to use that return value or not.

I guess one way out would have been to change the return value of MoveTo to void. That way, people who didn't care about the return value would still compile, while people who did try to use the return value would get a compile error and have to switch to MoveToEx.

Yeah, I guess that could've been done, but you could also have done that yourself:

#define MoveTo(hdc, x, y) ((void)MoveToEx(hdc, x, y, NULL))

I find it interesting that most people who write their own MoveTo macro don't use the (void) cast. In most cases, this is a mistake in porting from 16-bit Windows. (I can tell because the macro is mixed in with a bunch of other porting macros.) However, in other cases, it could be intentional. The authors of the macro may simply not have known about the old 16-bit days and simply expected their macro to be used as if it were prototyped as BOOL MoveTo(HDC, int, int).

These people will probably be baffled if they run across any actual 16-bit Windows code that tried to extract the high word from the return value of MoveTo. "Why are you extracting the high word from a BOOL?"

Historical exercise: Instead of adding a new parameter, why not just make MoveToEx return an __int64?

Comments (42)
  1. I’m guessing the 16-bit ABI didn’t specify how 64-bit values are returned, or something like that?

  2. s says:

    I assume there couldn’t be a 64bit return value because at the time compiler support for 64bit values was patchy – hence ULARGE_INTEGER and FILETIME

  3. Gabe says:

    Even if the compiler supported a 64-bit integer type, it wouldn’t be feasible because code that did MoveTo(x, y) >> 16 would be wrong. Not only would it be wrong, but it would still compile.

  4. Alexandre Grigoriev says:

    The function could return 64-bit value as POINT structure. This would not have much runtime overhead because this would be returned as EAX:EDX.

  5. DriverDude says:

    "Instead of adding a new parameter, why not just make MoveToEx return an __int64?"

    Because packing two descrete values into an integer, in the days of a multi-MB OS, is just lame. And it’s just asking for people to make careless mistakes.

    C has data structures and pointers – use them!

    I’m glad the Win32 folks did…

  6. DWalker59 says:

    @Alexandre Grigoriev: The 64-bit value is not a pointer, so it should not be returned as a POINT structure.  That’s asking for trouble.

  7. Alexandre Grigoriev says:

    @DWalker59,

    I don’t quite understand your objection. Are you saying that:

    POINT MoveTo2(HDC hdc, int x, int y);

    is not an acceptable function declaration?

  8. Nawak says:

    most people seem to think that maintaining source code compability between the 32-bit and 64-bit versions of Windows was an idea whose stupidity rivals prosecuting a land war in Asia.

    I don’t know about "most", it is often the disagreeing side which is the most vocal, since the agreeing side, well, has ‘won’ and doesn’t feel so strongly about the situation.

    Anyway, indeed backward compatibility is a very dividing subject and your blog sure helps grasping the many aspects of it. It should eventually tame the epidermic reactions to the subject.

    So, thanks Raymond!

  9. porter says:

    > POINT MoveTo2(HDC hdc, int x, int y);

    1. How about "MoveTwo" as a replacement name?

    2. __int64 cannot be passed as an LPARAM or LRESULT in Win32 unlike DWORD. Win64 is a different story.

  10. Alexandre Grigoriev says:

    @porter,

    Any changes in API should:

    1. NOT break code that doesn’t need changes.
    2. BREAK code (make it not compile) that needs changes.

    Changing MoveTo return type to POINT would achieve that.

  11. Gabe says:

    Of course it would be possible to assume that the few users of MoveTo that were using the return value were not using coordinates outside the range that 16-bit numbers could represent. Afterall, if the 16-bit version of the program didn’t use large coordinate values, why would the 32-bit version?

    But then you have the danger of somebody using MoveTo in a 32-bit program that does use large coordinate values, only to get the MSW of each coordinate chopped off. They would have the same problem as most users of GetFileSize do now.

  12. John says:

    16 bits ought to be enough for anybody.

  13. "16 bits ought to be enough for anybody."

    Not if you’re printing on A3 paper with MM_HIMETRIC coordinates. Or, for that matter, MM_TEXT and a 2400 dpi device.

  14. Larry Hosken says:

    Future compatability with fuzzy logic? Extracting the low word would be silly, though.

  15. Michael Mol says:

    My three guesses:

    1. We may eventually want device contexts with sufficiently high DPIs to require positional values greater than 32 bits will hold.  However, for a 30 yard printed banner, that would require a dpi value itself approaching the limits of a 32-bit field; You would need a microscope to see the individual dots.  A Mercator projection of the Earth’s surface would only require a horizontal resolution of about 1.5 billion pixels for 1dpi, but 2.72 dpi bumps it up against the 32-bit limit.  You get 1 dpi at that limit if you go to an altitude of about 10,000 km. (radius * 2.7 – radius)

    2. __int64 is a compiler extension. ‘Nuff said.

    3. Switching to pass-by-reference allows the future addition of a Z parameter, for when three-dimensional displays become more popular. (Heh. GDI for 3D rendering?  An amusing thought.)

  16. doynax says:

    So why was 32-bit coordinates considered important for MoveTo while, say, WM_MOUSEMOVE still has to make do with 16-bit coordinates?

    [I’m confident you can figure this out. -Raymond]
  17. doynax says:

    Device coordinates vs pixel coordinates, and possibly printing?

  18. Joseph Koss says:

    "Instead of adding a new parameter, why not just make MoveToEx return an __int64?"

    I suspect it has something to do with the fact that WIN32 could actualy run *under* WIN16.

  19. Shaka says:

    @doynax,

    I think that it is because WM_MOUSEMOVE, and the other mouse related messages, deal with… mouse coordinates as it moves around the screen. And I think that we must wait a bit to put our hands on a display that support resolutions upper than the 32K x 32K range used by WM_MOUSEMOVE & co.

  20. Skizz says:

    @Shaka

    Well, 16 2048xwhatever monitors side-by-side would hit the 32K range – and yes, I am trying to out-do Swordfish!

  21. mh says:

    The main reason I can think of is that if you had code that needed to run on both 16 bit and 32 bit Windows.  Everytime you used MoveTo you would need an OS version check and two different return value decoding systems.  It’s cleaner to just replace the function, and it has the added bonus of ensuring that a certain type of programmer who is hell-bent on doing things the wrong way despite all documentation and dire warnings is forced to do them the right way.

  22. Joseph Koss says:

    __int64 being a compiler extension isnt really a good arguement, since there also is no standard int, long int, long long, etc..

    ..the non-extensions, when used, are used in error. You shound’t be using ‘int’ for 32-bit values since int’s might be 16-bit values, or 64-bit values..

    I had another thought about this. Remember that NT doesnt exist only on x86 derivative platforms. It could very well be that 64-bit return values was verboten on some of the platforms supported by NT.

  23. benski says:

    The __stdcall calling convention (Aka WINAPI and CALLBACK) specifies returning values going into the EAX register, which isn’t large enough to hold an __int64 or a POINT structure or anything like that.  Returning a pointer is unsafe because you have a multithreaded race condition (like some other old Win16 APIs that Raymond has gone over the in the past).

  24. Tom says:

    @doynax: I’m going to go out on a limb here and say it’s becuase WM_MOUSEMOVE is in screen coordinates (device units) whereas MoveTo() is in logical coordinates.  The GDI mapping mode can be used to take huge coordinate systems (logical) and map them to a constrained set of device coordinates (physical).  Current display technology is just about 2,000 pixels wide right now — we’ve got to get displays a lot bigger before we worry about overflowing 16 bits of pixel address.

  25. Alexandre Grigoriev says:

    @Benski,

    EAX:EDX is used to return 64 bit values or structures.

  26. Joseph Koss says:

    Alex:

    Minor quibble, but thats EDX:EAX

    (long time assembler programmer here)

  27. porter says:

    > Minor quibble, but thats EDX:EAX

    Ah, but i386 is little endian…

  28. Alexandre Grigoriev says:

    @porter,

    Doesn’t matter, it’s always been high:low. ss:bp, es:bx, dx:ax, edx:eax.

  29. Joseph Koss says:

    The ‘:’ in this case is used similar to a decimal point.

    Programmers from both endians write values from most significant digit to least, because that is the standard convention.

    EDX:EAX is standardized upon because the integral division instructions support double-wide numerators, which must be stored specifically in these registers in that order.

  30. Ulric says:

    I’m gonna go ahead and make the statement that no one ever cared about the returned value MoveTo and no non-trival program used it.  Who’s with me? :P I can’t recall seeing the return value used in 17 years.  There was never really a need to restore the raster coordinate.

    on a more serious note, not only the source code compat was broken, to me it felt inelegant that GDI no longer had a proper MoveTo function.

    I’m curious about that __int64 bit though.

    BTW, MFC insulated us from the source code change…  CDC::MoveTo continued to exist and return a CPoint.

  31. porter says:

    > I’m curious about that __int64 bit though.

    As far as I know, __int64 still worked in WIN32S.

    But for backward compatibility, Microsoft have bucked the trend of using 64 bit longs in 64 bit environments because of the screeds of code that assumes sizeof(long)==4.

  32. Mike Caron says:

    I am hardly an expert on this subject, but the obvious answer is that it’s because you want to avoid returning large amounts of data on the stack.

    As was alluded above, a single 32-bit value can be returned in a register, but what about larger values? What if you need to return 96 bits?

    All in all, better to just pass it by reference.

  33. porter says:

    > All in all, better to just pass it by reference.

    Are you not arguing for return values to be prohibited from with Win32 API and all values to be returned by reference on the chance that they may change size?

  34. Mike Caron says:

    No, I’m saying don’t return structs. Return ints, return BOOLs, return integral types/pointers. If you need to sling large data types around, do it by reference.

    Plus, the compiler has to generate code to move your data from the stack into wherever you’re keeping it. I don’t know that a 64-bit struct would be returned in EAX:EDX (or however you say it, I don’t do assembly), since you’re not returning a 64-bit integer, you’re returning a struct whose length *happens* to be 64-bits.

  35. porter says:

    >> I don’t know that a 64-bit struct would be returned in EAX:EDX (or however you say it, I don’t do assembly), since you’re not returning a 64-bit integer, you’re returning a struct whose length *happens* to be 64-bits.

    Win32 __stdcall does return an 8 byte struct in EDX:EAX. OS/2’s _System calling convention to which it is closely related didn’t.

  36. Ulric says:

    What?  "Pass by reference"?  That doesn’t make sense to me.  If the function returned a pointer, aka a ‘reference’, that would mean that the pointer would point to a structure would be somewhere in a global variable.  This would be a total nightmare, as the next call would overwrite it, and there would need to be one such variable for each thread to make it thread safe.  MoveToEx already solves all of that problem by copying the struct into your own

  37. Joseph Koss says:

    Mike Caron:

    Functions return values do not go on the stack. They return them in standardized registers.

    Win32 STDCALL:

    32-bit returns are in EAX.

    64-bit returns are in EDX:EAX.

    Floating point return values of all sizes are returned on top of the FPU stack (its registers are 80-bits wide)

    The definition further says that structures up to 64-bits are returned within EDX:EAX (even if it contains a floating point value)

    Anything beyond this is not part of the specification, however standard convention is for the function prototype to explicitly specify caller-provided pointers to hold additional, or nonstandard return values (caller-provided for obvious reasons)

  38. porter says:

    Ironically the return values larger than 64bits breaks the __stdcall decorations designed to indicate how many bytes of arguments are on the stack, as the compiler pushes an additional pointer on the stack to point to where the return value should be put. So return values larger than 64bits are already passed by reference whether you like it or knew it.

  39. Michael Mol says:

    @Ulric

    "Pass by reference" typically means that you use one of the function’s parameters to specify where you want the value to be put.  In C, this would have to be passing a pointer to the variable where you want the value stored.  In C++, you can use reference types (Which almost, but not quite, avoids the problem of an invalid reference; It’s possible to get dangling references in C++ if you’re too clever.), but we all know that the Win32 API is targeted at C.

  40. Michael Mol says:

    @Joseph

    Which is why Win32 provides WORD, DWORD, ULONGLONG, etc.  I don’t know if Mr. Chen was implying the use of ULONGLONG, though, as he used __int64 specifically.

    Perhaps a more interesting approach in this case might have been to use DWORD_PTR or UINT_PTR, under the assumption that those types represent the largest native pointer type, and under the assumption that the largest native pointer type corresponds to the largest native unsigned integer type.

    While that would have expanded capabilities with the increase in type size, it would have required developers to do some extra trickery to account for varying sizes, which they already didn’t bother to do despite the fact that the C and C++ languages don’t make many guarantees about their type sizes.  A workaround might have been to provide a type like UINT_HALFPTR, but a workaround to support a workaround is just…ugly.

    Forget my earlier guesses.  Pass by reference is just plain cleaner…

  41. Ulric says:

    @Michel Mol

    I know what Pass By Reference means, but I thought you guys were discussing a way to implement the return value, not the way MoveToEx was eventually implement returning the POINT through an argument

  42. Joseph Koss says:

    Michael Mol:

    Win32 definately does not target C since C’s calling convention, CDECL, is nowhere in sight.

    STDCALL passes arguements right to left (just like the CDECL calling convention) but delegates stack cleanup to the function itself (just like the PASCAL calling convention.)

    Microsoft has two DOS compilers that operated exactly like this even before Windows 3. One was the BASCOM/PDS line and the other was the QuickBASIC line. Even the required register preservation (albeit in 16-bit form instead of 32-bit form) for their ‘Quick Libraries’ (which are similar to modern DLL’s) was the same.

    Make what you want of that. I’d argue that the features of STDCALL were chosen independently and that it only coincedentaly matches that of their early Basic compilers due to having similar dynamic linking requirements.

Comments are closed.