You are currently offline, waiting for your internet to reconnect

Understanding Pen Driver Functionality Under Windows

Retired KB Content Disclaimer
This article was written about products for which Microsoft no longer offers support. Therefore, this article is offered "as is" and will no longer be updated.
Summary
This article is designed to give the reader a general introduction to thefunctionality of the Windows pen driver by explaining what the source doesand how it does it. It is assumed that the reader has a basic understandingof pen driver functionality and is familiar with the Windows Device DriverKit (DDK).

The files that make up the pen driver can be found in the Windows for PenComputing OEM adaptation kit and in the Windows version 3.1 DDK directory.The files of interest are: DISABLE.ASM, ENABLE.ASM, FILTER.ASM,INSTALL.ASM, LOAD.ASM, MISC.ASM, PLAY.ASM, WACOM.ASM and DIALOGS.C. It isimportant that these source files be read in conjunction with this article.

In addition, this article is meant to be read with the article titled
"Architecture of Windows Pen Drivers"
More information
This article is divided into three sections. The first section providessome background information about the pen driver, the second sectionintroduces the source files used to build the driver, and the third sectiondescribes the pen driver source code in detail.

BACKGROUND INFORMATION

  1. Q. What does a pen driver do?

    A. A pen driver is a Windows installable driver, which means it is a dynamic-link library (DLL) that conforms to certain standards as outlined in Chapter 25 of the Windows version 3.1 Software Development Kit (SDK) "Programmer's Reference, Volume 1: Overview" manual. A pen driver must:

    • Properly handle standard driver messages that get sent to it by Windows.
    • Process certain messages that are unique to the pen driver.
    • Gather information from the tablet hardware, package it in the hardware-independent pen packet structure, and submit it to the PENWIN.DLL for processing.
  2. Q. Where do standard driver messages come from?

    A. The standard driver messages, such as DRV_LOAD, DRV_DISABLE, and so on, are sent from Windows (specifically from USER.EXE) to installable drivers when important system events occur, such as Windows booting up, Windows switching to an MS-DOS box, and when an application wants to start a conversation with an installable driver.
  3. Q. Where do pen-specific driver messages come from?

    A. Some of the messages (such as DRV_GetPenInfo) are often sent by applications that want to get information about the pen driver. Some messages (such as DRV_SetCalibration) are sent by Control Panel applications that want to change some pen driver parameters. Some messages (such as DRV_SetEntryPoints) are sent by the PENWIN.DLL when it is ready to accept pen packets. Pen-specific driver messages come from Windows-based applications and DLLs that know about the pen driver and want to exchange information with it.
  4. Q. Where do interrupts come from?

    A. Interrupts can come to the pen driver from one of three places, depending on the mode of Windows (standard or enhanced) and the mode of the processor (protected or real) when the tablet hardware wants to send information to the IBM-compatible PC. In the most common case (Windows for Pen Computing running on a 386-based computer in Windows enhanced mode), the actual hardware interrupts are trapped and processed by a completely separate virtual driver. Once this virtual driver has assembled a complete pen packet, it notifies the pen driver via an interrupt. The pen driver then has the easy job of performing any adjustments (calibration, filtering, and so on), and submitting the pen packet to PENWIN.DLL.

    In the less common case (Windows is running in standard mode and there is no virtual driver to handle hardware I/O), the pen driver itself must hook I/O interrupts for both protected and real mode. Hardware interrupts will then arrive in both modes, and it is up to the pen driver to collect the tablet information, create a pen packet, switch the processor into protected mode if it isn't already, and then perform the same steps (calibration, filtering, and so forth) as in the enhanced case.

SOURCE FILES

There are nine files in the pen driver source that are needed to buildthis driver. Each file contains logically grouped functions. Thefollowing is a list of the functions that are found in these files:
   Files and Functions            Description of Functions   -------------------------------------------------------   INSTALL.ASM      DriverProc                  Message processing routine   LOAD.ASM      Load                        Initializes variables   DISABLE.ASM      Disable                     Turns off serial interrupts   ENABLE.ASM      Enable                      Turns on serial interrupts      setup_tablet                Makes tablet connection   MISC.ASM      OutTabletChar               Sends character to COM port      OutTabletString             Sends character string to COM port      SetPenSamplingRate          Sets sampling rate      SetPenSamplingDist          Sets sampling distance      GetPenInfo                  Fills PenInfo structure      GetName                     Returns name of driver      SetPenDriverEntryPoints     Gets function addresses      RemovePenDriverEntryPoints  Sets function pointers to NULL      GetCalibration              Returns width and height      SetCalibration              Sets width and height      WEP                         Windows exit procedure   WACOM.ASM      int_stuff                   Global entry point for interrupts      rm_DWPP_cb                  Real-mode pen packet entry location      deal_with_pen_packet        Processes pen packet      do_mouse_event              Causes driver to act like a mouse      deal_with_playmode          Works with PLAY.ASM functions      shut_up                     Prevents out-of-range pen packets      speak_up                    Initiates in-range pen packets      out_tablet_bl               Transmits characters to tablet   FILTER.ASM      FFilterPacket               Removes incorrect pen packets   PLAY.ASM      PenPlayStart                Routines for simulating tablet events      PenPlayBack      PenPlayStop   DIALOGS.C      ConfigDlgProc               Handles configurable dialog box messages      ConfigDialog                Function that loads driver-configurable                                  dialog box      LibMain                     Entry point				
The remainder of this article focuses on the actions of each one of thefunctions listed above.

HOW THE PEN DRIVER DOES ITS WORK

The actions of the pen driver have been divided into three categories:load time work, interrupt time work, and miscellaneous services.

Load Time Work

The pen driver is a DLL. Like all Windows DLLs, when it is loaded intomemory, the first piece of code the Windows loader executes is LibMain,found in the last few lines of DIALOGS.C. This code is uninteresting.

The pen driver is an installable driver, and like all installable drivers,it has a message handler that is sent a DRV_LOAD message the first time itis loaded into memory. The message handler for the pen driver is inINSTALL.ASM. Because the load-time code is complex, the message handlermakes a far call to the load procedure, found in LOAD.ASM. As an aside, themessage handler is definitely not a complex procedure, nor does it use anyspecial register or memory tricks, and it should not be written inassembly. Much of the pen driver should be written in a clearer, easier tomaintain language, such as C.

When the pen driver is loaded into memory, it reads .INI file switches andperforms some DPMI tricks to handle switching from real to protected mode.It does not become involved with interrupts or communications yet; thathappens when the pen driver is enabled.

Lines 119 and 120 of LOAD.ASM load registers with near pointers to strings.The pointers are used a lot when calling GetPrivateProfileInt(). The first.INI file switches read in are the undocumented BeforeLaunch= andAfterLaunch= switches. These switches are useful for debugging pen drivers.They can hold values 0, 1, or 2, and they describe how the pen driver issupposed to behave before and after the PENWIN.DLL has been launched (0means ignore all input from the tablet, 1 means turn all tablet data intomouse events, bypassing Windows for Pen Computing, 2 means turn all tabletdata into pen events so that handwriting recognition can take place). Thedefault values are BeforeLaunch=1 and AfterLaunch=2, which means that thepen behaves similar to a mouse when Windows for Pen Computing is notpresent, and similar to a pen when Windows for Pen Computing is present.

Lines 137-154 read in the pressure .INI file switches. If pressure is notsupported (Pressure=0), then there is no need to read the Inductive= flagand the Force= value. WACOM makes two kinds of pressure pens, inductive andnoninductive. The force value represents how much force (on a scale of 0-70) must be applied to cause a WM_LBUTTONDOWN message to be generated; itdefaults to 35.

Lines 155-159 patch up the PENINFO structure. The PENINFO structuredefaults to "no pressure." If the driver is driving a pressure pen, thePENINFO structure must be patched to describe a pressure pen. First thecbOemData field is set to 2, because there will be 2 bytes (one word) ofOEM data in each pen packet. Then the first slot of the rgOemPenInfo fieldis set to PenDataType=pressure, MaximumValue=70, DistinctValues=70. Thismeans that in addition to the three words of X, Y, Status included in everypen packet, there will be an additional word of OEM data. This is pressureinformation, which is represented by a number from 0 to 69, such that thereare 70 distinct values in that range.

Lines 168-180 check for the Wacom510 switch. The pen driver by defaultdrives a WACOM HD-648A (WACOM 648) tablet, but if the Wacom510 switch isset, the pen driver makes some adjustments to the tablet measurements(lines 172-177) and clears the INTEGRATED bit (line 178) because theWacom510 tablet is not integrated with the display. Line 179 jumps over thecalibration .INI file switches, because it makes no sense to calibrate anopaque tablet with the screen.

If the tablet is not a Wacom510 tablet, however, lines 186-215 read in thecalibration switches. The first four switches (cxRawWidth, cyRawHeight,wDistinctWidth, and wDistinctHeight) override the default values in thePENINFO structure. The last two values, wOffsetX and wOffsetY, are thevalues added to the X and Y components of each pen packet before they aresubmitted to PENWIN.DLL, plus 1000. Thus the pen driver subtracts 1000 fromthe values (lines 208 and 214) before saving them in global variables. Thepractice of adding 1000 to the actual value was started because in someearlier releases of Windows, the pen driver has a difficult time reading innegative values. The actual value 1000 was chosen because if a tablet isoff by more than 1 inch (1000 raw units, and each raw unit = 1/1000th of aninch), then there is a major defect in the hardware that no calibrationprogram can correct. The calibration values are usually written by acalibration program, such as the Calibrate Control Panel applicationdistributed with Microsoft Windows for Pen Computing.

Lines 223-230 read in the maximum allowable change in X (wDeltaXMax) and Y(wDeltaYMax) if filtering is enabled. The filtering algorithm is explainedin a later section.

Lines 237-248 read in the Com2= flag, and depending on the value, someglobal variables dealing with serial communications are set.

Lines 256-261 read in the DisplayOrientation= value from the SYSTEM.INIfile. Lines 262-287 deal with the value, rotating the X and Y measurementsof the tablet properly if the display is rotated from the default 0, 0,which is the upper-left corner.

Lines 293-296 write more global variables. Lines 302 and 303 get a selectorthat points to the same memory as the pen driver's data selector but hasthe code attribute set. This is done because the interrupt handling code isin the data selector, but the pen driver needs a code selector for someservices.

One of the important variables in the pen driver is rglpfn_DWPP, which is atwo-element array of long pointers to the function deal_with_pen_packet.When the pen driver is processing an interrupt, and has assembled a penpacket, it will call rglpfn_DWPP[0] if the driver is in protected mode, andrglpfn_DWPP[4] if it is in real mode. Lines 309-311 fill in the first 4bytes of this array with the selector and offset of the protected modeaddress of deal_with_pen_packet.

Line 317 saves the protected mode data selector in a global variable. Thereason for this will be clear when discussing the interrupt handler. Lines323-327 convert the protected mode data selector of the pen driver into areal mode data segment. This value gets stored 4 bytes past the protectedmode data selector in the DS_reg global variable. Again, the reasons forthis will be clear when discussing the interrupt handler.

Lines 333-342 allocate a real mode callback via the magic of DPMI. Severaltimes this document has mentioned "switching from real mode to protectedmode." The actual procedure for switching from real to protected mode isdone through a real mode callback. When allocating a real mode callback,ES:DI points to a protected mode procedure that is run when the processoris switched from real to protected mode. DS:SI points to a protected modedata structure where DPMI can place the real mode register values when theprocessor switches from real to protected mode. Finally, AX contains theservice number of the "allocate-real-mode-callback" routine. After callingDPMI via a software interrupt, CX:DX points to a function that, if calledfrom real mode (that is, CX is a code segment, not a code selector), willswitch the processor into protected mode and jump to the procedurespecified in ES:DI. The CX and DX registers are saved in rglpfn_DWPP,starting at byte offset 4 (lines 349 and 350).

Lines 352-356 verify that Windows is running in protected mode (which itmust if Windows 3.1 is running, and Windows 3.1 is required for Windows forPen Computing). If the load procedure returns a 0 in DX:AX, the installablepen driver will be unloaded from memory.

It is worth noting that once again, a procedure was written in assemblythat should have been written in C. The only tricky part of the code is theDPMI code in lines 333-343, and if that can't be done with int86() calls,assembly code could be inserted with _asm {} directives. For ease ofmaintenance, this code should be rewritten in C.

The next message the pen driver gets is DRV_ENABLE. When Windows is runningin enhanced mode, the DRV_ENABLE message gets sent once, soon after the pendriver is loaded into memory. It gets the corresponding DRV_DISABLE messageonce, when the pen driver is unloaded from memory as Windows shuts down.The situation is similar when Windows is running in standard mode, exceptthat a DRV_DISABLE message gets sent to the driver every time Windowsswitches to an MS-DOS box, and it gets a DRV_ENABLE when Windows switchesback to Windows.

The enable procedure starts at line 99 of ENABLE.ASM. Lines 101-103preserve some registers on the stack, and lines 105 and 106 initialize twovariables used in the hardware-detection algorithm used later.

Because the enable routine hooks interrupts while preserving the oldinterrupt vectors, it would be disastrous if the enable routine was calledtwice without a disable call in-between. It would also be a bad idea tohook interrupts if the DRV_LOAD message hasn't been called once. The twoflags fPenExists and fPenEnabled keep track of the message state, and if,for example, load has not been called or if the pen interrupt vectors havealready been hooked, the enable procedure is aborted.

The WACOM pen driver drives serial hardware, so much of the enable routinedeals with the intricacies of PC serial communications. One of the bestdescriptions of PC serial communications is Chapters 6 and 13 of "The MS-DOS Encyclopedia," published by Microsoft press.

Lines 133-141 turn off serial interrupts at the 8259 chip, and save theprevious state of the chip. Please note the use of CLI and STI on lines 134and 140. If an interrupt occurred between these lines, and the interrupthandler modified the bits in the MASK_PORT, then the pen driver wouldoverwrite any changes the other interrupt handler made.

Lines 144 and 145 check to see if enhanced mode Windows is running in thissession. If it is, then the virtual pen driver hooks all tablet interrupts,and there is no need for the pen driver to hook any. Thus, line 145 willskip over the interrupt hooking process if the WF_ENHANCED bit is set.Because the pen driver only saves the low word of the Windows flags in wWF,line 143 verifies that the WF_ENHANCED bit is still in the low word of theWindows flags double word. If in some future release of Windows theWF_ENHANCED bit moves out of the low word, this file will generate an errorand not compile, and the code will have to be rewritten.

Lines 147 and 148 skip over the interrupt hooking routines if theinterrupts have already been hooked. The variable fPenEnabled ismisleading, and should probably be renamed fInterruptVectorsHooked or justfIntsHooked.

Lines 154-158 save the old protected mode interrupt vector inlpOldPModeVector. Lines 164-172 set the new protected mode interruptvector. Notice how the selector is IntCS, the alias to the pen driver'sdata selector. The offset is pmode_int, a label in WACOM.ASM.

Lines 178-182 save the old real mode interrupt vector, using DPMI to get atreal mode features from the protected mode thread that the pen driver isrunning in. Lines 188-192 set the new real mode interrupt vector, againusing DPMI. Notice how the segment is the real mode data segmentcorresponding to the pen driver's data selector, and the offset is thelabel rmode_int, again found in WACOM.ASM. There was no need to convert thedata segment to a code segment, because unlike protected mode selectors,real mode segments do not have code or data properties.

Line 195 sets the fPenEnabled (which should be renamed to something moreappropriate such as fIntsHooked).

Lines 201-230 set various parameters of the PC serial communications. Bythe time line 230 is executed, enough of the communications parameters havebeen set that the pen driver can send information to the tablet. Line 237calls the hardware-dependent routine setup_tablet to put the tablet in theproper state.

Setup_tablet (lines 429-478) first resets the tablet with the "RE" command(line 436). According to the WACOM documentation, after a reset, a drivermust wait before sending further commands, so lines 442-451 busy wait.These lines look at the time value in BIOS, but should be rewritten to useWindows's GetTickCount() instead. Line 458 sends more tablet commands, andlines 464-467 put the tablet into pressure mode if the .INI file indicatesa pressure pen is being used. Finally, setup_tablet sets the pen samplingrate (line 474) by calling SetPenSamplingRate (which is explained later).

After setup_tablet returns, execution continues at line 243. Interrupts atthe 8250 chip are disabled (243-247) to be sure no interrupts arrive whilethe PC communications are being changed. Lines 243-257 set DTR and RTS tothe appropriate values, and then a character is read in from the I/O port.It is a quirk of the PC I/O architecture that if a character is not read atthis point, I/O will not occur. Lines 272-291 are code lifted from theserial mouse driver. It is not known exactly what this code accomplishes.

Lines 297-303 enable interrupts at the 8259 chip (note again the CLI andSTI on lines 299 and 303), lines 309-312 enable Data Ready interrupts onthe 8250 chip, and lines 318-324 raise OUT2 on the 8250 chip, which isnecessary for the 8250 chip to generate serial interrupts. At this point,the tablet should be generating interrupts and the interrupt handler shouldbe processing them.

It is now time to notify the virtual pen driver that the pen driver exists,is ready to accept interrupts, and give the virtual pen driver severalpieces of information it needs. The virtual pen driver needs to be notifiedonly once, so lines 330-332 ensure that the VPenD initialization occursonly once.

For the pen driver to call the virtual pen driver, the pen driver needs toknow the address of the application programming interface (API) procedureof the virtual pen driver. A virtual driver's API procedure is similar tothe DrvProc message handler of an installable driver--it is a place whereother pieces of code can call the virtual driver and make requests forservices. Lines 334-343 ask enhanced mode Windows (line 338) for the APIentry point (line 336) of the virtual pen driver (line 337). The address (aprotected mode long pointer) is placed into ES:DI. If this value is 0:0,there is no virtual pen driver to initialize and the next few pieces ofcode are skipped (line 342).

If there is a virtual pen driver, then it needs to be told three things:the address of the PenInfo structure (which describes the pen), the addressof the emode_int procedure to call when the virtual pen driver hasassembled a pen packet, and the address of a pen packet structure to befilled by the virtual pen driver and used by the pen driver. These threepointers are put into a structure (vpend_data) that both the virtual pendriver and pen driver know about.

Next, the pen driver calls the virtual pen driver's API procedure.Remember, the address of the API procedure is in ES:DI, but the 386architecture has no "call ES:DI" instruction. Rather than do somethingsimple such as saving ES:DI in a dword and doing an indirect call, the pendriver performs some stack trickery to save 4 bytes of data space. Thisshould be rewritten to make the code easier to understand.

The plan is to set up the stack to make it appear as if the pen driver madea far call to the API procedure, and then jump to the location at ES:DI.First the "return address" is pushed onto the stack. This address isCS:E_done_VPenD_init, which is at line 381. Lines 368 and 370 push thisvalue onto the stack. At this point, the pen driver wants to jump to thelocation in ES:DI, but there is no "jmp ES:DI" instruction either, so thepen driver instead pushes ES:DI on the stack (lines 371 and 372) and laterexecutes a far return, which causes the 386 processor to pop a long addressof the stack and jump to it. Line 374 loads AX with the VPEND_ENABLEmessage/service request, and line 375 loads SI with the offset of thevpend_data structure. This simple protocol is between the pen driver andvirtual pen driver only, and any OEM is free to change it, add moreservices, and so forth. Finally, line 378 executes a far return, whichcauses the processor to jump to the location that was in ES:DI; when thatprocedure returns, the processor will jump to the next location on thestack, namely CS:E_done_VPenD_init. Notice how it took several lines todescribe how the pen driver did a register indirect far call and saved 4bytes of data. This code should be rewritten.

Lines 383-385 read in a character from the COM port. This is probablyunnecessary, and should be removed.

Now the enable routine is mostly done. If all I/O has been performedproperly, the tablet should be generating interrupts and the interrupthandler should be processing them. It is our experience at Microsoft,however, that all I/O is not always performed correctly. Sometimes tabletsare unplugged when a desktop computer is first booting up. Sometimes acharacter is sent as an "R" and gets received as a "P". This means that thetablet might not be generating interrupts, and the tablet will appear deadto the user.

Every time the pen driver processes an interrupt, it sets the fGotAnIntflag to TRUE. At this point (line 387), the enable routine waits for thefGotAnInt flag to be set. If after 20 ticks of the BIOS clock an interrupthas still not been detected, the pen driver tries to initialize the tabletagain by jumping to E_enable_tablet again. After attempting to initializethe tablet cEnableTry times without success, the enable routine gives up.

Please note that the WACOM tablet has been configured such that the tabletwill generate interrupts even when the pen is not near the tablet. This isoften not the case with other OEM hardware, and thus if those OEMs do nottake steps to modify this code, they will always wait 20*cEnableTry ticksbefore finishing with the enable routine. This is a noticeable (on theorder of seconds) and annoying delay when booting up Windows. All OEMsshould be sure they understand this looping code and remove it if theirhardware is not capable of supporting interrupt generation verificationlike the WACOM tablet can.

The opposite of DRV_ENABLE is DRV_DISABLE. This message is sent to the pendriver when standard mode Windows switches to an MS-DOS box and whenWindows shuts down. The disable handler is supposed to undo the work of theenable handler, and leave the system in more or less the same state asbefore the driver was enabled.

When the driver gets a DRV_DISABLE message, it calls the disable routine,starting at line 58 of DISABLE.ASM. Similar to the way the enable routinechecks for reentrancy, the disable routine checks on line 64 to make surethe pen is enabled right now, and if it isn't, skips over the disable code.

Because there is no WACOM MS-DOS-mode pen driver to go with the WACOMWindows pen driver, the pen driver then disables serial interrupts in everypossible way so that no tablet interrupts will slip through to MS-DOS. Ifthere was an MS-DOS-mode WACOM pen driver, the Windows and MS-DOS pendrivers might want to agree on leaving the PC's I/O hardware in aparticular state.

Lines 72-76 disable interrupts at the 8250 chip. Lines 82-88 clear theobscure OUT2 bit on the 8250 chip to ensure that interrupts are disabled.Lines 94-98 also turn interrupts off at the PIC. There is a bug in thissection of code; an interrupt could arrive between lines 95 and 98 on someother hardware device, that interrupt handler could modify the PIC bits,and then line 98 would overwrite its work. A PUSHF and CLI should beinserted before line 95, and a POPF should be added after line 98 torestore interrupts to their previous state.

Now that interrupts from the serial port have been totally disabled, theprotected (lines 104-111) and real (lines 113-117) mode interrupt vectorsare restored to their previous state.

Lines 124-131 restore that mysterious BIOS table, but there is a bug inthis code too. This code and the corresponding code in ENABLE.ASM use thevariable wBIOSPortIndex to denote whether any BIOS work has been performed.If the value is 0, no work has been done/needs to be undone. Unfortunately,0 is also the rs232 offset in the BIOS table for one of the COM ports.Thus, if work is performed on the first COM port in the BIOS table, theoffset 0 is written into wBIOSPortIndex, and then the work will not beundone by DISABLE.ASM. One possible solution is to add an INC AXinstruction on line 289 of ENABLE.ASM, and DEC CX on line 126 ofDISABLE.ASM. If the code is left in its current state, devices on COM1 willstop working after running standard then enhanced mode Windows.

As usual, DISABLE.ASM should probably be rewritten in C forreadability.

Interrupt Time Work

When an interrupt occurs in any of the three processor modes (enhancedprotected-mode virtual interrupt, standard mode protected-mode interrupt,protected-mode real interrupt), it jumps to an entry point near the top ofWACOM.ASM.

As noted before, all of the code that is run at interrupt time is in thedata segment. Line 54 of WACOM.ASM begins the data segment, and it does notend until line 809, at the end of the file. In retrospect, the savingsgained by putting the code in the data segment probably do not justify theconfusion this has caused.

The interrupt handlers all follow the same strategy. First, try to create apen packet. Second, get the processor into the proper mode (protectedmode). Third, call deal_with_pen_packet.

Emode_int, on line 124 of WACOM.ASM, is the entry point for the enhancedmode virtual interrupt passed to the virtual pen driver as part of enable.When this code runs, there is already a pen packet in the pp (pen packet)variable (it was put there by the virtual pen driver), and the processor isalready in protected mode. Thus the work of this interrupt handler isrelatively easy.

Because the virtual pen driver does not reenter emode_int, line 125 enablesinterrupts. Lines 126 and 127 save the BP register and fill it with thevalue 2. Throughout the interrupt handler, the BP register is used to keeptrack of what mode the processor was in when the interrupt occurred;0=standard protected, 2=enhanced protected virtual, 4=standard real.

Lines 128-130 save the other registers, and line 131 gets the protectedmode data selector by referencing the DS_reg offset from the code segment.Now that the DS register has the proper value, the flag fGotAnInt can beset so enable will know interrupts are flowing.

Then line 134 calls deal_with_pen_packet, registers are restored, and theinterrupt handler executes an IRET to return control back to the virtualdriver.

When an interrupt arrives in standard real or standard protected mode, thepen driver must perform I/O to read the byte of information off the tabletand attempt to construct a pen packet. Real mode interrupts arrive at line153, and protected mode interrupts arrive at line 169. In both cases, theyrestore interrupts (there won't be any more serial interrupts until an EOIis sent to the PIC), the BP register is saved and loaded with the proper (4or 0) value, and execution goes to the int_286 label on line 185.

Lines 186-188 save important registers, and line 189 loads the propersegment (real mode) or selector (protected mode) into the DS register byindexing off of the BP register.

Lines 196-201 verify that a character is ready on the I/O line. Sometimesinterrupts are generated for other reasons, and the pen driver does notwant to read in an invalid value off the serial line in that case.

Lines 213-217 read a character into the AL register, and then check fordata overrun. If the tablet hardware has generated information faster thanthe PC can process it, then serial data overrun occurs and the OR bit getsset in the LSR register of the communications chip. In that case, executiongoes to line 204, where all collected tablet data is thrown out, and theinterrupt thread returns.

Lines 223-267 put the byte into the appropriate location in rgbByteBuffer,while taking the sync bit status into account. COUNT_ERRORS (lines 231 and252) is an assemble-time option that records unexpected-sync-bit andunexpected-nonsync-bit errors into reserved word areas of the PenInfostructure. When testing a driver on new hardware it is often useful toenable this feature and note how error-free the serial communications are.It might be useful to monitor overrun errors also.

Line 273 checks to see if the enough information has been pulled off thetablet to construct a pen packet. If not (line 275), the interrupt routinereturns. If 7 bytes of information have been pulled off the tablet, theibByteBuffer index is reset and the fGotAnInt flag is set. fGotAnInt shouldprobably be renamed to fGotAPacket, because by this time at least 7interrupts have been serviced.

Lines 285 and 286 check the WACOM's OutOfRange bit. If it is set, the X, Y,and Status fields do not need to be processed. The pp (pen packet) gets itsPDK_OUTOFRANGE bit set, and the code jumps to the WI_constructed_pen_packetlabel.

Otherwise, lines 302-322 convert the tablet data into X and Y coordinates,and lines 328-352 set the PDK bits of the pen packet and maybe the pressurefield of the first OEM Data field too.

At line 359, a valid pen packet has been constructed and put into the ppvariable. Now the interrupt handler calls either deal_with_pen_packet(protected mode) or the real mode callback function (real mode), whichswitches to protected mode and calls deal_with_pen_packet. When this callreturns, if the processor is in protected mode, an EOI has already beensent to the PIC by DWPP (deal_with_pen_packet). Line 361 checks forprotected mode, and if it is true, skips over the EOI code on lines 366-370. Lines 371-375 restore registers and return from the interrupt.

If the processor was in real mode when the interrupt occurred, line 359will call into an obscure location inside DMPI's tables, and execution willpop out at line 397 with the processor in protected mode. Lines 397-404simulate a far return in the real mode thread's register set, which it mustdo so that the real mode thread continues execution at the instructionafter the call on line 359. Line 405 sets the real mode's BP register to 0,so it will not send an extra EOI to the PIC.

When the real mode callback routine is entered, ES:DI points to therm_callback structure that holds information that DPMI needs to properlyreturn back into real mode. If this routine is reentered, the oldrm_callback values will be overwritten and the pen driver will most likelycrash. To prevent this, the pen driver copies the current rm_callbackstructure into an empty slot in a tablet of rm_callback structures.

Lines 411-417 search the table for an empty slot. If none can be found,lines 422-425 send an EOI and quickly return to protected mode because thisinterrupt cannot be processed. There is a bug here. There is a slightchance that 7 tablet interrupts could arrive after the EOI is sent, thelast one could be in real mode, and the real mode callback routine could bereentered. While the chances of this occurring are remote, it could happen,and then the pen driver could crash. What the code ought to do at thispoint is instead of sending an EOI, merely set the real mode's BP registerback to its original value of 4, and then return. Using this method, thetest on line 362 would fail, and the pen driver would have interruptsdisabled (the CLI on line 366) from the EOI to the end of the interrupthandler.

In the more common case, the pen driver is at line 428, where an empty slotis found and marked as full. After the increment on line 429, DS:BX pointsto the space where the rm_callback structure will be copied. This valuegets saved on the stack (lines 431-432) for future reference, and then therm_callback structure is copied (lines 433-437).

Line 439 zeros the BP register so DWPP knows this is an I/O handlingthread, rather than the non-I/O virtual interrupt thread (when BP=2). Line441 calls DWPP, and then lines 443-444 restore the rm_callback pointer intoES:DI, as DPMI requires for a return. Line 445 disables interrupts, becauseif an interrupt arrived between marking the slot as empty again (line 446)and the return to DPMI and real mode (line 449), there is a slight chancethe rm_callback structure in ES:DI could be overwritten.

Deal_with_pen_packet, which starts on line 474, is where the threeinterrupt threads come together. It is in this procedure that the penpackets are finally passed to PENWIN.DLL.

Lines 474-495 rotate the X and Y values in the pp structure based on theorientation of the screen. Lines 501-531 calibrate the X and Y fields ofthe pen packet and convert the values from the raw coordinate system (thevalues coming off of the tablet) into tablet units (thousandths of aninch).

Line 537 makes DS:SI point to the pen packet. This is what PENWIN.DLLexpects.

Lines 538-546 (which are only assembled in if the PLAY directive isdefined) check to see if the pen driver is playing back pen packets ratherthan generating new ones. If the pen driver is in play mode, it callsdeal_with_play_mode (line 541), which will replace the current pen packetwith the one that will be played back. This function can choose to abortthe current pen packet (if there is nothing to play) by returning 0; if itdoes, the pen driver jumps to the end of DWPP.

Lines 547-551 are some more conditional code that call a filteringalgorithm. Again, if the filtering algorithm returns 0, the current penpacket is not sent to PENWIN.DLL.

Lines 552-568 use special features of the WACOM tablet to guarantee that anout-of-range event generated by the tablet will not get lost due to anerroneous byte in serial communications. Most tablets generate only one out-of-range event when the pen goes out of range. This is known as"shut-up" mode. Some tablets generate interrupts at the proper samplingrate even when the pen is out of range. This is known as "speak-up" mode.

If an out-of-range event gets lost due to serial overflow, faulty wiring,or a slow CPU unable to keep up with the tablet hardware, then the pen willbe out of range but the operating system will think the pen is still inrange. The operating system might even think the pen is still touching thetablet. This can cause many problems such as stuck buttons, recognitionappearing to hang, and so forth. The NCR pen driver for the NCR 3125 tabletdoes exactly this, and received a terrible review in a recent (August,1992) issue of INFOWORLD.

The WACOM pen driver gets around this problem by putting the WACOM tabletinto speak-up mode when the pen is in range, and putting the tablet intoshut-up mode (for efficiency reasons) only when a given number(OUT_COUNT_MAX) of out-of-range packets have been processed. Once the penhas been moved back in range, the pen driver puts the tablet back intospeak-up mode a number of times (IN_COUNT_MAX).

By the time the pen driver gets to line 572, it has a pen packet in the ppvariable and must do something with it based on the BeforeLaunch= andAfterLaunch= values specified in the .INI file and stored in the rgbToDoarray. Line 572 puts the fLaunched flag into BX (true if PENWIN.DLL hasbeen launched, false if it hasn't), loads the appropriate action into AL(line 573), and then branches based on the AL register.

If it is 0, the pen driver jumps to the end of the DWPP routine. If it is1, it calls do_mouse_event, which turns the pp (pen packet) into a mousepacket and calls MOUSE_EVENT in USER.EXE. If it is 2, it calls theAddPenEvent entry point in PENWIN.DLL, sends an EOI to the PIC (if the I/Ois being handled by the pen driver and not the virtual pen driver) to allowfurther pen interrupts, and then calls ProcessPenEvent in PENWIN.DLL.AddPenEvent is documented as being small, fast, and nonreentrant, whileProcessPenEvent is documented as being big, slow, and reentrant so tabletinterrupts can be safely turned on (and should be turned on) after the callto AddPenEvent.

The do_mouse_event procedure starts on line 614. Its mission is to take apen packet in DS:SI and call user's MOUSE_EVENT entry point with theregisters set to the appropriate values for the call. The first thingdo_mouse_event checks for is out of range. If the pen is out of range,there is no need to send anything to user. Next, lines 617-631 convert thepen packet's X and Y values in tablet coordinates (thousandths of an inch)into normalized (0000H-FFFFH) coordinates and place them into BX and CX.Lines 633-641 put the button and Absolute-Movement flags into the AXregister, and lines 643-646 set the rest of the registers to theappropriate values. After a call to MOUSE_EVENT on line 647, do_mouse_eventreturns. MOUSE_EVENT is documented in the MSDN.

Lines 667-746 implement the deal_with_playmode procedure. It is up to thisprocedure to make DS:SI point to the pen packet to be played back toPENWIN.DLL, or to return 0 in AX to prevent any pen packet from beingplayed back during this interrupt. Lines 667-669 make the obvious check tosee if the pen driver is in play mode. If it isn't, it returns success;that is, play the pen packet that is already in DS:SI.

Lines 667-669 check to see if the pen driver is still playing pen packetsthat were passed to it via the DRV_PenPlayBack message. If there are nomore pen packets to be played, because the buffer of pen packets is emptyand the fPlaying flag was set to FALSE, then deal_with_playmode returns 0;that is, don't play any pen packets now.

Lines 675 and 676 get ES:DI to point to the next pen packet in the buffer.Pen packets in the buffer can be one of two types, pen packets that aremeant to be submitted to PENWIN.DLL in sequence, and pause packets. Pausepackets have the PDK_PAUSE bit set in the wPDK field, and the Y and Xcoordinate values together describe how many milliseconds the pen packetplayer must pause before submitting more pen packets. If the current penpacket is a pause packet, lines 680-696 take care of checking the clock tosee if the requisite number of milliseconds have passed. If they haven't,the pen driver returns a 0 (line 704) to abort the current pen packet anddoes not increment the ibPlay index. If enough time has elapsed, the pendriver increments the ibPlay index (line 699) so next time the next penpacket in the queue will get processed. Line 680 ought to callGetTickCount, and not the obsolete GetSystemMsecCount.

If the pen packet in ES:DI was not a pause packet, but rather a normal penpacket meant for PENWIN.DLL, the pen driver copies the pen packet fromES:DI to DS:SI (lines 708-726). This takes a lot of register manipulationsbecause the 8086 instruction movsb assumes the source is in DS:SI and thedestination is in ES:DI. Finally, the AX register is set to return true(line 727).

Lines 730-732 check to see if the pen packet play buffer is empty or not.If it is, the fPlaying flag is set to FALSE (line 733), and the currenttick count (again, line 734 should use GetTickCount) is put into thepointer passed to the pen driver as part of the DRV_PenPlayStart message.

Lines 756-807 implement procedures that put the WACOM tablet into speak-upand shut-up modes. The routine to output a character to a tablet had to beduplicated here (lines 794-806) because the regular OutTabletChar routineis not in a locked selector and may not be available at interrupt time.

The only other code that might be executed at interrupt time is theFFilterPacket routine in FILTER.ASM. This routine implements a simplefiltering algorithm. Appropriate filtering is unique to each brand ofhardware, and this algorithm will most likely be customized by the carefulOEM.

The algorithm is to maintain a queue of valid pen packets, where valid isdefined as not being more than 1 inch away from the previous pen packet.When the queue reaches a certain size (3), the filtering algorithm submitsthe pen packets from the head of the queue.

Lines 47-52 of FILTER.ASM copy the new pen packet to the tail of the queue.Lines 59-64 set the SI register to point to the current pen packet, and theDI register to point to the previous pen packet, for comparisons. Lines 71-93 verify the X and Y coordinates are valid, and lines 99-103 make sure thebutton status has been the same for the last few pen packets. If the penpacket fails any of these tests, then the queue is cleared of all other penpackets, the head and tail pointers of the queue are set appropriately(lines 154-162), and the routine returns a 0. Otherwise the new pen packetis entered into the queue by incrementing the tail (lines 110-115); if thequeue is not full (lines 122-124), there have not been enough valid penpackets in a row and the routine returns 0 (line 125). If the queue isfull, it is time to submit the pen packets by making DS:SI point to thehead of the queue (line 133), incrementing the head pointer (lines 135-141), and returning a 1 (line 143). Throughout the increments anddecrements of the circular queue pointers, special attention has been paidto wraparound.

Miscellaneous Services

Miscellaneous services include both internal functions and the remainingdriver message handlers. They can be found in MISC.ASM, PLAY.ASM, andDIALOGS.C.

MISC.ASM contains most of these functions. OutTabletChar, lines 108- 119,sends one character to the tablet, by waiting for the 8250 transmitregister to be empty (lines 108-114), and then sending out a character(lines 116-119). OutTabletString, on lines 142-165, sends an entire null-terminated string to the tablet, which is useful for the long strings sentto the tablet at enable time.

SetPenSamplingRate, lines 191-231, handles the DRV_SetPenSamplingRatemessage. This function must return the old rate of the tablet, so thisvalue is first placed on the stack on line 191. Lines 202-212 try to findthe nearest WACOM-supported sampling rate equal to or above (or below ifthere is no rate above) the rate given in the argument. The supportedsampling rates (which for the WACOM tablet are the integral dividends of200) are kept in a tablet (rgwRateTable) for fast lookup. Once the properWACOM supported rate is found, the proper command is constructed and sentto the tablet (lines 221-226). Lines 228-231 store the new rate in thePenInfo structure and pop the old sampling rate into the return value ofthe function.

Lines 257-278, SetPenSamplingDistance, perform a similar operation with thesampling distance of the WACOM tablet. Line 257 saves the old distance onthe stack, lines 258-262 compute the distance that will be used (that is,clip the requested value to the hardware), line 266 saves the new value inthe PenInfo structure, lines 268-273 construct and send a tablet command,and lines 277-278 pop the old distance into the return value.

The DRV_GetPenInfo message handler is implemented on lines 311-328. Line311 checks to see if the input parameter is null, which means the caller isonly interested in the presence or absence of the tablet. Lines 315-319copy the PenInfo structure to the long pointer argument, and lines 327-328always return true. This function has several problems. First, if aninvalid pointer is passed to this function, it will GP (general protection)fault, which is unacceptable. This function should call IsBadWritePtr firstand verify that the specified memory area can be written to. Second, thisfunction always returns true, even if the tablet is not attached to thehardware. This function should probably return the value of FGotAnInt.Better yet, the load routine or enable routine should have sent a WACOMdiagnostic string, checked for a valid return result, and stored theexistence/nonexistence of the tablet in a global variable. In the case ofdrivers for tablets that are part of the computer, always returning true isacceptable.

Lines 338-359 implement DRV_GetName. This simple routine writes as much ofa predefined string as it can into its parameter, and returns the number ofcharacters written in DX:AX. Again, this function should also callIsBadWritePtr to verify the string copy won't crash.

SetPenDriverEntryPoints (lines 414-456) handles the DRV_SetPenEntryPointsmessage that PENWIN.DLL sends to the pen driver when it is ready to receivepen packets. Before this message gets sent to the pen driver, the pendriver must not make any references to PENWIN.DLL or submit any pen packetsto it. Any events that come off of the hardware before this message getssent must either be thrown away or (more likely) turned into mouse eventsand passed off to USER.EXE. Lines 414-423 get a module handle to the pendriver (by default PENWIN.DLL) by calling OpenDriver andGetDriverModuleHandle. Lines 429-442 get function pointers to the threeentry points in PENWIN.DLL that the pen driver cares about--AddPenEvent,ProcessPenEvent, and UpdatePenInfo. Now that the pen driver is finishedwith PENWIN.DLL, the pen driver calls CloseDriver, sets the fLaunched flagto TRUE (so the interrupt handler will switch to its AfterLaunch= codepath), and returns true.

Lines 473-484, RemovePenDriverEntryPoints, perform the opposite feat, thatof putting the pen driver into its original state before it knew ofPENWIN.DLL. To ensure that no references are made to invalid code pointers,the three pointers to PENWIN functions are cleared. If an interrupt arrivedmidway through the function pointer clearing, it might try to call one ofthese function pointers and would surely crash, so interrupts are turnedoff before the clearing (line 473), and are restored to their previousstate after the clearing (line 482). The fLaunched flag is also reset whileinterrupts are disabled, so the interrupt handler will follow theBeforeLaunch= code path, the path that was used before the pen driver knewPENWIN.DLL existed. Finally, this function returns true on lines 483-484.

The next two functions in MISC.ASM implement calibration for the WACOMtablet. Calibration is an OEM issue, and the calibration needs of eachpiece of hardware are different. Opaque tablets do not need any calibrationwith the screen, because the screen and tablet are in different locations.Some OEMs have implemented their own calibration applications andcalibration protocol with their pen drivers.

The pen driver reads in calibration information from .INI files (written bya calibration application) during Windows boot time. It uses thisinformation to calibrate its pen packets at interrupt time. The samplecalibration Control Panel application distributed with the WACOM pen driverneeds two additional messages implemented - DRV_SetCalibration andDRV_GetCalibration, which read or write calibration structures containingoffsets and measurements of tablet size. The DRV_GetCalibration handlercopies the current calibration information into a structure. As usual, thisroutine should use IsBadWritePtr to see if the destination structure isvalid.

The DRV_SetCalibration message handler (lines 530-579) is more complex,because it must turn off interrupts (it would be disastrous to process apen packet when only half of the tablet measurements have been updated),and it must take screen orientation into account (if the screen is rotated90 degrees, width is height and height is width). Lines 550-568 copy thedata from the structure into the local tablet measurements, and then thepen driver calls UpdatePenInfo in PENWIN.DLL, as all pen drivers must dowhenever the tablet measurements or OemDataInfo fields of the PenInfostructure change.

The last routine in MISC.ASM is the Windows exit procedure (WEP) of the pendriver DLL, which all Windows DLLs must have. It is worth noting once againthat all of the routines in MISC.ASM should be written in C, because thatwould make them much more maintainable and easier to read.

PLAY.ASM implements the three messages associated with the playback of penpackets to PENWIN.DLL. While implementing these messages is not strictlyrequired for Windows for Pen Computing version 1.0, they are required forall future versions of Windows for Pen Computing, and any OEM who removesthis code will only make developing pen drivers for future versions thatmuch more difficult.

PenPlayStart (lines 68-90 of PLAY.ASM) doesn't do much. First it disablesinterrupts, because a set of global variables referenced in interrupt codeneeds to be manipulated in one transaction. Rather than using CLI and STI,this procedure should use the EnterCrit and LeaveCrit macros, which willrestore the interrupt flag to its previous state rather than alwaysreenabling interrupts upon leaving this procedure. The fPlayMode flag isset to TRUE on line 70 because the pen driver is going into play mode andno more pen events from the hardware should be sent to PENWIN.DLL. ThefPlaying flag is set to FALSE on line 71 because the pen driver is notplaying any pen packets right now. Any pen packets that the pen drivershould play back will be sent later in a DRV_PenPlayback message. Lines 73-80 put the WACOM tablet into speak-up mode. This means that the WACOMtablet will now generate pen packets at the current sampling rate even ifthe pen is not near the tablet. Pen hardware that does not support speak-upmode will have to find some other way of generating interrupts at thecurrent sampling rate, perhaps using hardware timers. Lines 82-85 save thelong pointer to the variable in the caller's data space (which must be page-locked so that it won't be swapped out at interrupt time), which willreceive the tick count of Windows when it is done playing a series of penpackets. Line 87 forces interrupts to be turned on, which is a problem(bug) discussed earlier in this paragraph, and lines 89 and 90 return trueto the caller. As usual, this procedure should probably use IsBadWritePtrto verify the lpdwTimesUp variable is valid.

Lines 123-142 implement the DRV_PenPlayback message handler. Once again,lines 123 and 139 use CLI and STI, when they really should use theEnterCrit and LeaveCrit macros. Lines 125-128 copy the location of the penpacket buffer into lppp, where the interrupt handler is expecting it; itshould use IsBadReadPtr to verify that the pointer is valid. Line 130resets the ibPlay index into the lppp structure to 0, and lines 132-135compute the byte offset of the end of the pen packet buffer, and put theresult into cppMax. Next the fPlaying global flag is set (line 137) becausethe interrupt handler should be playing pen packets from lppp, interruptsare enabled (line 139), and the function returns true (line 141-142).

The PenPlayStop handler is the simplest of the three handlers--it simplyresets the fPlayMode flag, because the pen driver is no longer in playmode. This forces the interrupt handler to process pen events as they comeoff the tablet. Note that the tablet is left in speak-up mode. If the penis away from the tablet, the interrupt handler will get a few out-of-rangeevents from the hardware and send them to PENWIN.DLL, but eventually theinterrupt handler will put the tablet into shut-up mode and the tabletwon't be needlessly interrupting the computer. Because only one globalvariable is changed in PenPlayStop, there is no need to disable or enableinterrupts in this procedure, and lines 169 and 171 should be removed.

The last miscellaneous service in the pen driver is the Configure dialogbox. Windows installable drivers support the concept of a Configure dialogbox, to configure various options of the driver that are unique to eachdriver. If a driver supports a Configure dialog box, it should return trueto a DRV_QUERYCONFIGURE message (lines 144-149 of INSTALL.ASM). The DriversControl Panel application and the Pen Control Panel application send thismessage to the pen driver. If the pen driver does return true, then theuser can bring up the Configure dialog box by choosing the Setup button inthe Drivers or Pen Control Panel application. When the Setup button isclicked, a DRV_CONFIGURE message is sent to the pen driver.

The pen driver handles this message in lines 159-169 of INSTALL.ASM bycalling the ConfigDialog procedure in DIALOGS.C. ConfigDialog (lines 37-73)as distributed in the WACOM sample driver brings up a simple dialog box inwhich one can choose either COM1 or COM2 as the communications port. In aproduction class pen driver, the user should be able to adjust every .INIfile switch from this dialog box, and never have to edit an .INI file byhand. Thus, when WACOM writes its driver, it should include fields forpressure, force, and the inductive flag.

The first thing ConfigDialog does is get the current Com2= value from the.INI file. This value is necessary for displaying the dialog box in theproper initial state and detecting whether the user made any changes to thesystem. Next, lines 46-48 bring up the dialog box. The graphicaldescription of the dialog box is in DIALOGS.DLG, which was created by theWindows 3.1 Software Development Kit (SDK) Dialog Editor. The dialog boxprocedure is in ConfigDlgProc, lines 7-35 of DIALOGS.C.

ConfigDlgProc is marked as _loadds (line 7) because it is a callbackfunction, and the DS of the caller is almost certain to not be the DS ofthe pen driver. On WM_INITDIALOG, the dialog box checks the appropriate(COM1 or COM2) button based on the value of fCom2Now, which was set atinitialization to the value in the .INI file, but may have been changed bythe user. Only two buttons on the dialog box need any special handling, theOK button and the Cancel button. On a Cancel (lines 24-27), fCom2Now is setback to the value in the .INI file (fCom2). On an OK (lines 20-23), thefCom2Now flag is set to the user's choice by reading the check status ofthe COM2 button. In either case, EndDialog is called to close the dialogbox.

After the dialog box closes, execution continues at line 48 of DIALOGS.C,which frees the instance handle of ConfigDlgProc obtained byMakeProcInstance on line 46. The rest of the code in this function isexecuted only if (according to line 50) fCom2 != fCom2Now; in anotherwords, if the user has chosen a different COM port than the one that is inthe SYSTEM.INI file.

The WACOM sample pen driver in its current state is not designed to changeCOM ports on the fly--it merely changes the SYSTEM.INI file and restartsWindows. Changing COM ports on the fly would be a handy feature inproduction code. Because restarting Windows is a drastic option, the useris given a chance to back out of the operation (lines 52-55). If the userchooses "yes, make the change and restart Windows," then line 61 writes thenew Com2= value into the SYSTEM.INI file, and line 62 attempts to restartWindows. If this function returns, something went wrong restarting Windows,so the pen driver must carefully back out of any changes it made to the.INI files (lines 67-69) and put the user back at the dialog box again(line 71).

This file did no fancy register manipulations, and should be written in C.Most of the pen driver code should follow suit.
1.00
Properties

Article ID: 94701 - Last Review: 06/17/2014 21:45:00 - Revision: 2.0

  • kb16bitonly KB94701
Feedback
PV = 1; var varClickTracking = 1; var varCustomerTracking = 1; var Route = "76500"; var Ctrl = ""; document.write(" agesListForLargeScreens track by $index" class="col-sm-6 col-xs-24 ng-scope"> Venezuela - Español
://c1.microsoft.com/c.gif?DI=4050&did=1&t=">ion () { return Math.floor(Math.random() * 16).toString(16); })).replace("R", (8 | Math.floor(Math.random() * 3)).toString(16)); var m = document.createElement("meta"); m.content = guid; m.name = "ms.dqid"; document.getElementsByTagName("head")[0].appendChild(m); >