Errata and Updates for Systems Programming for Windows 95 (Microsoft Press 1996)
Page references are to the English edition.
- Crashes in sample programs
- System Control Messages and the Critical Section (p. 231)
- How to do an interlocked add (p. 233)
- Where to find DEVIDS.TXT
- Bugs in SERIAL sample
- Error in _PageReserve Example (p. 349)
- Erroneous reference to 2F/1601 (p. 231)
- Building VxDs with Microsoft Visual C++ Version 4.2
- Missing VXD_IO Macros (p. 404)
- Problems with vdmad.h Header
- Missing statement in dynamic unload example (p. 178)
- Missing WINAPI in OpenVxDHandle example (p. 239)
- PM and V86 API arguments reversed (p. 273)
- Incorrect Control_Dispatch Arguments (p. 171)
- How Big is that RAM Disk? (p. 563)
- How an FSD pends a request
- Signal_Semaphore[_No_Switch] (p. 236)
- R0_FindFirstFile error
- Problems with iosdcls.h Header
- What error to return from failed mount request
- Those pesky linker warnings
- Error in APC example (p. 286)
- Error in initialization sequence (p. 141)
- More about Big Real Mode
- Using Microsoft Visual C++ Version 5
- Problems with CHAP15 samples
- Using VXDINLINE Service Wrappers
- DeviceIoControl parameter usage
- R0_FileRead and R0_FileWrite errors
Walter Oney Software
267 Pearl Hill Road
Fitchburg, MA 01420Tel.: (978) 343-3390
For more information, contact waltoney@oneysoft.com
I can't respond to telephone requests for assistance, but I will answer your e-mail. And please be considerate and not call my 800 number, which costs me over 20 cents per minute!
Crashes in sample programs
Readers are getting various crashes when they run my sample VxDs. There might be several causes for these crashes (in addition to the obvious possiblity that my code just doesn't work.) Check out these suggestions:
- There can be a difference in segment ordering algorithms between various versions of the linkage editor. Please refer to the topic Building VxDs with Microsoft Visual C++ Version 4.2 for instructions about how to correct the problem.
- Some of my function wrappers have errors, specifically VDMAD.H and IOSDCLS.H.
- If you can't find anything on this page that rings a bell, send me e-mail explaining what happened.
System Control Messages and the Critical Section (p. 231)
It's said (p. 231) that System_Control claims the critical section around calls to system control message handlers in VxDs. The critical section is not taken during these messages:Kernel32_Initialized Kernel32_Shutdown Create_Thread Thread_Init Terminate_Thread Thread_Not_Executeable [sic] Thread_DestroyConsequently, the code on p. 213-14, which I assert is thread-safe, would have to use explicit synchronization methods to actually be proof against the creation and destruction of thread control blocks.
How to do an interlocked add (p. 233)
The example on p. 233 of adding one to a memory location is unncessarily complex. It would suffice to use the 486 XADD instruction instead of CMPXCHG:mov eax, 1 xadd idcounter, eaxWhereupon EAX holds the pre-increment value of the "idcounter" variable.
Where to find DEVIDS.TXT
The DEVIDS.TXT file is no longer maintained on the CompuServe PLUGPLAY forum. Look instead in http://www.microsoft.com/hwdev/download/devids.txt
Bugs in SERIAL sample
- The "break" statement in the SETRTS case of CSerialPort::cextfcn is one line too soon. It should follow the "_asm popfd" instead of preceding it. (Thanks to Kevin Vigor of Ascend Communications.)
- The first portion of expression used to set pComstat->BitMask in CSerialPort::GetQueueStatus should read:
(((*m_pMsrShadow & m_OutHHSLines) ^ m_OutHHSLines) >> 4) // fCTSHold (01), fDSRHold (02)The x-or operation in the revised expression results in correctly identifying changes in the handshaking lines.
- The event mask was being incorrectly computed within the HwIntProc of SERIAL.CPP. First of all, the "deltamask" and "linemask" arrays are named backwards. That is, the array I called "deltamask" in the book sample should be called "linemask" instead, and vice versa. Second, the expression which computes the event mask should read:
*m_pEvent |= (deltamask[msr&15] << 3) | (linemask[msr>>4] << 8);The incorrect code in the book sample reflected my misunderstanding about which of the event bits represented the state of a control line and which represented a change in the control line. (The bits whose names end in "S" indicate status.)
- Hardware handshaking lines were being incorrectly tested in the HwIntProc. The statement at line 885 in the sample version of SERIAL.CPP should read:
if (m_OutHHSLines != (msr & m_OutHHSLines))The revised code correctly tests the handshaking bits in the MSR instead of looking for the MSR to have only handshaking bits set.
- The book sample implementation of CPort::Setup slavishly imitated what I think is incorrect behavior in the Microsoft SERIAL sample program. In retrospect, I wish I had coded this routine to not leak memory when called in certain sequences:
BOOL CPort::Setup(PCHAR RxQueue, DWORD RxLength, PCHAR TxQueue, DWORD TxLength) { // CPort::Setup ASSERT_VALID_CPORT(this); m_pd.dwLastError = 0; // no error m_pd.QInCount = 0; m_pd.QInGet = 0; m_pd.QInPut = 0; m_pd.QOutCount = 0; m_pd.QOutGet = 0; m_pd.QOutPut = 0; if (RxQueue || RxLength) { // changing input buffering if (RxQueue) { // external buffer if (m_MiscFlags & MF_RXQINTERNAL) { // release old internal buffer _HeapFree((PVOID) m_pd.QInAddr, 0); m_MiscFlags &= ~MF_RXQINTERNAL; } // release old internal buffer } // external buffer else { // need internal buffer ASSERT(RxLength); if (m_MiscFlags & MF_RXQINTERNAL) { // reallocate existing buffer RxQueue = (PCHAR) _HeapReAllocate((PVOID) m_pd.QInAddr, RxLength, 0); if (!RxQueue) return FALSE; } // reallocate existing buffer else { // allocate buffer 1st time RxQueue = (PCHAR) _HeapAllocate(RxLength, 0); if (!RxQueue) return FALSE; // means no change made m_MiscFlags |= MF_RXQINTERNAL; } // allocate buffer 1st time } // need internal buffer m_pd.QInAddr = (DWORD) RxQueue; m_pd.QInSize = RxLength; } // changing input buffering if (TxQueue || TxLength) { // changing output buffering if (TxQueue) { // external buffer if (m_MiscFlags & MF_TXQINTERNAL) { // delete old internal buffer _HeapFree((PVOID) m_pd.QOutAddr, 0); m_MiscFlags &= ~MF_TXQINTERNAL; } // delete old internal buffer } // external buffer else { // need internal buffer if (m_MiscFlags & MF_TXQINTERNAL) { // reallocate existing buffer TxQueue = (PCHAR) _HeapReAllocate((PVOID) m_pd.QOutAddr, TxLength, 0); if (!TxQueue) return FALSE; } // reallocate existing buffer else { // allocate buffer 1st time TxQueue = (PCHAR) _HeapAllocate(TxLength, 0); if (!TxQueue) return FALSE; m_MiscFlags |= MF_TXQINTERNAL; } // allocate buffer 1st time } // need internal buffer m_pd.QOutAddr = (DWORD) TxQueue; m_pd.QOutSize = TxLength; m_MiscFlags |= MF_TXQSET; } // changing output buffering return TRUE; } // CPort::Setup
- The CSerialPort constructor is reading the wrong registry value because of a typing error. The correct value is named "Settings" (without the extraneous comma inside the literal).
- The line in CSerialPort::inicom which reads "ClrFlag(fEFlagsMask)" should read "ClrFlag(~fEFlagsMask)" instead.
- The book sample doesn't handle a baud rate of 115200 correctly. The first fix is to CSerialPort::CheckState. Where the book sample tested only "rate >= CBR_110" to see if a table index was being used, the code should instead test "rate >= CBR_110 && rate != 115200". There's a similar problem in CSerialPort::EndSetState, where the code should be testing "m_dcb.BaudRate >= CBR_110 && m_dcb.BaudRate != 115200". Both of the problems arise from the fact that 115200 is bigger than 65535 (duh!) and, therefore, bigger than the 16-bit constants used for CBR_110, etc.
- While still on the subject of CSerialPort::EndSetState, handling of the handshaking lines was coded incorrectly. It should read:
BYTE hslines = *m_pMsrShadow & m_OutHHSLines; if (!(m_dcb.BitMask & fWin30Compat) && hslines != m_OutHHSLines) if (m_OutHHSLines == (MSR_DSR | MSR_CTS) && !(m_flags & fUseDSR) && hslines == MSR_CTS) m_HHSLines &= ~MSR_DSR; // ignore DSR else m_HSFlag |= HHSDown | HHSAlwaysDown;- Timeout notifications should only be sent for open ports. Consequently, the code in CPort::Timeout which is testing to see if a notification should be sent in respect of a given port should include an initial test of m_open:
for (port = CPortAnchor; port; port = port->m_next) if (port->m_open // port is open && !(port->m_MiscFlags & MF_CLRTIMER) // timer enabled for port [etc.]
- I found that I was crashing pretty often in CPort::CallNotifyProc due to having a null callback function pointer. Consequently, I replaced the three ASSERT statements (which only do anything in the debug build, of course) with short-circuit tests like this:
case CN_EVENT: if (!m_EvNotify) break; [etc.] case CN_RECEIVE: if (!m_RxNotify) break; [etc.] case CN_TRANSMIT: if (!m_RxNotify) break; [etc.]
- The order of operations in CPort::Close is slightly wrong with respect to handling the timer. The correct shutdown sequence is:
m_MiscFlags |= MF_CLRTIMER; ManageTimer(); if (!trmcom()) return FALSE; // mini-driver couldn't close Release(); // release from contention mgr m_open = FALSE; m_pMsrShadow = NULL; // shouldn't be referenced any more!!
- CPort::SetModemStatusShadow should be copying the current status from the old shadow location to the new one. Thus, before changing m_pMsrShadow, the code should include the following statement:
*pShadow = *m_pMsrShadow;
- CPort::SetCommState should always set the ChangedMask bit corresponding to the BitMask parameter if the BitMask field is indicated in the ActionMask. Otherwise, DTR won't always be initialized when it should be. Specifically, change the line of code which reads "ss(BitMask)" to read as follows:
if (ActionMask & fBitMask) { ChangedMask |= fBitMask; m_dcb.BitMask = pDCB->BitMask; }
Error in _PageReserve Example (p. 349)
The example on page 349 of calling _PageReserve to reserve address space for accessing device memory is wrong. The first parameter should be PR_SYSTEM instead of "firstpage". (Thanks to Richard Scott of Consultancy in Advanced Tech.)The example would also be more helpful if it reminded you to adjust the linear address upwards by whatever offset your physical memory has within its starting page. If I had it to do over again, I would have presented the example as follows:
ULONG npages = (physsize + 4095) >> 12; ULONG firstpage = physaddr >> 12; DWORD linaddr = _PageReserve(PR_SYSTEM, npages, PR_FIXED) | (physaddr & 4095); _PageCommitPhys(linaddr >> 12, npages, firstpage, PC_INCR | PC_WRITEABLE); _LinPageLock(linaddr >> 12, npages, 0);When you later release the memory, call _PageFree with the address "linaddr & ~4095" rather than just "linaddr".
Erroneous Reference to 2F/1601 (p. 231)
Page 231 refers to INT 2Fh, function 1601h. The reference should be to function 1681h instead. This function claims the Windows critical section, as you would guess from the context of the reference.
Building VxDs with Microsoft Visual C++ Version 4.2
Microsoft Visual C++ Version 4.2 corrects the problem that made 4.1 unsuitable for building VxDs. You will need or want to make several changes to my make scripts to use 4.2.
- Modify the rule for ASM files to read as follows:
.asm.obj: ml -coff -DBLD_COFF -DIS_32 -W2 -c -Cx -DMASM6 -Zi -DDEBUG $*.asm editbin $@ -section:_EBSS=.bss$$1 -section:INITCODE=.CRT$$XCA -section:INITEND=.CRT$$XCZI took out the -Zd option that used to be there. It was redundant with -Zi, which is the one you want to use anyhow. The "editbin" line is new. Editbin is one of the tools in the MSDEV\BIN directory.
- Add the following lines to the SEGMENTS directives in the module definition (.def) file:
_XDATA CLASS 'LCODE' PRELOAD NONDISCARDABLE _CRT CLASS 'ICODE' DISCARDABLEAlso remove the SEGMENTS directive for _XCODE$$X.
- Change the -Zd option in every compile or assembly step to be -Zi instead. Also specify an explicit name for the PDB file you want the compiler to generate. The rules should end up looking like this:
.c.obj: cl -c -Gs -Zipl -Fdmyvxd.pdb -Od -D_X86_ -YX -W3 -DDEBLEVEL=1 -DBLD_COFF -DDEBUG -DIS_32 $*.c .cpp.obj: cl -c -Gs -Zipl -Fdmyvxd.pdb -Odb2 -D_X86_ -YX -W3 -DDEBLEVEL=1 -DBLD_COFF -DDEBUG -DIS_32 $*.cpp(In these rules, use the name of your own VxD plus the extension PDB instead of MYVXD.PDB.)
- Modify the link step to use the MSVC 4.2 linker (instead of the DDK linker), to create a PDB file containing debugging information, and (optionally) to use the NMSYM utility supplied with Soft-Ice/W version 3.0. The new rule would read as follows:
myvxd.vxd : $*.def $(OBJECTS) link @<< -machine:i386 -def:$*.def -out:$@ -debug -debugtype:cv -map:$*.map -vxd vxdwraps.clb -nodefaultlib -merge:.CRT=_IDATA -merge:.xdata=_LDATA $(OBJECTS) << c:\winice95\nmsym /translate:source,package,always $@I learned the hard way that the -merge option is case sensitive, so be sure to spell the option exactly as shown above.This step will give you a NSM file containing local symbol and type information.
If you're not using Soft-Ice/W 3.0 yet, retain the previous "-debugtype:map" option and run the old MSYM utility instead of NMSYM.
- Remove the #pragma init_seg statements from any headers or source files. You don't have to do this to fix the problem, but that #pragma no longer does what it used to do in the 2.x series of compilers and might as well come out.
Missing VXD_IO Macros (p. 404)
The example of an IO Table on p. 404 is missing the VXD_IO macros that you use to define elements of the table. It should read:
Begin_VxD_IO_Table iotable VXD_IO 3F8h, TrapXmitHoldingReg VXD_IO 3F9h, TrapInterruptEnableReg [etc.] End_VxD_IO_Table iotable(Thanks to Taejoo Chang)
Problems with vdmad.h Header
The VDMAD.H header on the sample disk contains several errors. For example, VDMAD_Lock_DMA_Region should return 0 to indicate success or a nonzero error code to indicate failure. As coded, however, the wrapper returns "1" in the success case. Additionally, several of the wrappers incorrectly use __declspec(naked). Click here to download a corrected copy of vdmad.h. (Thanks to Ivan Harris)
Missing statement in dynamic unload example (p. 178)
The example on page 178 of how to dynamically unload a VxD is missing one crucial statement. The VXDLDR API expects one of two sets of parameters. BX can hold the unique ID of the VxD to be unloaded. This form of the call isn't very useful, because most dynamic VxDs use Undefined_Device_ID (namely zero) as their identifier. So the alternate form of the call is most often used: set BX to -1 and DS:DX to the null-terminated name of the device.It was the second, alternate, method I was trying to show in the example, but I forgot to set BX to -1. The correct example should read:
mov ax, VXDLDR_APIFUNC_UNLOADDEVICE mov bx, -1 mov dx, offset name call [vxdldr] jc fail(Thanks to Mike Lipney)
Missing WINAPI in OpenVxDHandle example (p. 239)
The OpenVxDHandle example on page 239 should read as follows:
typedef DWORD (WINAPI *OPENVXDHANDLE)(HANDLE); ...The WINAPI keyword is macroed as "__stdcall" in the standard headers files. Without this keyword, the compiler assumes that OpenVxDHandle uses whatever calling convention the rest of your application uses. That's normally __cdecl, but OpenVxDHandle is a __stdcall function like all other Win32 APIs. (Thanks to Geoffrey Levand)
PM and V86 API arguments reversed (p. 273)
The protected-mode and V86-mode API arguments are reversed in the example at the very bottom of page 273. The example should read:Declare_Virtual_Device MYVXD, ..., v86api, pmapi(Thanks to Mike Lipney)
Incorrect Control_Dispatch Arguments (p. 171)
The function names specified in the first two Control_Dispatch macros at the the top of page 171 are reversed. The example should read:Control_Dispatch Sys_Critical_Init, OnSysCriticalInit, sCall,(Thanks to Gary Shooter)Control_Dispatch Device_Init, OnDeviceInit, sCall, ...
How Big is that RAM Disk? (p. 563)
The example on page 563 allocates 2048 pages, which your computationally challenged author claimed is just right to hold 2 MB of space. Since a page is 4096 bytes long, the example is actually allocating 8 MB. (Thanks to Trey Nash)
How an FSD pends a request
On p. 633, I say you should return ERROR_IO_PENDING from FS_ReadFile if you plan to complete the request asynchronously. You should instead return STATUS_PENDING. (Thanks to Todd Snoddy)
Signal_Semaphore[_No_Switch] (p. 236)
The note at the top of p. 236 says that Signal_Semaphore and Signal_Semaphore_No_Switch do the same thing. Namely, both of them return without allowing a context switch to occur. That was certainly true in the beta version of Windows 95 I was using at the time I wrote the book. The released version of Windows 95 implements them differently, however. Consequently, you want to be sure to call the No_Switch version if that's the behavior you want. (Thanks to Byron Atkinson-Jones)
R0_FindFirstFile error
The R0_FindFirstFile wrapper in the IFSMGR.H file on the code disc has an error in it: it fails to load the attribute mask into the CX register as required. The correct wrapper reads:int VXDINLINE R0_FindFirstFile(WORD attrib, const char* pathname, _WIN32_FIND_DATA* fd, PDWORD phandle) { _asm { mov eax, R0_FINDFIRSTFILE mov esi, pathname mov edx, fd mov cx, attrib } VxDCall(IFSMgr_Ring0_FileIO) _asm { jc error mov ebx, phandle mov [ebx], eax xor eax, eax error: movzx eax, ax } }(Thanks to Fred Hewett)
Problems with iosdcls.h Header
The wrapper functions for IOS_Register and IOS_Requestor_Service fail to restore the stack on return from IOS. These two wrappers should read:VXDINLINE VOID IOS_Register(PDRP drp) { _asm push drp VxDCall(IOS_Register) _asm add esp, 4 } VXDINLINE VOID NAKED IOS_Requestor_Service(PIRS irs) { _asm push irs VxDCall(IOS_Requestor_Service) _asm add esp, 4 }(Thanks to Mike Shappell)
What error to return from failed mount request
I said (p. 605) that ERROR_ACCESS_DENIED was a "convenient" error code to return from an FSD whose mount function doesn't recognize a volume. I've learned that this code causes various unfortunate side effects later on. In addition, there's an error code that means just what you want: ERROR_VOLUME_UNRECOGNIZED. (Thanks to Peter van Sebille.)
Those pesky linker warnings
The linkage editor that comes with Microsoft Visual C++ generates a variable number of warnings about mismatched section attributes. The warnings occur because the assembler and the C/C++ compiler use different flags to describe the segments they generate. Although the warnings don't do any harm, they're disconcerting and annoying.From looking at some Microsoft samples, I learned about the undocumented "-ignore" option to the linker. If you include the following additional command-line options in your link script, you can get rid of the warnings altogether:
-ignore:4078 -ignore:4039
Error in APC example (p. 286)
The example on p. 286 shows how to pass the address of two APC (Asynchronous Procedure Call) callback routines named "abegin" and "aend" to the VxD by calling DeviceIoControl. The code should read as follows:DeviceIoControl(hDevice, 1, abegin, 1, NULL, 0, NULL, NULL); DeviceIoControl(hDevice, 2, aend, 1, NULL, 0, NULL, NULL);What's wrong in the book is that I coded "&abegin, sizeof(abegin)" and "&aend, sizeof(aend)" in the application. But in the VxD, I treated lpvInBuffer as being the address of the procedure instead of the address of a pointer variable containing the address of the procedure. Note that it's silly to try to supply a real length for the input data when the VxD is just saving the pointer instead of actually copying some data, which is why the correction shown here uses "1" for the length. (Thanks to Karsten Spriesterbach.)
Error in initialization sequence (p. 141)
The code sample on pp. 141-42 indicates you should call C++ initializers before initializing the BSS section. This is backwards: the BSS initialization should occur first. Note that the code sample in Chapter 16 (devdcl.asm for LPFS) uses the correct order. (Thanks to Yoshida Kazumi.)
More about Big Real Mode (p. 78)
In the discussion of "big real mode" on p. 78, note that all attributes of the segment (not just base address and limit) persist when you return to real mode. Furthermore, reloading the segment register doesn't change the attributes (but it does change the base address) unless you go back into protected mode. (Thanks to Dan Teven.)
Using Microsoft Visual C++ Version 5
To build my samples with Microsoft Visual C++ version 5.0:
- Avoid using the linker that service pack 3 for VC 5 installs. If you've installed this service pack, go find your original disk and copy LINK.EXE onto your hard disk with a name like VXDLINK.EXE. Then go and change all your make scripts to use VXDLINK instead of LINK. (The problem with the SP3 linker is that its symbol information is incompatible with Soft-Ice/W. If you don't care about using that debugger, you can ignore this item of advice.)
- Now follow all of the suggestions earlier about Building VxDs with Microsoft Visual C++ Version 4.2 and about Those pesky linker warnings
Problems with CHAP15 samples
Many people have been telling me about crashes caused by building the sample programs in Chapter 15, specifically the RAMDISK port driver, with Visual C++ version 5.0. I think these problems really stem from two mistakes in my IOS function wrappers. See Problems with iosdcls.h Header above. In addition:
- Be sure to do the stuff described in the immediately preceding entry (Using Microsoft Visual C++ Version 5) if you're using VC 5.
- In addition, add the option "-ignore:4070" to the link script. This suppresses a harmless warning caused by the fact that you're building a file with an extension of .PDR instead of .VXD.
Using VXDINLINE Service Wrappers
Many of the VxD service wrappers in my sample programs use the VXDINLINE directive in their declarations. This symbol is #define'd to be __inline, which sometimes causes the compiler to generate inline code. However, if you combine __inline with __declspec(naked), you end up with code that doesn't work. Specifically, you'll get a VxDJmp in the middle of a program, but there won't be a return address for the target service to return to.Moral: don't use VXDINLINE with C-convention service wrappers that just do a VxDJmp.
DeviceIoControl parameter usage
It's said (p. 270) that you can use the parameters to DeviceIoControl for any random purpose you please. Not so! The input data pointer and count must either be NULL (and zero) or the address and length of a data area to which you have read access. The output data pointer and count must either be NULL (and zero) or the address and length of a data area to which you have write access. KERNEL32.DLL validates these parameters before it ever sends the IOCTL to your VxD.
R0_FileRead and R0_FileWrite errors
The R0_ReadFile and R0_WriteFile macros in my ifsmgr.h follow an incorrect entry in the DDK documentation to the effect that the number of bytes actually read or written comes back in ECX. In reality, this value comes back in EAX. The macros should therefore be as follows:int VXDINLINE R0_ReadFile(BOOL incontext, DWORD handle, DWORD count, DWORD pos, PBYTE buffer, PDWORD nread) { DWORD opcode = incontext ? R0_READFILE_IN_CONTEXT : R0_READFILE; WORD result; _asm { mov eax, opcode mov ebx, handle mov ecx, count mov edx, pos mov esi, buffer } VxDCall(IFSMgr_Ring0_FileIO) _asm { jc error mov ebx, nread mov [ebx], eax <== this is the change xor eax, eax error: mov result, ax } return result; } int VXDINLINE R0_WriteFile(BOOL incontext, DWORD handle, DWORD count, DWORD pos, const BYTE* buffer, PDWORD nwritten) { DWORD opcode = incontext ? R0_WRITEFILE_IN_CONTEXT : R0_WRITEFILE; WORD result; _asm { mov eax, opcode mov ebx, handle mov ecx, count mov edx, pos mov esi, buffer } VxDCall(IFSMgr_Ring0_FileIO) _asm { jc error mov ebx, nwritten mov [ebx], eax <== this is the change xor eax, eax error: mov result, ax } return result; }(Thanks to Monte Creasor)