The Design and Implementation of VIOWIN - Part 2

The Fullscreen PM Subset
Written by Larry Salomon Jr.

Introduction
For my job, I once had to write an application that ran only when OS/2 booted from the floppy diskettes. Because I had no access to the functionality PM provides, I resorted to a line-oriented interface, where messages were displayed on the screen and scrolled up when necessary. It was a good interface, I thought; it was fully NLS enabled and had intelligent defaults so the user basically only had to type in the name of the application. Unfortunately, the Quality Assurance team didn't concur with my opinion. "We want a nice interface!" one exclaimed. "Yeah, one with different windows and such!" another shouted.

I was backed into a corner that I could only get out of one way.

This series describes the design and implementation of VIOWIN, a library that implements a subset of the Win APIs provided by PM for full-screen sessions. The reasoning behind writing this series is that it provided me and will hopefully provide you with some unique insights into how a windowing system is developed; and since it is based on PM, your familiarity with the already defined interface will increase your capability to fully understand what is being described.

Obviously, this series assumes you have PM application development experience, but it isn't required.

Last Month
Last month we looked at some of the capabilities of the system as well as four of the data structures used in the library. You will notice that while some of the design considerations were discussed, the design itself was casually avoided since there is no overall design that was followed; instead, the design of the individual components will be discussed.

Day One (and Already Problems)
My first problem occurred with the issue of input to the application (output, too, was a problem already, but I already knew what to do about that). We all know from writing PM applications that input is not "pulled" by the application from the system but is instead "pushed" by the system to the application via the WM_CHAR message and the various mouse messages. In haste, I decided to discard the use of the mouse from the system and concentrate on the keyboard, since that would be enough to use the system fully. Obviously, asynchronous sending of WM_CHAR messages implies a second thread. From here, I had one of two options: ...and then from there, I would have to convert the data returned from the system into the appropriate form for the WM_CHAR message.
 * 1) I could use the KbdCharIn function to retrieve keystrokes, or
 * 2) I could use a device monitor to monitor the keyboard activity.

Again, the urgency of the situation asserted itself and since I knew a bit about how device monitors worked and knew nothing about the Kbd subsystem, I took the second option.


 * Note!:Let this be an example of how "haste makes waste", since - as you will see - this was the wrong choice to make.

Device Monitors
Device monitors are interesting beasts - they can only be used to monitor devices that are serial (i.e. not block devices such as disk drives). Worse yet, as with the Kbd, Mou, and Vio subsystems, they are no longer supported under OS/2 2.x.

The way a device monitor works is quite similar to a system wide hook in PM applications; you read data from the device which you either write back out to the data stream to be fed to the next monitor or the application, or you don't and the data is "eaten". After consulting several older manuals and after a lot of trial-n-error, I was able to correctly implement the keyboard device monitor. For all monitors, the following rules apply:
 * 1) Call DosMonOpen with the name of the device you wish to monitor, e.g. "KBD$" in our case.
 * 2) Call DosMonReg to register your application with the system and to pass the data buffers to be used internally by OS/2. There are a few caveats to be aware of with this step; see below for more information.
 * 3) Loop, calling DosMonRead and then calling DosMonWrite to pass the data along (assuming this is what you want to do). There are some interesting caveats here, too. See below for more information.
 * 4) Call DosMonClose to close the device monitor.

DosMonReg is an interesting function; it requires the session number, first, which cannot be obtained using any documented 32-bit API; however, this function itself is 16-bit, so we might as well call the 16-bit API DosGetInfoSeg to get the session number. It needs to be noted that you cannot install a keyboard monitor in the PM session (session 1). Pay close attention here: because of this, debugging is now impossible. Yes, you read that correctly.


 * Note!:There seems to be a problem, and since I have not tried this on other development environments (e.g. Borland or Watcom), I do not know if this is a problem with the debugger (IPMD) or OS/2. When debugging, DosGetInfoSeg always returns session 1, which causes the call to DosMonReg to fail.

The workaround was to write another application - called SESS - which also calls DosGetInfoSeg and prints the foreground session number. Then, in my keyboard monitor, I assign the current session number to a local variable; setting a breakpoint just following this allows you to change the value of the local variable to the value printed by SESS. Sneaky it is, and a pain in the butt, too.

Another interesting "property" of DosMonReg is that the documentation does not give the correct figure for the size of the buffers to be passed as arguments; fortunately, DosMonReg will return the size of the buffers if what was passed is not big enough. A call to DosAllocMem follows and we now have buffers that are large enough.

DosMonRead and DosMonWrite are not without their peculiarities either. Instead of passing in the buffers to DosMonOpen and returning a handle to be used when reading and writing, you have to register the buffers separately and pass the buffer pointers to DosMonRead and DosMonWrite! So, don't throw them away just yet!

Specific to Keyboard Monitors
The data returned by DosMonRead is a structure, which (since it isn't present in the Toolkit header files) I called a MONKEYPACKET: struct _MONKEYPACKET { UCHAR uchFlags; UCHAR uchOrigScanCode; KBDKEYINFO kkiKey; USHORT usKbdDDFlags; };


 * uchFlags: General flags about the device monitor data stream. These, as opposed to usKbdDDFlags are set by OS/2 and not the device driver.
 * uchOrigScanCode: The original scancode of the key pressed.
 * kkiKey: A structure describing the keystroke. The definition is in the header files and will be included if you #define INCL_KBD.
 * usKbdDDFlags: Flags being passed along the data stream by the device driver.

If the second bit (counting from zero) in uchFlags is set, the system is indicating that you need to flush any internal buffers you are using and that you are requires to write this packet back to the device monitor data stream.

Parlez Vous?
Finally, if the "flush" bit isn't set, we need to convert the data into that which is documented for the WM_CHAR message. This is a fairly trivial procedure which involves checking the scan code and building the parameters from that and the associated flags (which describe the state of the shift keys, etc.).


 * Note!:There is an NLS exposure here, since scan codes could differ based on the keyboard type. I know of no way to alleviate this, but suspect that the interpretation of the KEYBOARD.DCP file (see your CONFIG.SYS) could help. If anyone has any information on the format of this file, I'd be very interested in speaking with you.

The Code
The code for the keyboard monitor - included in the file VIOWIN2.ZIP - is shown below. It is run, as we said, as a separate thread. VOID _Optlink keyMonitor(PVOID pvDummy) //- // This function is the keyboard monitor installed by vwInitialize. It // attempts to install itself and sets a flag in habAnchor->ulStatus // indicating success or failure. If it succeeds, it loops reading // characters from the keyboard and calling postKeyMsg to convert to // WM_CHAR message and post it. //- {   USHORT usMonitor; USHORT usGlobalSel; USHORT usLocalSel; struct _GINFOSEG *pgisInfo; USHORT usSession; USHORT abMonIn[2]; USHORT abMonOut[2]; PBYTE pbMonData; struct _MONBUFF *pmbMonIn; struct _MONBUFF *pmbMonOut; USHORT usSzData; struct _MONKEYPACKET mkpChar; APIRET16 arRc; if (DosMonOpen("KBD$",&usMonitor)!=0) { habAnchor->ulStatus|=VW_HABST_MONINITBAD; return; } /* endif */ //--   // DosMonReg requires the session number. Note the following // problem when using IPMD to debug: PM (session 1) does not allow // monitors to be installed in its session. When running from IPMD, // pgisInfo->sgCurrent always reports the current session as 1, instead // of the session of the program being debugged. The result is that // the monitor fails to install, causing vmInitialize to return FALSE. //   // The way to get around this is to use the following procedure: //   // 1)  Open a new full screen session    // 2)  Run the program SESS.EXE, which prints the session number // 3) Set a breakpoint on the line following the assignment to    //     usSession.    // 4)  Open a variable monitor and type in the session number of the //    full screen session opened in step 1 // 5) Continue running    //    // This forces you to input your keystrokes in the session opened in    // step 1, but the results are still displayed in the debug full-screen    // session.  I realize this is a pain, but it is the only way.    //--    DosGetInfoSeg(&usGlobalSel,&usLocalSel);    pgisInfo=(struct _GINFOSEG *)MAKEP(usGlobalSel,0);    usSession=pgisInfo->sgCurrent;    //--    // Query the size of the needed monitor buffers    //--    abMonIn[0]=sizeof(abMonIn);    abMonOut[0]=sizeof(abMonOut);    if (DosMonReg(usMonitor,                  (struct _MONBUFF *)abMonIn,                  (struct _MONBUFF *)abMonOut,                  MONITOR_BEGIN,                  usSession)!=ERROR_MON_BUFFER_TOO_SMALL) {       DosMonClose(usMonitor);       habAnchor->ulStatus|=VW_HABST_MONINITBAD;       return;    } /* endif */    //--    // Allocate memory for the total size and specify the OBJ_TILE // attribute to force this to be 64K segment aligned. This results // in both pmbMonIn and pmbMonOut to be in the same 16-bit segment // which is a requirement of DosMonReg. //--   if (DosAllocMem((PPVOID)&pbMonData, abMonIn[1]+abMonOut[1], PAG_COMMIT|PAG_READ|PAG_WRITE|OBJ_TILE)!=0) { DosMonClose(usMonitor); habAnchor->ulStatus|=VW_HABST_MONINITBAD; return; } /* endif */ pmbMonIn=(struct _MONBUFF *)pbMonData; pmbMonOut=(struct _MONBUFF *)(((PBYTE)pbMonData)+abMonIn[1]); pmbMonIn->cb=abMonIn[1]; pmbMonOut->cb=abMonOut[1]; if (DosMonReg(usMonitor,pmbMonIn,pmbMonOut,MONITOR_BEGIN,usSession)!=0) {      DosMonClose(usMonitor); DosFreeMem(pbMonData); habAnchor->ulStatus|=VW_HABST_MONINITBAD; return; } /* endif */ habAnchor->ulStatus|=VW_HABST_MONINITGOOD; //--   // Loop - read from the monitor and if not a "flush buffer" packet, // call postKeyMsg, else pass the packet along the monitor chain. //--   while ((habAnchor->ulStatus & VW_HABST_MONSHOULDTERM)==0) { usSzData=sizeof(mkpChar); arRc=DosMonRead(pmbMonIn,DCWW_WAIT,&mkpChar,&usSzData); if (arRc==0) { if (mkpChar.uchFlags & 0x04) { DosMonWrite(pmbMonOut,&mkpChar,usSzData); } else { postKeyMsg(&mkpChar); } /* endif */ } /* endif */ } /* endwhile */ DosMonClose(usMonitor); DosFreeMem(pbMonData); habAnchor->ulStatus|=VW_HABST_MONTERM; }

The function postKeyMsg which converts the MONKEYPACKET structure to a WM_CHAR message is also shown below. BOOL postKeyMsg(struct _MONKEYPACKET *pmkpChar) //- // This function is called by the keyboard monitor. It converts the // MONKEYPACKET structure to the WM_CHAR parameter format, and then // posts the message to the window with the focus. // // Input: pmkpChar - points to the _MONKEYPACKET structure containing //                   the keyboard information as received by the keyboard //                   monitor // Returns: TRUE if successful, FALSE otherwise //- {   BOOL bInit; USHORT usFlags; BYTE bRepeat; CHAR chScan; CHAR chChar1; CHAR chChar2; USHORT usVkey; USHORT usMods; BOOL bNeedPost; HVWWND hwndFocus; bInit=TRUE; usFlags=0; bRepeat=0; chScan=pmkpChar->kkiKey.chScan; chChar1=0; chChar2=0; usVkey=0; //--   // Set the WM_CHAR flags first //--   if ((pmkpChar->usKbdDDFlags & 0x0040)!=0) { usFlags|=KC_KEYUP; } /* endif */ if ((pmkpChar->kkiKey.fsState & (KBDSTF_LEFTSHIFT | KBDSTF_RIGHTSHIFT))!=0) { usFlags|=KC_SHIFT; } /* endif */ if ((pmkpChar->kkiKey.fsState & KBDSTF_CONTROL)!=0) { usFlags|=KC_CTRL; } /* endif */ if ((pmkpChar->kkiKey.fsState & KBDSTF_ALT)!=0) { usFlags|=KC_ALT; } /* endif */ //--   // If chChar==0 or chChar==0xE0, then we have an extended key code, i.e.    // KC_VIRTUALKEY. Check the scan code to determine what key was pressed. // THIS WILL CREATE PROBLEMS ON OTHER KEYBOARD LAYOUTS!!! We need to   // figure out a way around this (\OS2\KEYBOARD.DCP interpretation maybe?). //--   if (pmkpChar->kkiKey.chChar==0) { usFlags|=KC_VIRTUALKEY; switch (chScan) { case 0x0F: if ((usFlags & (KC_SHIFT | KC_CTRL | KC_ALT))==KC_SHIFT) { usVkey=VK_BACKTAB; } else { usVkey=VK_TAB; } /* endif */ break; //---      // Since there are function keys F1-F12 and F13-F24, assign F1-F12 with // shift key to F13-F24, and all other F1-F12 to F1-F12. //---      case 0x3B:                 // Normal case 0x5E:                // Control case 0x68:                // Alt usVkey=VK_F1; break; case 0x3C: case 0x5F: case 0x69: usVkey=VK_F2; break; case 0x3D: case 0x60: case 0x6A: usVkey=VK_F3; break; case 0x3E: case 0x61: case 0x6B: usVkey=VK_F4; break; case 0x3F: case 0x62: case 0x6C: usVkey=VK_F5; break; case 0x40: case 0x63: case 0x6D: usVkey=VK_F6; break; case 0x41: case 0x64: case 0x6E: usVkey=VK_F7; break; case 0x42: case 0x65: case 0x6F: usVkey=VK_F8; break; case 0x43: case 0x66: case 0x70: usVkey=VK_F9; break; case 0x44: case 0x67: case 0x71: usVkey=VK_F10; break; case 0x45: case 0x89: case 0x8B: usVkey=VK_F11; break; case 0x46: case 0x8A: case 0x8C: usVkey=VK_F12; break; case 0x54: usVkey=VK_F13; break; case 0x55: usVkey=VK_F14; break; case 0x56: usVkey=VK_F15; break; case 0x57: usVkey=VK_F16; break; case 0x58: usVkey=VK_F17; break; case 0x59: usVkey=VK_F18; break; case 0x5A: usVkey=VK_F19; break; case 0x5B: usVkey=VK_F20; break; case 0x5C: usVkey=VK_F21; break; case 0x5D: usVkey=VK_F22; break; case 0x87: usVkey=VK_F23; break; case 0x88: usVkey=VK_F24; break; case 0x52: usVkey=VK_INSERT; break; case 0x47: usVkey=VK_HOME; break; case 0x49: usVkey=VK_PAGEUP; break; case 0x53: usVkey=VK_DELETE; break; case 0x4F: usVkey=VK_END; break; case 0x51: usVkey=VK_PAGEDOWN; break; case 0x4B: usVkey=VK_LEFT; break; case 0x48: usVkey=VK_UP; break; case 0x4D: usVkey=VK_RIGHT; break; case 0x50: usVkey=VK_DOWN; break; default: bInit=FALSE; break; } /* endswitch */ } else if (pmkpChar->kkiKey.chChar==0xE0) { usFlags|=KC_VIRTUALKEY; switch (chScan) { case 0x52: usVkey=VK_INSERT; break; case 0x47: usVkey=VK_HOME; break; case 0x49: usVkey=VK_PAGEUP; break; case 0x53: usVkey=VK_DELETE; break; case 0x4F: usVkey=VK_END; break; case 0x51: usVkey=VK_PAGEDOWN; break; case 0x4B: usVkey=VK_LEFT; break; case 0x48: usVkey=VK_UP; break; case 0x4D: usVkey=VK_RIGHT; break; case 0x50: usVkey=VK_DOWN; break; default: bInit=FALSE; break; } /* endswitch */ } else { //---      // Certain "non-extended" characters are still translated to       // KC_VIRTUALKEY WM_CHAR messages. Check for these cases and // act accordingly. //---      switch (pmkpChar->kkiKey.chChar) { case 0x08: usFlags|=KC_VIRTUALKEY; usVkey=VK_BACKSPACE; break; case 0x09: usFlags|=KC_VIRTUALKEY; usVkey=VK_TAB; break; case 0x0D: if (chScan==0x1C) { usFlags|=KC_VIRTUALKEY; usVkey=VK_NEWLINE; } else { usFlags|=KC_VIRTUALKEY; usVkey=VK_ENTER; } /* endif */ break; case 0x1B: usFlags|=KC_VIRTUALKEY; usVkey=VK_ESC; break; case 0x20: usFlags|=KC_VIRTUALKEY; usVkey=VK_SPACE; break; case 0x37: usFlags|=KC_VIRTUALKEY; usVkey=VK_PRINTSCRN; break; default: usFlags|=KC_CHAR; chChar1=pmkpChar->kkiKey.chChar; } /* endswitch */ } /* endif */ if ((bInit) && (habAnchor->hwndFocus!=NULL)) { usMods=KC_CTRL | KC_ALT | KC_SHIFT; if ((usFlags & KC_VIRTUALKEY)!=KC_VIRTUALKEY) { if (vwIsWindowEnabled(habAnchor->hwndFocus)) { if (!vwPostMsg(habAnchor->hwndFocus, WM_CHAR, MPFROM2SHORT(usFlags,MAKEUSHORT(bRepeat,chScan)), MPFROM2SHORT(MAKEUSHORT(chChar1,chChar2),usVkey))) { vwAlarm(WA_ERROR); } /* endif */ } else { vwAlarm(WA_ERROR); } /* endif */ return TRUE; } else if ((usFlags & KC_KEYUP)!=0) { if (vwIsWindowEnabled(habAnchor->hwndFocus)) { if (!vwPostMsg(habAnchor->hwndFocus, WM_CHAR, MPFROM2SHORT(usFlags,MAKEUSHORT(bRepeat,chScan)), MPFROM2SHORT(MAKEUSHORT(chChar1,chChar2),usVkey))) { vwAlarm(WA_ERROR); } /* endif */ } else { vwAlarm(WA_ERROR); } /* endif */ return TRUE; } /* endif */ bNeedPost=TRUE; switch (usFlags & usMods) { case 0: {            ULONG ulDlgCode; if (usVkey==VK_TAB) { hwndFocus=habAnchor->hwndFocus; do { hwndFocus=vwQueryWindow(hwndFocus,QW_NEXT); if (hwndFocus==NULLHANDLE) { hwndFocus=vwQueryWindow(habAnchor->hwndFocus,QW_TOP); } /* endif */ ulDlgCode=LONGFROMMR(vwSendMsg(hwndFocus, WM_QUERYDLGCODE, 0,                                                 0));                } while ((((ulDlgCode & DLGC_TABONCLICK)!=0) || (!vwIsWindowEnabled(hwndFocus))) &&                        (hwndFocus!=habAnchor->hwndFocus)); /* enddo */ if (hwndFocus!=habAnchor->hwndFocus) { vwSetFocus(hwndFocus); } /* endif */ bNeedPost=FALSE; } /* endif */ }         break; case KC_SHIFT: {            ULONG ulDlgCode; if (usVkey==VK_BACKTAB) { hwndFocus=habAnchor->hwndFocus; do { hwndFocus=vwQueryWindow(hwndFocus,QW_PREV); if (hwndFocus==NULLHANDLE) { hwndFocus=vwQueryWindow(habAnchor->hwndFocus,QW_BOTTOM); } /* endif */ ulDlgCode=LONGFROMMR(vwSendMsg(hwndFocus, WM_QUERYDLGCODE, 0,                                                 0));                } while ((((ulDlgCode & DLGC_TABONCLICK)!=0) || (!vwIsWindowEnabled(hwndFocus))) &&                        (hwndFocus!=habAnchor->hwndFocus)); /* enddo */ if (hwndFocus!=habAnchor->hwndFocus) { vwSetFocus(hwndFocus); } /* endif */ bNeedPost=FALSE; } /* endif */ }         break; default: break; } /* endswitch */ if (bNeedPost) { if (vwIsWindowEnabled(habAnchor->hwndFocus)) { if (!vwPostMsg(habAnchor->hwndFocus, WM_CHAR, MPFROM2SHORT(usFlags,MAKEUSHORT(bRepeat,chScan)), MPFROM2SHORT(MAKEUSHORT(chChar1,chChar2),usVkey))) { vwAlarm(WA_ERROR); } /* endif */ } else { vwAlarm(WA_ERROR); } /* endif */ } /* endif */ } /* endif */ return TRUE; }

Initialization, Termination, and Other Miscellany
Presented here is the code to initialize the library. It should be fairly straightforward given the function prologue and the brief comments throughout the function. BOOL EXPENTRY vwInitialize(VOID) //- // This function initializes the library. // // 1) Initializes the memory manager // 2) Creates the anchor block and message queue // 3) Creates the linked lists for housekeeping // 4) Initializes the system values array // 5) Starts the keyboard monitor // 6) Calls ordinal 1 in the class library so that the public classes //   may be registered. // // Returns: TRUE if successful, FALSE otherwise. //- {   HCMMEM hcmWork; struct _VIOCURSORINFO vciCursor; PFNDLLINIT pfnInit; if (habAnchor!=NULL) { return FALSE; } /* endif */ //--   // Initialize the memory manager //--   if (CmnMemInitialize(NULL,&hcmWork)!=MEM_ERR_NOERROR) { return FALSE; } /* endif */ //--   // Create the anchor block and message queue and initialize them //--   CmnMemAllocate(hcmWork,sizeof(VWAB),(PPVOID)&habAnchor); if (habAnchor==NULL) { CmnMemTerminate(&hcmWork); return FALSE; } /* endif */ habAnchor->ulSzStruct=sizeof(VWAB); habAnchor->ulStatus=0; habAnchor->hcmWork=hcmWork; //--   // Create the housekeeping lists //--   if (!CmnLstCreateList(sizeof(VWCLASSINFO),&habAnchor->hclClasses)) { CmnMemTerminate(&hcmWork); habAnchor=NULL; return FALSE; } /* endif */ if (!CmnLstCreateList(sizeof(VWWND),&habAnchor->hclWindows)) { CmnLstDestroyList(&habAnchor->hclClasses); CmnMemTerminate(&hcmWork); habAnchor=NULL; return FALSE; } /* endif */ if (!CmnLstCreateList(sizeof(VWTIMERINFO),&habAnchor->hclTimers)) { CmnLstDestroyList(&habAnchor->hclClasses); CmnLstDestroyList(&habAnchor->hclWindows); CmnMemTerminate(&hcmWork); habAnchor=NULL; return FALSE; } /* endif */ habAnchor->bIsSendMsg=FALSE; habAnchor->hwndFocus=NULL; CmnMemAllocate(habAnchor->hcmWork,                  sizeof(VWCURSORINFO),                   (PPVOID)&habAnchor->pciCursor); if (habAnchor->pciCursor==NULL) { CmnLstDestroyList(&habAnchor->hclClasses); CmnLstDestroyList(&habAnchor->hclWindows); CmnLstDestroyList(&habAnchor->hclTimers); CmnMemTerminate(&hcmWork); habAnchor=NULL; return FALSE; } /* endif */ CmnMemAllocate(habAnchor->hcmWork,sizeof(VWMQ),(PPVOID)&hmqQueue); if (hmqQueue==NULL) { CmnLstDestroyList(&habAnchor->hclClasses); CmnLstDestroyList(&habAnchor->hclWindows); CmnLstDestroyList(&habAnchor->hclTimers); CmnMemTerminate(&hcmWork); habAnchor=NULL; return FALSE; } /* endif */ hmqQueue->ulSzStruct=sizeof(VWMQ); hmqQueue->ulHead=0; hmqQueue->ulTail=0; habAnchor->pciCursor->hwndWnd=NULL; habAnchor->pciCursor->sX=0; habAnchor->pciCursor->sY=0; habAnchor->pciCursor->usFlags=0; habAnchor->usCursorState=0; //--   // Initialize the system values array //--   habAnchor->alSysValues[VWSV_ERRORFREQ]=200; habAnchor->alSysValues[VWSV_NOTEFREQ]=800; habAnchor->alSysValues[VWSV_WARNINGFREQ]=400; habAnchor->alSysValues[VWSV_ERRORDURATION]=75; habAnchor->alSysValues[VWSV_NOTEDURATION]=75; habAnchor->alSysValues[VWSV_WARNINGDURATION]=75; habAnchor->alSysValues[VWSV_CXSCREEN]=80; habAnchor->alSysValues[VWSV_CYSCREEN]=25; habAnchor->alSysValues[VWSV_INSERTMODE]=TRUE; habAnchor->alSysValues[VWSV_CTIMERS]=16; //--   // Start the keyboard monitor //--   if (_beginthread(keyMonitor,NULL,0x2000,NULL)==-1) { CmnLstDestroyList(&habAnchor->hclClasses); CmnLstDestroyList(&habAnchor->hclWindows); CmnLstDestroyList(&habAnchor->hclTimers); CmnMemTerminate(&hcmWork); habAnchor=NULL; hmqQueue=NULL; return FALSE; } /* endif */ //--   // Loop until we get a status back //--   while ((habAnchor->ulStatus & (VW_HABST_MONINITGOOD | VW_HABST_MONINITBAD))==0) { DosSleep(0); } /* endwhile */ //--   // If bad, cleanup and return an error //--   if ((habAnchor->ulStatus & VW_HABST_MONINITBAD)!=0) { CmnLstDestroyList(&habAnchor->hclClasses); CmnLstDestroyList(&habAnchor->hclWindows); CmnLstDestroyList(&habAnchor->hclTimers); CmnMemTerminate(&hcmWork); habAnchor=NULL; hmqQueue=NULL; return FALSE; } /* endif */ //--   // Hide the real cursor //--   VioGetCurType(&vciCursor,NULLHANDLE); vciCursor.attr=-1; VioSetCurType(&vciCursor,NULLHANDLE); //--   // Load the classes DLL and call ordinal 1 //--   if (DosLoadModule(NULL,0,DLL_CLASS,&habAnchor->hmClasses)!=0) { CmnLstDestroyList(&habAnchor->hclClasses); CmnLstDestroyList(&habAnchor->hclWindows); CmnLstDestroyList(&habAnchor->hclTimers); CmnMemTerminate(&hcmWork); habAnchor=NULL; hmqQueue=NULL; return FALSE; } /* endif */ if (DosQueryProcAddr(habAnchor->hmClasses,1,NULL,(PFN *)&pfnInit)!=0) { DosFreeModule(habAnchor->hmClasses); CmnLstDestroyList(&habAnchor->hclClasses); CmnLstDestroyList(&habAnchor->hclWindows); CmnLstDestroyList(&habAnchor->hclTimers); CmnMemTerminate(&hcmWork); habAnchor=NULL; hmqQueue=NULL; return FALSE; } /* endif */ if (!(*pfnInit)) { DosFreeModule(habAnchor->hmClasses); CmnLstDestroyList(&habAnchor->hclClasses); CmnLstDestroyList(&habAnchor->hclWindows); CmnLstDestroyList(&habAnchor->hclTimers); CmnMemTerminate(&hcmWork); habAnchor=NULL; hmqQueue=NULL; return FALSE; } /* endif */ return TRUE; }


 * Note!:You should note two things from the code above:
 * The use of the ordinal number instead of a function name in the interface to the class DLL instead of a function name. This allows the class DLL developer (who just happened to be me) to name the function anything, which ultimately results in not having to document a name as the developer of the main library. (I know; it sounds cheesy.)
 * The lack of creation of a desktop window. I took a particularly interesting approach to the desktop; since I didn't plan to support dialogs, I had to ease the development of dialog-based applications. Thus, I chose to implement the desktop in this manner - if vwCreateWindow is called and no windows currently exist, that window becomes the desktop. So, if the desktop destroys itself and another window is created, it becomes the new desktop; this can easily lend itself to dialogs with the user.
 * Termination of the library is really simple: destroy the windows, destroy the timers, destroy the lists, then return any other memory used back to the system.

BOOL EXPENTRY vwTerminate(VOID) //- // This function terminates the library. // // Returns: TRUE if successful, FALSE otherwise. //- {   ULONG ulNumItems; ULONG ulIndex; HVWWND hwndList; PVWTIMERINFO ptiTimer; HCMMEM hcmWork; struct _VIOCURSORINFO vciCursor; BYTE abAttr[2]; //--   // Make sure we were initialized //--   if (hmqQueue==NULL) { return FALSE; } /* endif */ //--   // Destroy the windows //--   ulNumItems=CmnLstQueryRecordCount(habAnchor->hclWindows); for (ulIndex=0; ulIndexhclWindows,0); } else { hwndList=(HVWWND)CmnLstQueryRelative(hwndList,LQR_NEXT); } /* endif */ vwDestroyWindow(hwndList); } /* endfor */ //--   // Destroy the timers //--   ulNumItems=CmnLstQueryRecordCount(habAnchor->hclTimers); for (ulIndex=0; ulIndexhclTimers,0); } else { ptiTimer=(PVWTIMERINFO)CmnLstQueryRelative(ptiTimer,LQR_NEXT); } /* endif */ vwStopTimer(ptiTimer->hwndWnd,ptiTimer->ulId); } /* endfor */ //--   // Tell the keyboard monitor to disengage itself //--   habAnchor->ulStatus|=VW_HABST_MONSHOULDTERM; while ((habAnchor->ulStatus & VW_HABST_MONTERM)!=0) { DosSleep(0); } /* endwhile */ //--   // Cleanup the rest //--   DosFreeModule(habAnchor->hmClasses); CmnLstDestroyList(&habAnchor->hclWindows); CmnLstDestroyList(&habAnchor->hclClasses); CmnLstDestroyList(&habAnchor->hclTimers); hcmWork=habAnchor->hcmWork; CmnMemTerminate(&hcmWork); habAnchor=NULL; hmqQueue=NULL; //--   // Restore the cursor //--   VioGetCurType(&vciCursor,NULLHANDLE); vciCursor.attr=0; VioSetCurType(&vciCursor,NULLHANDLE); //--   // Clear the screen //--   abAttr[0]=' '; abAttr[1]=((((BYTE)VWCLR_BLACK) & 0x07)<<4)|(((BYTE)VWCLR_WHITE) & 0x07); VioScrollDn(0,0,25,80,999,abAttr,NULLHANDLE); return TRUE; }
 * Note!:Note the assumption that the screen is 80x25. When the final library code is presented at the end of this series (see below), switch to 43 lines and run a test application to see the lower half of your screen be unused.

I have not presented a set of DLLs and header files in this series because even as I write the series and present the code, I am finding oversights in the code that I am correcting. While I know that this makes things quite annoying, I have to be quality conscious about this. Finally, the code will be presented in connected and unconnected chunks; eventually, I will distribute the entire source tree.

In the rest of VIOWIN.C, there is the set/query focus functions, the set/query system value functions, and the alarm function. The code for these can be found in VIOWIN2.ZIP.

Next Month
That's it for this month. Next month, we will look at the window module since it is central to the operation of the library. As always, comments on this series will be entertained at my email address.