|
|
Header File Organization ------------------------
This document describes the rules for public Windows 3.1 header files. These rules are designed to promote consistency, clarity, Win32 compatibility, ANSI compatibility, motherhood, and apple pie a la mode.
In the past, windows.h has been fairly randomly organized: it wasn't very easy to look in the file and figure out how constants, types, and functions are related. However, the new windows.h is much more rationally organized, and it's now far easier to understand and modify. In interests of consistency, readability, and maintainability, it's important that all of our public header files are consistently organized.
* Include a copyright banner at the top of the file. Something like:
/*****************************************************************************\ * * * header.h - Brief description of purpose of header file * * * * Version 3.10 * * * * * Copyright (c) 1992, Microsoft Corp. All rights reserved. * * * \*****************************************************************************/
If this header file has been released before, the copyright date should be something like: 1985-1992.
* Arrange your header in functional groups, like windows.h. Try to keep related types, structures, constants and functions as close together as possible in the header. Separate functional groups within the header with a banner comment, as in windows.h.
* Within a functional group, general typedefs and constants should come first, followed by logically organized function prototypes.
* Constants or types used by only one or two functions should be declared near the function.
* Make sure that everything defined in the header file is included by default: don't require people to #define things to get certain definitions.
* If you do want to break up your header file, use the #define NOXXX convention used by windows.h. Try not to have too many groups controled by NOXXX #defines, because they get confusing and hard to deal with. Compiler speed and memory capacity is not the problem it once was, especially with precompiled headers.
* Constants designed to be ANDed or ORed should be defined in hex. The number of digits should reflect the data size: 2 for bytes, 4 for words, and 8 for longs. Long hex constants should have an appended L, e.g., 0x12345678L.
* Ordinal constants values (e.g., 1, 2, 3, 4) should be declared in decimal.
* Provide a comment on all #else's and #endif's that suggests its corresponding #ifdef: e.g.
#ifdef FOO ... #else /* FOO */
#endif /* !FOO */
* Precede the header file with #pragma pack(1), and terminate with #pragma pack(). This ensures that the structures declared in the header will be packed properly, regardless of what compiler packing options the user is using for his own code. Because the Windows RC compiler chokes on #pragma statements in .rc files, it's a good idea to include this (and any other #pragmas) in an #ifndef RC_INVOKED.
#ifndef RC_INVOKED #pragma pack(1) /* Assume byte packing throughout */ #endif /* RC_INVOKED */
and:
#ifndef RC_INVOKED #pragma pack() /* Revert to default packing */ #endif /* RC_INVOKED */
* Prevent multiple inclusion of your header file with the following construct:
#ifndef _INC_MYHEADER #define _INC_MYHEADER
...body of header...
#endif /* _INC_MYHEADER */
This is the convention used by the C runtimes. For each header there is a #define that can be used to determine whether the header has already been included.
Win32 Upward Compatibility --------------------------
Part of the goal of 3.1 is to provide a more unified API that will scale with minimal pain to 32 bits in Win32. To this end, there are a few things you have to worry about in your headers (and in your code, but that's a different story...)
In 32-bit Windows, almost all 16 bit parameters, return values, and field types have been widened to 32 bits. This allows us to generate much more efficient code on the 386 and on RISC machines.
We need a way of declaring the quantities that will "float" to 32 bits in 32-bit Windows. It turns out that the C language already provides for this capability: the "int" type, for example, is 16 bits on 16 bit platforms, but is 32 bits on 32 bit platforms. "short" is always 16 bits on any platform, "long" is always 32 bits, and "char" is always 8 bits.
So, functions and structures with "int" declarations are already portably declared. The problem, though is with the WORD type. "WORD" has become an industry-wide synonym for a 16 bit unsigned quantity. But, it's also used widely in Windows header files.
Enter the UINT type. The new UINT type is typedef'd as "unsigned int": an unsigned value that is 16 bits on 16 bit platforms, and floats to 32 bits on 32 bit platforms. In the 3.1 headers, UINT is used in place of WORD wherever the size of the return value, parameter, or field will change depending on the platform.
This is a rule that applies to code you write too: on 32 bit platforms, use of the UINT type rather than WORD will generate faster smaller code. But be careful of hard-coded size dependencies on WORD: be sure to use sizeof() instead of constants, etc.
In some cases there may be structure fields whose size WON'T be changing in 32-bit windows, perhaps because the structure is used in a file format and compatibility is required. If you know ahead of time that this is the case, be sure to use short and WORD to indicate 16 bit quantities across platforms. There are a few of these exceptions with the 3.1 bitmap information structures in windows.h. If you don't know, then use UINT and int.
The new WPARAM, LPARAM, and LRESULT types, used for polymorphic or arbitrary parameters and return values (e.g., the SendMessage() function), also provide a useful degree of platform isolation. The WPARAM type is similar to UINT in that its size varies with the platform. WPARAM should be used in function parameter, return value, AND structure declarations, even though its size may vary. The use of these types indicates to the programmer that the value must be cast and assigned to the proper type before use.
Hence, the following rules:
* Use int and UINT instead of short or WORD, UNLESS you know for sure that the quantity will remain 16 bits in 32-bit Windows. The Windows HIWORD and LOWORD macros use WORD, for example. Be sure to check your uses of short as well as WORD: there are probably a few lurking out there that should be changed to int. Use int FAR* or UINT FAR* instead of LPINT or LPWORD.
* Use the LPARAM, WPARAM, and LRESULT types instead of WORD, LONG, or DWORD as appropriate.
ANSI Compatibility ------------------
Public header files should be ANSI compliant so that people can take advantage of the highest compiler warning levels possible. This also helps ensure that our header files work with a wider range of development tools.
* Don't define constants, typedefs, or functions named with a preceding underscore. This violates the ANSI namespace conventions. There are a few violations of this rule already in existence (e.g., _lread), but try not to create any new problems. (The rules are actually more complicated than "don't use underscores", but you're safe if you keep away from them).
* Don't use "//" style comments in the header: these are convenient but non-ANSI, and warning level 4 complains.
* Always test your header file by compiling it with the -W4 compiler option to ensure that it's ANSI-compatible.
* Make sure that you have no identifier conflicts with the following C library header files (NOTE: This list may be incomplete. It's a good start, though).
assert.h ctype.h errno.h float.h limits.h locale.h math.h setjmp.h signal.h stdarg.h stddef.h stdio.h stdlib.h string.h time.h
* Structure declarations should be declared with the "tag" prefix, rather than a leading underscore, as shown below:
typedef struct tagFOO { int i; UINT u; } FOO;
* Declare fully-prototyped typedefs for all callback functions. By convention, the type name should be all caps and end in PROC. For example, the window procedure callback function typedef from windows.h:
typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
Windows 3.0 Backward Compatibility ----------------------------------
In order to allow users to develop applications with 3.1 headers that will still run on 3.0, users can #define the WINVER constant to be equal to the version number of Windows they are compiling against. For 3.0, this would be 0x0300. This constant should be used to ensure that new, non-3.0 compatible features are not declared when the user is compiling a 3.0 application. Keep in mind that this version number is hex, not decimal (to be compatible with the GetExpWinVer() API).
Some of you may own headers that are designed to work with windows 3.0 as well as 3.1: in this case, you won't have some of the new 3.1 typedefs and macros defined (e.g., UINT). You can use #ifdef _INC_WINDOWS to determine whether you've included the 3.1 windows.h. Because yours may not be the only header that will want to define certain types like UINT and LPCSTR, you should #define these to be WORD and LPSTR, respectively, since you cannot typedef something twice. The other option, of course, is to have separate 3.0 and 3.1 versions of your header.
* New, non-3.0 compatible declarations and definitions should be inside #ifdef (WINVER >= 0x030a)/#endif so that the 3.1 headers can be used to create 3.0-compatible applications.
* If your header must be compatible with the 3.0 windows.h, use #ifdef _INC_WINDOWS around #definitions that define the missing types. The 3.0 windows.h file did not #define _INC_WINDOWS. Use #define rather than typedef to ensure that other headers can safely do the same thing. Here's an example that will handle most of the common problems:
#ifndef _INC_WINDOWS /* If not included with 3.1 headers... */ #define LPCSTR LPSTR #define WINAPI FAR PASCAL #define CALLBACK FAR PASCAL #define UINT WORD #define LPARAM LONG #define WPARAM WORD #define LRESULT LONG #define HMODULE HANDLE #define HINSTANCE HANDLE #define HLOCAL HANDLE #define HGLOBAL HANDLE #endif /* _INC_WINDOWS */
C++ Compatibility -----------------
To be able to use functions declared in your header directly from C++, you need to do one thing:
* Bracket the header file typedefs inside an extern "c" {} block, conditionally using the __cplusplus #define: Near the beginning of your header:
#ifdef __cplusplus extern "C" { /* Assume C declarations for C++ */ #endif /* __cplusplus */
And at the end:
#ifdef __cplusplus } #endif
STRICT Compatibility and Windows 3.0 Backward Compatibility -----------------------------------------------------------
One of the most important features of STRICT is that handle types are no longer defined as WORDs. They are declared in such a way that will cause a compiler error if you try to pass the wrong type of handle or a non-handle value to a function, for example. It's important that all of our handle types be declared this way when the user #defines STRICT.
A number of new types and such have been defined in windows.h, such as HINSTANCE, HGLOBAL, and HLOCAL, which should be used where appropriate in place of the generic HANDLE type. HANDLE should be used only in cases of an arbitrary handle type.
The WPARAM, LPARAM, and LRESULT types should be used for arbitrary or polymorphic parameters or return values. Typedefs exist for all callback functions, which are used in place of FARPROC.
In most cases, functions declared with these types are fully 3.0 compatible unless STRICT is #defined. It may sometimes be necessary to use #ifdef STRICT/#else/#endif to provide 3.0-compatible, non-STRICT declarations in some cases.
* Use DECLARE_HANDLE() to declare handle types. If you have polymorphic API parameters (or structure fields) that are designed to accept more than one type of handle (e.g., the GDI SelectObject function), there are a few tricks you can employ. 1) Declare a generic handle type like HGDIOBJ as void _near*, which will accept any handle type. The HANDLE type can be used for this purpose. 2) if the number of polymorphic types is small, and there are lots of cases where they can be used polymorphically, use DECLARE_HANDLE to declare one handle type, and typedef the rest to be the same as the first one (e.g, HMODULE and HINSTANCE in windows.h).
* Structure and function declarations should use the appropriate STRICT type, rather than the generic HANDLE,
* Declare arbitrarily or polymorphic types with LPARAM, WPARAM, and LRESULT instead of WORD, LONG, or DWORD. This indicates to a programmer that these values should not be used directly, but should instead be cast and assigned to the proper type of value before use.
* Declare arbitrarily or polymorphic pointer types with void FAR* instead of LPSTR or BYTE FAR*. The nice thing about the void FAR* type is that you can pass any type of pointer to it without having to cast first.
* If any of the above STRICT rules result in declarations that are not compatible with previously released versions of the header file, use #ifdef STRICT/#else/#endif to ensure that both declarations are present.
* Use WINAPI instead of FAR PASCAL for declaring APIs. Use CALLBACK instead of FAR PASCAL in callback function typedefs.
* Be sure to use "const" where appropriate in your pointer parameters. If the pointer is read-only, then it should be const. If the function writes through the pointer, it must not be const. For const zero-terminated string pointers, use LPCSTR instead of LPSTR.
* Don't declare NPXXX or SPXXX pointer parameter types for new structures. (but don't remove them if they've already been defined in a shipped header). Users are encouraged to use "*", const, _near, _far, and _huge explicitly where appropriate. Now that our headers contain "const" pointer types, having LP, NP, and const pointer type variants for every structure would just clog the compiler up with typedefs.
* Spell out pointer declarations, rather than using the LPXXX type form. This allows for use of const and _huge where appropriate, without having to define lots of new typedefs:
SetFoo(const FOO FAR* pfoo); GetFoo(FOO FAR* pfoo);
* Use parameter names in your API function prototypes. This greatly contributes to the readability and usefulness of your header, at very little cost. Make sure all your APIs and callback declarations are fully prototyped. Use the same naming conventions as in our documentation (contact gregro or ralphw for a summary of those conventions). NOTE: As of this writing, windows.h does not yet include function prototype names.
|