My Photo
Location: Downham Market, Norfolk, United Kingdom

Tuesday, October 17, 2006

I have recently been suffering from what I perceive to be shortcomings in Microsoft's API documentation. In particular the following characteristics of Windows API functions seem to be completely undocumented:
  1. Memory alignment requirements. For example the 'PlgBlt' function requires its lpPoint parameter to be DWORD aligned.
  2. Structure padding requirements. For example the 'FtpFindFirstFile' function writes past the 'end' of the WIN32_FIND_DATA structure, because it assumes it has been padded to a multiple of 4 bytes.
  3. Message loop requirements. For example the 'TrackPopupMenu' function must be called from a thread with a message loop (message pump).

In the case of (1) and (2) a C or C++ programmer generally doesn't need to worry, because the Windows header files set #pragma pack(8) which I understand means that the C compiler will automatically align and pad a structure to the size of its largest member. So for example if the largest member is a DWORD then the memory address of the structure is guaranteed to be a multiple of 4 and the structure will be padded to a multiple of 4 bytes.

However this doesn't help a BBC BASIC programmer. In version 5.30a of BBC BASIC for Windows I have arranged that all structures are aligned on DWORD boundaries so that goes a long way to overcome issues causes by item (1). Item (2) is still a problem though, because BBC BASIC doesn't pad structures. The saving grace is that many Windows structures don't need to be padded (they are already the right length) or, if they do, most Windows API functions don't write into the padding region.

FtpFindFirstFile (and I presume InternetFindNextFile) are unfortunately exceptions. They return a WIN32_FIND_DATA structure and if you add together the lengths of all the structure members you get 318 bytes, which is the size of structure that BBC BASIC creates. However a C compiler will actually allocate 320 bytes and what's more that is the amount of data that FtpFindFirstFile returns! Calling the function from BBC BASIC will cause the two bytes immediately following the structure (on the heap) to be corrupted, with disastrous consequences. To avoid that you must add a dummy, sacrificial, member onto the end of the WIN32_FIND_DATA structure declaration.

It is interesting to speculate why those functions write 320 bytes, when the last two bytes don't contain any useful information. One suggestion is that they simply do a structure assignment, which in C copies a number of bytes from source to destination equal to the size of the structure, i.e. the value returned from sizeof(WIN32_FIND_DATA). This is 320 because of the automatic padding. Looked at from this perspective it is fortunate that most API functions don't work that way, and indeed both FindFirstFile and FindNextFile, which also use the WIN32_FIND_DATA structure, return only 318 bytes.

Some Windows 'experts' claim that since the Windows API is a C API (that is, it is documented using C syntax) and since the C header files are considered to be an integral part of the SDK, it is not necessary to state which API functions have special alignment and padding requirements. They would say that any programming language claiming to be binary-compatible with the Windows API needs to support the alignment and packing requirements implied by the #pragma pack(8) directive (and by implication be able to parse the C header files). Personally I think that's unreasonable, and the information should be explicitly included in the docs.

A similar justification cannot be made for item (3) above. Certainly if one is writing a simple single-threaded Windows application, with a conventional window procedure, then it will not be an issue because the calling thread (the only one) is guaranteed to have a message loop. However many non-trivial applications use multiple threads, and the fact that TrackPopupMenu must be called from a thread with a message loop is liable to trip people up - it certainly did me when I first discovered it!

One 'expert' did claim that it was obvious that this was a requirement because the function creates a window, but that's not valid because several other functions that create windows (MessageBox, GetOpenFileName etc.) don't need to be called from a thread with a message loop - generally because they create modal windows with their own private message loops. TrackPopupMenu behaves like a modal window too, at least when called with the TPM_RETURNCMD flag.


Post a Comment

<< Home