Hart.gif (5380 bytes)Win32 System Programming

Return to Addison-Wesley Home Page  


Return to Top Page

Additional Comments

Author Note: I have found the following notes to be useful when teaching courses based on the book. If you have additional comments or questions, please forward them to me at jmhart@world.std.com.


p. 8

Custer's book has been updated by David A Solomon as a second edition of Inside Windows NT. The edition is updated and expanded to include file system and Version 4.0 information. The ISBN is 1-572-31677-2.

p. 30 (bottom)

The Win32 functions are defined similarly; for example:

    #ifdef UNICODE
    #define CreateFile CreateFileW
    #else
    #define CreateFile CreateFileA
    #endif

The "A" denotes ASCII, and "W" denotes wide characters. This explains why the compiler will generate error messages about CreateFileA if, for example, you use incorrect parameters.

p. 31

It is tempting to call a function such as lstrcmp a "locale-specific" version or strcmp that performs a word rather than string compare. In reality, however, the l actually is for "long pointer" and is a legacy from 16-bit systems so that a programmer could specify the strings with a long pointer.

So, while lstrcmp and lstrcmpi both account for the locale, this "locale-specific" interpretation does not add any value to lstrlen, lstrcpy, lstrcat, and lstrcpyn.

Win32 functions such as CompareString provide considerable power and could be considered in place of lstrcmp. The comments in the on-line help are useful.

p. 42

See atou for additional atou performance comparisons that modify and extend some of the conclusions in the text.

p. 57-58

FindFirstFile and FindNextFile will compare the search file name or pattern against both the full file name and the 8.3 file name. Thus, the pattern may match one or both of the cFileName and cAlternateFileName fields. This may lead to unexpected results. For instance, if the search pattern is TimeM???.exe, you would find the file TimeMutex.exe, which is surprising as you wanted files with exactly 8 characters followed by the .exe extension.

p. 65

For more information on the C Run-Time Library, see Win32 vs. The C Run-Time Library for File I/O. comp.os.ms-windows.programmer.win32 is also an excellent place to go for additional information or to get answers to your questions.

p. 81

Here is the scheme for using completion handlers in a loop body. The example also shows how to determine how the loop body ended.

   While (...) {
      BOOL Flag = FALSE;
      _try {
         ...
      }
      _finally{
         Flag = AbnormalTerm ( );
      }
   }

p. 110

Here is how to use CreateFileMapping and OpenFileMapping in conjunction to share memory. The same scheme applies to synchronization objects (Chapter 11).

Share memory by using pointers pA and pB in Process and Process B, respectively.

The next diagram shows how shared memory operates and how two processes (PA and PB) can obtain a coherent view of the same mapped file. Notice that the file really does not play a role. To assure correct operation using shared memory, you will want to synchronize access to shared variables and be certain to use volatile storage. These topics are covered in Chapter 11. Shared memory is illustrated in the Multiple Wait Semaphore example.

p. 149

The following code fragment illustrates how to redirect the standard output of a child process. Program 8-2 uses this technique.

   STARTUP_INFO si;
   SECURITY_ATTRIBUTES
      sa = {SIZEOF (SECURITY_ATTRIBUTES), NULL, TRUE};
   ...
   hF = CreateFile(..., &sa, ...};
   GetStartUpInfo (&si);
   si.hStdOutput = hF;
   si.dwFlags = STARTF_USESTDHANDLES;
   si.hStdInput = GetStdHandle (STD_INPUT_HANDLE);
   ...
   CreateProcess (..., TRUE, &si);

p. 153

In the UNIX notes in the middle of the page, JDH points out that SIGKILL and TerminateProcess are equally risky; see the comments at the end of the first paragraph.

Page 153-5

Wait Function time-outs. According to discussion in an on-line forum, the time-out period is actually a signed integer, so do not use anything greater than 0x7FFFFFFF. This gives you a maximum of 2 billion ms, or 2,147,483 seconds. That's 596 hours, or 24 days, which should be enough!

p. 155

The following code fragment illustrates how to create a process and close the thread and process handles. Program 8-1 uses this technique.

   ...
   CreateProcess (...&ProcInfo);
   CloseHandle (ProcInfo.hThread);
   WaitForSingleObject (ProcInfo.hProcess);
   CloseHandle (ProcInfo.hProcess);

Page 161

Program 8-2 used, without explanation, the GetSystemTime function. The GetSystemTimeAsFileTime function is equivalent to the following code sequence:

   FILETIME ft;
   SYSTEMTIME st;
   GetSystemTime(&st);
   SystemTimeToFileTime(&st,&ft);

p. 167

Suppose a separate process has shared locks ("S") and exclusive locks ("E") on a file as shown. Then, suppose a second process attempts to read (R) or write (W) as shown. "Y" (yes) and "N" (no) indicate whether the operation succeeds.

Page 175

TOP (UNIX note)

Note that signal handlers must be thread-safe (see Chapters 10 and 11), just as console control handlers (Page 86) are. Also, notice that the timer callback functions at the bottom of the page do NOT execute in a separate thread and do not need to be thread-safe. The same holds for the waitable timers functions that will be discussed next. JDH.

TIMER DISCUSSION.

Program 8-9

If you test beep.c, you will find that it "works fine." However, "works fine" is a relative term, as the expectations on the program are not very high. Notice, in particular, that even though the TIMERPROC executes periodically, you do not return from MessageBox until you push the button, which then disables the timer. Perhaps you want the timer proc to execute periodically while you actually do something useful? If so, you may find it easiest to use threads or processes, as noted in the comments I've added to beep.c. Alternatively, if your program has a GUI and a Windows message loop (which, of course, my programs do not), then you may find this form of timer to be adequate for your needs.

Visual C++ 5.0 introduced the "waitable timers" that are Win32 synchronization objects (See Chapter 11). The sample program shows how to use a waitable timer. You will probably prefer them to the timers described in Chapter 8.

Page 178

Exercise 8-6

This exercise uses the very limited interprocess signaling mechanism so that the user can send a control-c or control-break to the managed process, allowing an orderly shutdown if the managed process has a console control handler. This only works, however, if the managed process shares the console with the managing process. (This seems far too restrictive to me; see the Criticism file.) Furthermore, the managed process does not seem to catch the control-c signal, but it will catch the break signal. This appears to be a Win32 bug.

p. 178

Explanation: Exercise 8-7

I have gotten more questions about this exercise than nearly anything else. Perhaps the following will help.

The idea is that a ProcessId will not be deallocated (and hence be eligible for reuse) so long as there is an open handle on it. But, how can there be an open handle if the process that created the background process (that is, JobBg) terminates, closing the only existing handle? The solution is for JobBg, the first time it runs, to create the "helper" process that "never" terminates. Then, after JobBg creates the normal background process, it duplicates the background process's handle into the helper process. Thus, even after the background process terminates on its own accord, there is still an open handle on it, so the process ID is not reused. JobBg can use DuplicateHandle to achieve this.

The helper process can be enhanced to do all sorts of other useful things by monitoring the background processes.

An alternative solution (which I suggested on the errata page) is to store the background process start time in the job management file. Then, by obtaining the process start time (this only works under NT; not under 95) from the process ID (first get a handle from the ID), we can tell if the ID refers to the original background process or some other process.

p. 184

CreateNamedPipe does not work under Windows 95/98. More generally, only NT systems can act as named pipe servers (this does not hold for mailslot servers). See pipeNP.c for an example.

p. 189

This diagram shows the interaction between the named pipe functions. The arrows indicate that the function at the head of the arrow cannot proceed until the function at the tail of the arrow completes This assumes, of course, that asynchronous I/O (Chapter 13) is NOT being used.

Note, also, that ConnectNamedPipe could fail with ERROR_NO_DATA if the client has terminated or closed its handle. The server should still call DisconnectNamedPipe in this case in order to release the pipe instance. Even more deceptively, there may be data in the pipe that is readable (from JDH). My sample programs do not handle this situation properly.

p. 207

Note that there is no obvious way to obtain a handle from a thread ID that is equivalent to OpenProcess. Therefore, you need to retain all thread handles if you expect to reference the thread in the future.

There is, however, a function NtOpenThread in ntdll.dll. See Chapter 12 to see how to call entry points within a Dynamic Link Library. Once you have taken care of the DLL details, the code is as follows, according to a post from "Peter" on comp.os.ms-windows.programmer.win32, dated July 17, 1998. I have not tested or examined this solution.

     HANDLE hThread;
     DWORD struct1[] = {0X18, 0, 0, 0, 0, 0 };
     DWORD struct2[] = {0, tid};
     NtOpenThread (&hThrad, accessflag, struct1, struct2);

p. 227-238

Synchronization Objects

For an extended example, look at the sample programs, where you will find an implementation of semaphores using events and mutexes.

  1. The test program illustrates a multiple producer/multiple consumer system using a semaphore.
  2. The test program also shows how to use a console control handler to shut down a threaded system and to respond to user-generated signals.
  3. The semaphore implementation, using an event/mutex pair illustrates some non-obvious aspects of Win32 in particular, and thread synchronization in general.

p. 234

There appears to be a defect (in NT Version 4.0, SP 3) in the way that ReleaseSemaphore returns the previous count. If the count is currently at the maximum, and you call ReleaseSemaphore, the call will fail, as it should, but the previous count is the maximum minus one.

p. 234-235

Potential Serious Bug: JDH reports that you can use a zero release value with ReleaseSemaphore and the function does not necessarily fail. He also called with a large negative release value with the effect that all threads waiting on the semaphore usually (not always) wait forever. It appears as if the count really does go negative, and the application program, quite reasonably, is built on the assumption that the count is never negative. Author comment: If this is so, I would classify it as a very serious bug. Independent confirmation is on my to-do list; reader confirmation would be appreciated as well.

For a more general solution to the multiple wait problem, see the Multiple Wait Semaphore sample program.

Several people have asked, "Why can't you use WaitForMultipleObjects using an array of handles where every array element is the same handle?" There are two answers. First, the WaitForMultipleObjects call will fail with a "parameter is incorrect" message; the function does not allow two handles to the same object (you can not avoid the restriction by using duplicate handles), although this fact is not documented. The second problem, ignoring the first, is that the semaphore handle would be signaled, even with a value of one, releasing the thread waiting for a larger semaphore count. JDH reports the same result; he also tried this as a method to create a multiple atomic wait.

p. 236-237

Summary of Event Behavior

Auto Reset Event

Manual Reset

Set Event

Exactly one thread is released. If none are currently waiting on the event, the first thread to wait on it in the future will be released immediately.

All currently waiting threads are released. The event remains signaled until reset by some thread.

Pulse Event

Exactly one thread is released, but only if a thread is currently waiting on the event.

All currently waiting threads are released, and the event is then reset.

Notice that a binary semaphore (its maximum value is 1) has the same effect as an autoreset event that is signaled with SetEvent.

Visual C++ 5.0 introduced the atomic SignalObjectAndWait function. This function requires two handles: the first is signaled, and the thread then waits on the second. There is a thread parameter and an alertable flag (see Page 277). This is an atomic operation, so no other thread (perhaps waiting on the first object) can wait on the second before the thread that calls SignalObjectAndWait. This could clearly be useful where the second object is a mutex. SignalObjectAndWait could be used in the main wait loops of the multiple wait semaphore or the Windows CE semaphore, simplifying the code. Many programmers prefer to do this, but it is not necessary for correct operation of these two examples.

If the first object is an event, it is not clear from the documentation if the even is pulsed or set. I suspect that it is set, but experiments are required to determine for certain.

p. 255-256

Implicit Linking

Using static libraries, executables in the file system and in physical memory look like this for programs P1, P2, …

An implicitly linked DLL shares the code between all the programs.


Appendix C -- p. 338

See atou for additional atou performance comparisons that modify and extend some of the conclusions in the text.

Bibliography

Custer's book has been updated by David A Solomon as a second edition of Inside Windows NT. The edition is updated and expanded to include file system and Version 4.0 information. The ISBN is 1-572-31677-2.