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 (First Edition Only)
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 (First Edition)/p. 31 (Second Edition)
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.
pp. 57-58 (First Edition)/pp. 58-59 (Second Edition)
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 (First Edition)/p. 66 (Second Edition)
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 (First Edition)/p. 98 (Second Edition)
Here is the scheme for using termination 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 (First Edition)/Material Included in Section Edition (pp. 152-153)
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 (First Edition)/pp. 179-180 (Second Edition)
The following code fragment illustrates how to redirect the standard output of a child
process. Program 8-2 (First Edition)/Program 7-1 (Second Edition) 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 (First Edition)/p. 185 (Second Edition)
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.
pp. 153-155 (First Edition)/pp. 185-187 (Second Edition)
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 (First Edition)/p. 187 (Second Edition)
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 (First Edition)/p. 194 (Second Edition)
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. 166 (First Edition)/p. 71 (Second Edition)
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 (First Edition Only)
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 (First Edition)/p. 195 ff (Second Edition)
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 (First Edition)/p. 205 (Second Edition)
Explanation: Exercise 8-7 (First Edition)/Exercise 7-8 (Second Edition)
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 (First Edition)/pp. 307-308 (Second Edition)
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 (First Edition)/p. 310 ff (Second Edition)
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
in the First Edition/Chapter 14 in the Second Edition) 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 (First Edition)/p. 212 (Second Edition)
Note that there is no obvious way to obtain a handle from a thread ID that is
equivalent to OpenProcess, unless you are running Windows
2000. 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 (First Edition)/Chapter 6 (Second
Edition) 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 (First Edition)/Chapters 9 and 10 of the Second Edition Contain Additional
Examples
Synchronization Objects
For an extended example, look at the sample programs, where
you will find an implementation of semaphores using events and mutexes.
- The test program illustrates a multiple producer/multiple consumer system using a
semaphore.
- 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.
- The semaphore implementation, using an event/mutex pair illustrates some non-obvious
aspects of Win32 in particular, and thread synchronization in general.
p. 234 (First Edition)/p. 257 (Second Edition)
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 (First Edition)/p. 257 (Second Edition)
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 (First Edition)/Material Included in Second Edition (p. 261 ff)
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 (First Edition)/pp. 164-165 (Second Edition)
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 (First Edition)/p. 480 (Second Edition)
See atou for additional atou
performance comparisons that modify and extend some of the conclusions in the text.
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.
|