the RatWin library

Contents

Distribution and Licensing

RatWin is copyright 1996–2004 by John M. Dlugosz. Use freely in code with attribution.

The RatWin project is available free of charge. Even if the Repertoire Project as a whole has a fee in the future, the RatWin component will always be free.

The official source for RatWin is http://www.dlugosz.com/Repertoire/Download.html.

General Concept

RatWin is a set of Rationalized Windows header files.

In a nutshell, I don't like having a monolithic header file. It drags in a ridiculous amount of stuff I don't need, compiles slowly, and overuses macros. I've been bitten by macros a time or two. The "NO" symbols for leaving stuff out is silly. Instead of a page or two of #define's before including <windows.h> to leave stuff out, I should say things to bring stuff in. Furthermore, <windows.h> doesn't always compile if you define a bunch of NO symbols! To top it off, the stuff shows a C heritage which is less than ideal for C++.

My concept is to repackage the include files. It uses namespaces, so you must explicitly ask for the symbols you want. It uses lots of small header files that are independent of each other, so you can include just what you need. It has strict type checking that makes the <windows.h> STRICT mode look untyped by comparison. And best of all, no macros! The only use of #define is for the include-file interlock symbol to prevent multiple inclusions.

The charter is to repackage the existing API, not design a new API. So, the function names and other symbol names are unchanged, even though a lot of the prefixes are unnecessary thanks to the better scoping being employed. Some of the functions have overloaded forms, default arguments, or missing arguments, use strong types, add const, and change pointers to references. But there are no big semantic changes.

Dependancies with Other Libraries and DLLs

The RatWin headers use some headers from Classics. However, its use of Classics is compile-time only. That is, it may use types and inline functions, from the headers, but cannot use anything that requires linking with Classics.

Using RatWin will not produce dependancies against any DLL or library other than the Win32 System DLLs.

Note: there seems to be a use of classics::string, which is contrary to this design requirement and will be eliminated.

Most of the features in the RatWin headers are simply types and inline functions to call Win32 system functions. So, including the RatWin headers will not necessarily require the RatWin.DLL file to be linked in as well.

Some features are implemented in compiled code, not entirely in the header, and if such a feature is used then it becomes necessary to link with RatWin.lib and supply RatWin.dll at run-time. This DLL is very small (20K). The RatWin.dll includes:

Theory of Implementation

Right now I'm using "cloaked" declarations to import the actual functions. They are cloaked because at least one argument is of a special type that will never actually be used anywhere. This way you will never match it when resolving overloading. Meanwhile, the C++ style functions call these global ones, using typecasts and inline code. I'll change this as soon as I learn enough about DLL's to devise a better way. In the meantime, I'm sidestepping the limitations of this technique.

Extending RatWin

I don't want to do (possibly automated) large-scale conversions of <windows.h> items. Instead, I want to do just what I need, so everything in there has been tested and used in a real situation. This applies not only to insuring correct translations, but tests the usability and packaging concepts. As it grows, I will certainly alter the organization.

Contributions are welcome. If you fix things or add more items, use a /*changed*/ or /*added*//*end added*/ comment so I can spot it to integrate into the next version.

User's Guide

Types

Fundimental Types (pointers, words, etc.)

In general, normal C++ types are used throughout RatWin. Instead of hiding a parameter's type under multiple layers of typedefs and macros, the actual C++ type is used. So you won't see Any use of DWORD or VOID. Instead, you'll see native C++ types such as long, int, and void. The only aliasing of primitive types are the following found in classics\common.h:

typedef unsigned long ulong;
typedef unsigned short ushort;
typedef unsigned char byte;

Pointer types are not typedefed to their own names. That is, a pointer to a FOO is not PFOO, but is properly spelled as FOO*.

Most of the time, however, references are used instead of pointers. Just about everyplace that the traditional WINDOWS.H file uses a pointer for a parameter, RatWin uses a reference instead.

If you really want to use WORD and other common WINDOWS.H types, you can include the file "ratwin\winnames.h". This declares these names in the winnames namespace. You can then dump these into the global namespace with a using directive.

#include "ratwin\winnames.h"
using namespace ratwin::winnames;

//now this works:
DWORD x= 5;

Enumeration Constants and Flags

The traditional WINDOWS.H file uses lots and lots of #define constants. In C++, this is properly done with constants and enumerated types. In general, RatWin uses a named enumeration to group a set of related flags.

When flags are mutually exclusive, or if there are 3 or fewer flags so it becomes feasable to enumerate every legal combination, the API function is strongly typed to accept that enumeration type.

In other cases, where the user is expected to combine flags using logical OR, the API signature is not strongly typed because combining two enumeration constants with an | operator in C++ produces an int for a result. C++ does not avail itself toward easy type checking in this case.

What to do about this is still under deliberation. It seems natural to use a flagword from Classics, which is feasable because, as a template, the implementation is entirely in a header file and will not cause a dependancy on another DLL. This has not yet been done, though. In the future, you can expect strong type checking on all flags parameters as well.

Structures and Classes

The various structures from the Win32 API are provided under the same name. Sometimes, member functions are added. But these types still qualify as PODS and may use aggregate initializers.

Adding a constructor is commonly done, but this is done in a derived class. For example, struct WNDCLASS is a PODS , and can be used with aggregate initializers or left uninitialized, as in traditional Windows programming. But class window_class : public WNDCLASS { … takes this structure and adds a proper constructor. You can use window_class in new code and take advantage of the constructor to fill in all the fields correctly with reasonable values, or you can still use WNDCLASS to bypass the calling of a constructor.

The names of the data members of the structures varies. Most use the same names as the traditional WINDOWS.H declarations, but I've experimented with "reformed" simple names as well as ways to provide both traditional and reformed names at the same time.

HANDLE and other Hxxx Types

For "Isa" relationships among the Hxxxx types, I use inheritance. So, everything "is a" HANDLE. This will actually use multiple inheritance when I get to that point, since the relationship is complex. The inability to model this complex type system in C is probably why the existing "STRICT" mode punts at so many points.

A full hiarachy of HANDLE types and compiler-awareness of the "isa" relationships between them will be fleshed out in an upcoming version.

Functions

In general, a function does the same thing as the function with the same name in the Win32 API. After all, this is just a C++ header file to get to the entry points in the various system DLLs, not a whole library in itself. So, the functions have the same names and take the same parameters in the same order.

Rather than macros to select a -A or -W form of a function, overloading is used. You can call TextOut, for example, passing it a char* or passing it a wchar_t*. Either way, the correct form is automatically used and there is no need for conditional compilation.

Note that not all functions are declared. The "add it as you need it" growth of RatWin ensures that everything in there is tested, and it stays small because functions nobody ever uses are not included. If you find that, for example, FooBar(char*) is present but FooBar(wchar_t*) is missing, add it!

Some functions take parameters that can be a string or a number; or a menu handle or a child ID. For functions that have one or two such parameters, it's practical to provide overloaded signatures that provide strong type checking of all legal parameter types and require no casting on the part of the caller. For functions that take more than two such flexible parameters, another way is needed. The new way is to use an internal type for the parameter that represents the set of legal choices (e.g. char* or int) and has constructors to provide implicit conversions from those types.

Namespace and Fragmentation Mania!

Organization

Compatability with WINDOWS.H

Binary Compatibility

Code compiled using RatWin is fully compatible with code compiled using WINDOWS.H. That is, handles obtained using RatWin may be passed to functions declared in WINDOWS.H, or vice versa; structures filled in by RatWin are identical to those used by WINDOWS.H, etc.

After all, RatWin is just a set of C++ header files providing access to the code that already exists in the system DLLs. It doesn't provide significant code itself. The system doesn't care what header the C++ compiler used to gain an understanding of the contents of the system DLLs; however it was accomplished, the DLL entry point needs to be called correctly.

You may have some OBJ files compiled with RatWin and link these with other OBJ files compiled using WINDOWS.H, without conflict.

General Source Compatibility

Unfortunatly, this compatability does not extend to the source level. Although RatWin tries to put everything inside a namespace and keep the global scope unpolluted, the "cloaking" technology used to implement the headers in a portable manner requires that signatures declaring the various system functions indeed be present at global scope.

Because C++ doesn't allow overloading functions with "C" linkage, this naturally conflicts with the contents of WINDOWS.H. In short, both WINDOWS.H and RatWin headers declare (for example) a function called CreateWindow. This is a conflict.

If RatWin is re-implemented to use forwarding COFF records instead of cloaked global declarations, this conflict will go away. For now, you have a general problem in that WINDOWS.H and RatWin cannot be #included in the same translation unit.

The solution is to separate RatWin-based code and WINDOWS.H-based code into different translation units. For code that is modular, cohesive, and uncoupled, this is not a big problem. For spegehetti code that calls anything from anywhere, it can be a difficulty.

Note that this declaration-compatibility issue is not just for RatWin. Microsoft's own DDK headers are also incompatible with WINDOWS.H ! A reusable module that itself used WINDOWS.H could not be reused for a device driver project. However, a resusable component written using the RatWin =struct files described below could be reused for a DDK-based project or anywhere else.

Proper use of RatWin allows you to write reusable components that's actually more compatible than WINDOWS.H.

Maximizing Source Compatibility

To prevent being part of the problem, header files can be designed so they are not coupled to either WINDOWS.H or the problem parts of RatWin.

RatWin only has a source-compatability problem with global function declarations of actual API functions. Code in a header does not need to refer to global functions, if functions that do such calling are placed in .CPP files and not in the header. What headers need are type definitions, and these cause no problems with compatibility. RatWin puts all its types inside a namespace, so it can have all the types it wants without polluting the global scope.

To this end, type definitions are separated out from API function declarations into different headers. These headers have a =struct suffix on their names.

.

Suppose, if you were writing a header file comm.h that referred to sockets, and comm.h included "ratwin\winsock2.h", then comm.h would be coupled with RatWin and incompatible with WINDOWS.H. A user could not include comm.h and include WINDOWS.H in the same .CPP file.

Likewise, if comm.h included WINSOCK.H to get what it needed, it would be coupled with WINDOWS.H and incompatible with RatWin. A user could not include comm.h and RatWin headers in the same .CPP file

The solution is to write comm.h so it gets what it needs by including "ratwin\socket2=struct.h". This header is very well behaved and does not affect the global scope, or anything other than the ::ratwin namespace.

Now, comm.h is compatible with anything. A user may include comm.h and also include WINDOWS.H, or may also include RatWin headers, or the Windows DDK headers, or use any other include file set that provides access to the Win32 API.


Footnotes and References

Aggregate Initializer see ANSI C++ CD2 §8.5.1. It refers to using curly braces around a list of items for a structure or array,
e.g. S x= {1, 2, 3};.

PODS stands for Plan 'Ol Data Structure, defined in ANSI C++ CD2 § 9 footnote 2

Forwarding COFF records — If anybody knows how to create "forwarders" in a DLL, please let me know. For example, Under Windows NT4, a program that imports DeleteCriticalSection from KERNEL32.DLL will actually get RTLDeleteCriticalSection from NTDLL.DLL. A record in this copy of KERNEL32.DLL (but not in Windows 95's implementation) somehow forwards the import to a different name in a different DLL, without inserting the run-time overhead of an extra wrapper.