FeatureUSENIX

 

working with Win32: the good, the bad, and the ugly

korn

by David Korn
<dgk@research.att.com>

David Korn is chief software architect in the research division of AT&T Labs. He explores software development techniques that improve programming productivity. His best known effort in this area is the Korn shell, ksh. He is a Bell Labs fellow and an AT&T fellow.

I have been working for over two years trying to write a UNIX interface for Windows NT and Windows 95 based on the Win32 API (Application Programmers Interface) provided by Microsoft.

The result of this effort is a software layer named U/WIN. Information about U/WIN can be found on the internet at <http://www.research.att.com/sw/tools/uwin>. Educational, research, and evaluation copies of U/WIN can be freely downloaded from the net. Commercial versions of U/WIN are available from Global Technologies Ltd., which owns the trademark for U/WIN and licenses U/WIN software from AT&T. The home page for Global Technologies Ltd. is <http://gtlinc.com>.

In this article I describe my experience with the Win32 API, the good, the bad, and the ugly. I had no experience with any previous DOS or Windows system, and my experience with the Win32 API is limited to the subset of functions needed to provide X/Open functionality and does not include graphical user interfaces. The opinions expressed here are mine alone and do not represent those of AT&T or any other entity.

One word of caution about the Win32 API. The Win32 API is not the same on Windows NT and Windows 95. In fact, it differs from one release to the other as new functions are added. More importantly, many of the interfaces are not implemented in Windows 95 or in some cases have different semantics.

The Good

There are a number of aspects to Win32 programming that I found to be quite usable. The association of "handles" with most objects was quite useful. Handles are similar to UNIX file descriptors except that they do not have any ordering property as do file descriptors. In addition to file, each process, thread, and synchronization object is accessed through a handle. Handles can be "duped" across process boundaries because the DuplicateHandle() function takes as arguments a handle of both the "from" process and the "to" process.

Handles have associated with them security descriptors that define the owner, group, and access permissions to the object. In addition to the usual read, write, and execute permissions, an object can have synchronization capability. A handle with synchronization capability can be in "ready" or "not ready" state. A process can block until at least 1, or up to 64, handles are in the ready state.

One facility in Win32 that is not available on most UNIX systems is the ability to be notified when files change. The Win32 API has a primitive to create a handle to a directory that has the synchronization capability and will be in place in ready state whenever changes occur to files in that directory or in its subdirectories.

I also liked the thread interface. It was simple to use and provides the necessary functionality without an overabundance of interfaces. One novel feature available only on Windows NT is the ability for one process to create a thread inside another process.

I was impressed with the I/O performance for Windows NT running on a Pentium Pro. It measured up well against Linux with users unlikely to perceive any differences.

I used the Visual C/C++ compiler from command line mode extensively and had relatively few difficulties. The compiler seems to conform with the ANSI-C standard.

Finally, I found some useful sources for information and tools for Win32. There was lots of useful information in Jeffrey Richter's book Advanced Windows NT (Microsoft Press, 1993) and several additional useful pointers in Matt Pietrek's Windows 95 System Programming Secrets (IDG Books Worldwide, 1995).

The Web site <http://www.ntinternals.com> by Mark Russinovich and Bryce Cogswell is an excellent place to go for useful tools; the set of tools provided by the Microsoft development kit is decidedly lacking.

The Bad

There were many things that I found bad about the Win32 API. Foremost among them was its complexity. The complex security model is likely to be the cause of security problems because only experts are likely to be able to apply it correctly.

A listing of the exported symbols shows that the kernel32 library has about 675 functions, the advapi32 library, which contains the security interface, has approximately 400 functions, and user32, which provides the user interface, contains approximately 600 functions. The development kit has over 225K lines of included files and comes with over 400 additional dynamically linked libraries.

One example of the complexity is illustrated by the CreateFile() interface. It provides the functionality of the UNIX open() function. CreateFile() takes seven arguments.

  1. pathname
  2. access ­ bitmask with three or more bits
  3. share ­ bitmask of read, write, delete
  4. security ­ security descriptor + inheritance
  5. mode ­ five modes
  6. flags_attr ­ 8 flags + 11 attributes
  7. template ­ a file handle

There are at least 33,554,432 possible combinations of options for a given pathname, not counting the security descriptor or template file handle.

The CreateProcess() function, which replaces the UNIX fork() and exec() family of functions, takes ten arguments; some of these are structures containing many members. In spite of the rather large number of flag combinations possible, there is no flag setting that gives the functionality of the exec family of functions. The exec family replaces the current process with a new one. The implementation of the UNIX exec family in U/WIN was one of the most challenging tasks.

Another example of complexity can be found in the registry. The registry appears to be an attempt to put configuration parameters in a single place and to provide fast access to them. The registry is structured like a filesystem but has a separate set of API functions to provide access. The number of registry keys is enormous. and their organization is often hard to predict and even differs between Window 95 and Windows NT.

In addition to complexity, a second bad feature of the Win32 API is its incompleteness. Handles can be inherited or duplicated from one process to another, yet there is no API function that given a handle can determine its type. Although the ReadFile() and WriteFile() functions can perform asynchronous I/O on a handle (in Windows NT only), the CreateFile() call must have created the handle with the FILE_FLAG_OVERLAPPED flag. Moreover, if the handle was created with this flag, then synchronous I/O on this handle is not possible. In spite of these restrictions, given a handle, there is no API to set, or even to test, what flags have been used to create the file handle.

Another example of incompleteness is the inability to handle the complete filename space. In addition to not being able to create case-distinct directories and to a lesser degree files, the Win32 interface doesn't allow certain filenames such as aux.c and com0.sh to be created. Nor is it possible to access or create files that end in a . (dot). The Win32 API does not provide a call to create a hard link even though the underlying NTFS filesystem supports this.

The third thing that I find bad about the Win32 API is its lack of consistency. This might be classified as "ugly" rather than as "bad" except for the fact that I have found it a source of several errors and much wasted time reading manual pages. UNIX is often inconsistent, and it appears that the Win32 designers didn't learn from mistakes. One area in which the Win32 APIs are inconsistent is in the return values from functions. For example, CreateFile() returns -1 for error, and a handle otherwise. The CreateEvent() function returns 0 for error and a handle otherwise. The RegCreateKey() functions returns the error and you pass it the address of the handle.

The argument usage is also inconsistent. The first argument to OpenProcess() is the desired access. This is also true for the OpenEvent() function. However, OpenProcessToken() passes the desired access as the second argument.

Unlike Windows 95, Windows NT provides a UNICODE interface to the operating system, allowing filenames and other resources managed by the system to be stored in UNICODE. Unfortunately, rather than using variable length UFT8 encoding for the UNICODE characters, characters are stored in 16-bit fixed size packets. The result is that the 7-bit ASCII subset is not compatible at the storage level. In addition, programs need to be compiled either for UNICODE or for ASCII rather than a unified version.

Other things that I found bad about Windows NT include its scheduling under load, its need to reboot often, and the single-user mentality. An example of its single-user mentality is that when you are logged on to Windows NT over a telnet session, ftp prompts for the password on the console, not on the terminal.

The Ugly

In addition to the things that I found bad, there are many things about the Win32 interface that I categorize as "ugly." I think many of the programming conventions and practices used in the Win32 API are poorly chosen.

In the previous section I listed some inconsistencies that I categorized as bad. In addition there are inconsistencies in the API that I categorize as ugly, for example, the inconsistent way functions are named. Many functions such as CreateFile() are named by joining a verb and a noun. I don't care for this convention because related functions tend to be scattered throughout the programming manual. (Yes, hypertext helps!) However, the Win32 API does not use this convention consistently. Functions such as RegCreateKey(), DeviceIoControl(), and DosDateToFileTime() do not obey this convention.

Many of the header files needed to build Win32 applications take a great deal of liberty with the namespace, making it more difficult to program. Although some of these defects exist in the UNIX API, new standards have been more careful in avoiding this problem. The Win32 header files seem to use the namespace in a rather random fashion. Words such as IN, OUT, FAR, TRUE, FALSE, DELETE, UNALIGNED, VOID, IGNORE, INFINITE, and MAXCHAR are defined by standard headers. Other define constants such as SP_BAUD, GPRT, PST_FAX, GHND, and LPTR, to name a few, are hard to remember.

Many of the function names in the Win32 interface seem unnecessarily long. This makes programs harder to both read and write. Using long English names provides no positive benefits to non-English-speaking programmers. An example of a long function name is GetFileInformationByHandle(). This function could have been named GetFileInfo() because there is no other API call to get file information other than a call by handle. As a result of long names and the large number of arguments, function calls often require several lines, making it harder for the eye to spot errors.

I dislike the convention of embedding type information for a variable in its name. For example, the structure instance name dwProcess is intended to indicate that the Process element is a double word. This is information that I don't want or need to know. Perhaps a future system will require 64 bits for Process. In this case the name would likely change, and I would have to change my code.

I strongly prefer the UNIX conventions of naming types with a _t suffix rather than using all caps for type names. By convention, symbolic constants are in all caps, and using them for this purpose makes programs harder for me to read.

I dislike defining at least two and sometimes three types for each abstract type. For example, in addition to the subject identifier type SID, there is the type PSID which is nothing more than SID *. The type FILETIME has two additional incantations, PFILETIME and LPFILETIME.

In summary, after over two years of programming to the Win32 API, I still find it cumbersome and hard to use. I still have to often refer to the manual page. I frequently have to write simple little test programs to find out what a library routine will do under certain circumstances. The UNIX API seems a lot easier, and thanks to U/WIN, I can do all my programming using it and still be able to run the programs on Windows systems.

 

?Need help? Use our Contacts page.
First posted: 3rd December 1997 efc
Last changed: 3rd December 1997 efc
Issue index
;login: index
USENIX home