home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 11 Util
/
11-Util.zip
/
MKHOOK1.ZIP
/
HOOK_KB.ASM
< prev
next >
Wrap
Assembly Source File
|
1992-09-05
|
24KB
|
592 lines
;---------------------------------------------------------------------------;
; M.F.Kaplon Begun:Fri 06-12-1992 Revised:Sat 09-05-1992
; Title : hook_kb.asm
;
; INITIALIZES,CREATES MESSAGE QUEUE, REGISTERS CLASS,CREATES STANDARD WINDOW
; ESTABLISHES MAIN MESSAGE LOOP, ESTABLISHES MAIN WINDOW PROCEDURE, Reads in
; C:\os2\hook_kb.dat , Establishes Hook HK_INPUT, Filters Messages and
; takes apprpriate action.
;
; For System Function Calls
;
; 1 - Must use the C Calling Convention which means
; Arguments Pushed on Stack in Reverse Order than declared in function
; And the stack pointer reset after call.
; This is done by the MACRO $Call defined in DOSWIN32.MAC
; 2 - Use .MODEL FLAT. When using a pointer, all you have to do is push
; the offset of the variable since in FLAT model everyting is NEAR
; in the 32 bit space
; 3 - Uses an 8K stack
;
; It is assumed that the Developers toolkit for OS/2 2.0 is installed
; on the users C:drive in the default installation
;
; The files needed to create the functioning program are:
; doswin32.mac Macros and Equates used by Program
; hook_kb.asm Source for Executable Assembled and Linked by mlc-w386.cmd
; hook_dll.def Define file needed by IMPLIB and LINK386 for DLL
; hook_dll.asm Source for DLL = Assembled and linked by dll-w386.cmd
; hook_kb.dat Text File assigning programs to key strokes -read by hook_kb
; The user creates this file according to structure outlined in
; sample and it MUST BE LOCATED IN C:\OS2\DLL
; dll-w386.cmd Command File to create the hook_dll.dll and copy to c:\os2\dll
; mlc-w386.cmd Command File to Assemble and Link hook_kb.asm
;
; Except as noted, all files should be in the same directory.
; To assemble and link use the directory holding the above files as default
; and use the commands
; dll-w386 hook_dll;creates hook_dll.dll from hook_dll.asm,moves to c:\os2\dll
; mlc-w386 hook_kb ;creates hook_kb.exe from hook_kb.asm
;
; kb_hook.exe is placed in the directory holding the above files
;
; There is one warning message
;
; LINK : warning L4036: no automatic data segment
;
; This message has to do with no DGROUP being defined
; It can be suppressed with DATA NONE in a "DEF" file
;
;---------------------------------------------------------------------------;
;USES HOOK HK_INPUT
;----------------- PRELIMINARIES ----------------
.386 ;preceeding .MODEL makes USE32 default
.MODEL FLAT,SYSCALL,OS_OS2
;---------- Conditionally required equates -------------
NUMBUFS equ 1 ; Uncomment if need number routines
DOSERROR equ 1 ; Uncomment if need DosError Messages
INCL_WINERRORS equ 1
INCL_WIN equ 1
INCL_DOSMEMMGR equ 1
INCL_DOSFILEMGR equ 1
INCLUDE doswin32.mac ;macros used by *.asm
INCLUDE c:\toolkt20\asm\os2inc\os2def.inc ;structure defns includes POINTL
INCLUDE c:\toolkt20\asm\os2inc\pmwin.inc ;structure defns POINTL defn required
INCLUDE c:\toolkt20\asm\os2inc\pmerr.inc ;errors
INCLUDE c:\toolkt20\asm\os2inc\pmshl.inc
INCLUDE c:\toolkt20\asm\os2inc\bsememf.inc;memory
INCLUDE c:\toolkt20\asm\os2inc\bsedos.inc ;files
INCLUDELIB c:\toolkt20\os2lib\os2386.lib ;Library
;---------- Prototype Definitions for MASM 6.0 -------------
InitMainWindow Proto Near SYSCALL var1:DWORD,var2:DWORD,var3:DWORD
MainPaint Proto Near SYSCALL var1:DWORD
MainKeyBoard Proto Near SYSCALL var1:DWORD,var2:DWORD,var3:DWORD
ExecOnKB STRUCT ;Structure used in program-stores data from hook_kb.dat
Exec DWORD ? ;Address of ASCIIZ str - name of exec program
CmdLn DWORD ? ;Address of ASCIIZ str - command line parms
SessT WORD ? ;Session Type
ExecOnKB ENDS
.STACK 8096 ;8K stack
.DATA
IFDEF NUMBUFS ;To use UNCOMMENT NUMBUFS equate above
$DefineNumBufs
ENDIF
IFDEF DOSERROR
$DOSErrorMessages
ENDIF
;------------- handles --------
hab DWORD ? ;Anchor block Handle
hmq DWORD ? ;Message Queue Handle
hwndMainFrame DWORD ? ;Handle to Main Frame Window
hwndMain DWORD ? ;Handle to Client window
;------------- Text Strings --------
szAppName BYTE "Main0",0 ;Class Name of Window Procedure
szWinTitle BYTE "** TSR **",0 ;Window Title
;------------- Text Strings for WinMessageBox calls for Errors --------
msgModuleLoaded BYTE "hook_KB Already Loaded",0
msgInfo BYTE "Debug Info",0
msgFileErr BYTE "Incorrect hook_kb.dat File",0
;------------- Styles --------
msgBoxStyle DWORD (MB_YESNO OR MB_DEFBUTTON1)
flStyle DWORD (CS_SIZEREDRAW OR CS_HITTEST) ;Window Style
flCtlData DWORD FCF_SYSMENU ;Invisible Window
;------------- structures --------
quemsg QMSG {,,,,,,} ;Queue message structure
AltX ExecOnKB 10 dup({0,0,9}) ;Array for Alt-# default Initialization
CtrlX ExecOnKB 10 dup({0,0,9}) ;Array for Ctrl-# default Initialization
;------------- Miscellaneous --------
parm1 DWORD ? ;handle of window sending message
parm2 DWORD ? ;message id value
parm3 DWORD ? ;message mp1
parm4 DWORD ? ;message mp2
parm5 DWORD ? ;message time
Concanted BYTE 64 dup(0) ;buffer to hold concanted strings
Alt_Ctrl DWORD ? ;0/1 if Alt/Ctrl key struck
Bits DWORD ? ;Related to Scan Code
;------------ Specific to TSR Hook use -----
DllLoadError BYTE 100 dup(0) ;Buffer for name of object contributing to error
DllHandle DWORD ? ;Handle of Dynamic LInk Module returned here
DllFullModName BYTE "c:\os2\dll\hook_dll.dll",0
DllProcAddr1 DWORD ? ;address of proc 1 in dynamic link module
;- StartSession structure Offset ------------ Identification --------------
StartData WORD 32 ; 0 Length of Structure
WORD 0 ; 2 Related 0 is independent, 1 is child
WORD 0 ; 4 0/1 Start in Foreground/Background
WORD 0 ; 6 Trace Option 0 is no trace
DWORD 0 ; 8 ProgramTitle 0 uses Program Name
DWORD ? ;12 Address of ASCZII string with fully qualified program name
DWORD 0 ;16 Address of Input Args to Pgm - 0 is none
DWORD 0 ;20 TermQ 0 is no Queue
DWORD 0 ;24 Environment - must be 0 for DOS
WORD 0 ;28 InheritOp 0 Inherits Shell Environment
WORD ? ;30 SessType see p.2-345 of Control Prog Ref.
SessID DWORD 0 ;receives Session ID for Alt-1
ProcID DWORD 0 ;receives ProcessID for Alt-1
;----Shared Memory Variables---
SharedMem DWORD ? ;base address returned
SharedMemName BYTE "\SHAREMEM\DATAE.DAT",0
SharedMemFlags DWORD 12h ;(PAG_COMMIT OR PAG_WRITE)
;-------------- parameters for memory usage and file opening
memAddr DWORD ? ;address of memory block
memFlags DWORD 13h ;(PAG_COMMIT OR (PAG_WRITE OR PAG_READ)) ;read-write access required
fName BYTE "c:\os2\hook_kb.dat",0 ;Address of data file
fhandle DWORD ? ;Address of Handle for File
fActionTaken DWORD ? ;Address for action taken
fSize DWORD ? ;Logical size of file
fAttribute DWORD 0
fOpenFlag DWORD OPEN_ACTION_OPEN_IF_EXISTS
fOpenMode DWORD OPEN_SHARE_DENYNONE OR OPEN_ACCESS_READWRITE
fExtaBuf DWORD 0 ;no extended attributes
fpointer0 DWORD ? ;file pointer start file
fpointer1 DWORD ? ;file pointer end file
EOFflag DWORD 0 ; EndOfFile flag
.CODE
startup: ;need to do this way with flat model
;---------- ESTABLISH WINDOW ------------
;-----Initialize Window -Anchor block handle returned = hab
$Call WinInitialize,0 ;called with argument 0
mov hab,eax ;return value
.IF hab == NULL
$Call WinTerminate,hab
$DosExit
.ENDIF
;-----Create MessageQue QueueHandle returned = hmq
$Call WinCreateMsgQueue,hab,0 ; 0 is default size of queue
mov hmq,eax ;returned queue handle
.IF hmq == NULL
$WinErrMsg " : WinCreateMsgQueue"
$DosExit
.ENDIF
;---- Register Window Class Returned value is TRUE or FALSE
$Call WinRegisterClass,hab,offset szAppName,offset MainWinProc,flStyle,0
.IF eax == FALSE
$WinErrMsg " : WinRegisterClass"
$Call WinTerminate,hab
$DosExit
.ENDIF
;---- CreateStandard Window - Returns handle for Main Window Frame and client Window
$Call WinCreateStdWindow,HWND_DESKTOP,WS_VISIBLE,offset flCtlData,offset szAppName,\
offset szWinTitle,WS_VISIBLE,0,0,offset hwndMain
mov hwndMainFrame,eax ;returned Frame Window handle
.IF hwndMainFrame == 0
$WinErrMsg " : WinCreateStdWindow"
Call ExitWin
.ENDIF
;------------ IS hook_DLL.DLL LOADED ? -------------------
$Call DosQueryModuleHandle,offset DllFullModName,offset DllHandle
.IF eax == 0 ;module already loaded
$Call WinMessageBox,HWND_DESKTOP,HWND_DESKTOP,offset msgModuleLoaded,NULL,0,msgBoxErrStyle
Call ExitWin
.ENDIF
;----- ALLOCATE SHARED MEM AND PLACE HANDLE THERE ----
$Call DosAllocSharedMem,offset SharedMem,offset SharedMemName,16,SharedMemFlags
.IF eax > 0
$WinErrMsg " : DosAllocSharedMem"
Call ExitWin
.ENDIF
mov esi,SharedMem
mov eax,hwndMainFrame
mov [esi],eax ;put window handle there
;---------- IS Data File AVAILABLE AND VALID ? ------------
;---------- First see if hook_kb.dat exists in proper location ------
$Call DosOpen,offset fName,offset fhandle,offset fActionTaken,fsize,fAttribute,fOpenFlag,fOpenMode,fExtaBuf
.IF eax != 0
$WinErrMsg " : DosOpen "
Call ExitWin
.ENDIF
;------- get file size
$Call DosSetFilePtr,fhandle,0,FILE_BEGIN,offset fpointer0
.IF eax != 0
$WinErrMsg " : DosSetFilePtr"
Call ExitWin
.ENDIF
$Call DosSetFilePtr,fhandle,0,FILE_END,offset fpointer1
mov eax,fpointer1
sub eax,fpointer0
mov fsize,eax ;fsize now has file size
add eax,16 ;allow a little leeway in buffer
;------- now allocate memory for file buffer
$Call DosAllocMem,offset memAddr,eax,memFlags
.IF eax != 0
$WinErrMsg " : DosAllocMem"
Call ExitWin
.ENDIF
;------Read File Into buffer and Close file -------
;reposition to start of file
$Call DosSetFilePtr,fhandle,0,FILE_BEGIN,offset fpointer0
$Call DosRead,fHandle,memAddr,fsize,offset nwritten
$Call DosClose,fHandle
;------ Read Buffer and Initialize Arrays ALtX and CtrlX ----
mov esi,memAddr
mov ecx,0 ;Index and Counter for memAddr
.WHILE ecx <= fsize
call SkipSpaces ;skip over any initial spaces
.BREAK .IF EOFflag == 1
call SkipCommentsToEOL ;skip over comments and spaces to first valid entry
.BREAK .IF EOFflag == 1
call SkipSpaces ;skip over initial spaces
.BREAK .IF EOFflag == 1
.IF (byte ptr [esi+ecx] == 'A' || byte ptr [esi+ecx] == 'a' || byte ptr [esi+ecx] == 'C' || byte ptr [esi+ecx] == 'c') && (byte ptr [esi+ecx+1] >= '0' && byte ptr [esi+ecx+1] <= '9')
inc ecx ;point to number
xor eax,eax
xor edx,edx
mov al,byte ptr [esi+ecx] ;get digit ID
sub al,'0' ;convert to decimal
.IF al == 0
add al,9 ;0 is treated as 10th element, 1st is offset 0
.ELSE
dec al ;ALt1/Ctrl1 is array element 0
.ENDIF
mov dl,10
mul dl ;ax has offset into array
inc ecx ;right after digit id A/C number
.ELSE
.CONTINUE
.ENDIF
.IF byte ptr[esi+ecx - 2] == 'A' || byte ptr[esi+ecx - 2] == 'a'
mov edi,offset AltX ;this is Alt Keys
.ENDIF
.IF byte ptr[esi+ecx - 2] == 'C' || byte ptr[esi+ecx - 2] == 'c'
mov edi,offset CtrlX ;this is a Ctrl Key
.ENDIF
.IF edi == offset AltX || edi == offset CtrlX
mov edx,eax ;edx is offset into array
call SkipSpaces ;go to SessID
.BREAK .IF EOFflag == 1
xor eax,eax
mov al,byte ptr [esi+ecx] ;get sess type
sub al,'0' ;convert to number
mov word ptr [edi + edx+8],ax
inc ecx
call SkipSpaces ;get to Exec string
.BREAK .IF EOFflag == 1
mov eax,esi
add eax,ecx ;offset of exec
mov [edi+edx],eax ;store its address
; have to go to end of Exec string and place a numeric 0 there
.WHILE byte ptr [esi+ecx] != ' '
inc ecx
.ENDW ;exits pointing to end of exec command
.BREAK .IF byte ptr[esi+ecx] != ' '
mov byte ptr [esi+ecx],0
inc ecx
call SkipSpaces ;get next parameter
.BREAK .IF EOFflag == 1
xor eax,eax
.IF byte ptr [esi+ecx]== '0' ;no command line
mov al,byte ptr [esi+ecx]
sub al,'0'
mov [edi+edx+4],eax
.ELSE ;its a string - copy its address
mov eax,esi
add eax,ecx ;offset of command line
mov [edi+edx+4],eax ;store command line address
.IF byte ptr [esi+ecx] != 22h ;not a "
.WHILE byte ptr [esi+ecx] != ' ' && ecx < fsize ;skip until a space
.IF byte ptr[esi+ecx] == lf || byte ptr[esi+ecx] == cr ; LF but no ";"
Call DataFormatErr
.ENDIF
inc ecx
.ENDW ;exits pointing to end of command line
.BREAK .IF ecx >= fsize
mov byte ptr [esi+ecx],0 ;put 0 at end
.ELSE ;first char is a quote
inc ecx ;move beyond it
.WHILE byte ptr [esi+ecx] != 22h && ecx < fsize ; skip until a "
inc ecx
.ENDW ;exits pointing to final quote
.BREAK .IF ecx >= fsize
inc ecx
.BREAK .IF byte ptr[esi+ecx] != ' '
mov byte ptr [esi+ecx],0 ;put 0 at end
.ENDIF
.ENDIF
;inc ecx
.BREAK .IF ecx >= fsize
xor edi,edi ;reset to 0 so test can be made
.WHILE byte ptr[esi+ecx] != ';' && ecx < fsize ;go to next comment ;
.IF byte ptr[esi+ecx] == lf || byte ptr[esi+ecx] == cr ; LF but no ";"
Call DataFormatErr
.ENDIF
inc ecx
.ENDW
.BREAK .IF ecx >= fsize
.CONTINUE
.ENDIF
.ENDW
; See if Dat File properly read in
.IF ecx < fsize
Call DataFormatErr
.ENDIF
;----------- ESTABLISH THE HOOK -------------
;The way to proceed is to install a System Hook. This has to go into a DLL
;otherwise it cannot be called by other programs. The procedure in hook_dll
;will inspect WM_CHAR to see if the one of the assigned keys is hit
;and if so posts a message to this program
$Call DosLoadModule,offset DllLoadError,LENGTHOF DllLoadError,offset DllFullModName,offset DllHandle
.IF eax != 0
$DosErrMsg " DosLoadModule"
.ENDIF
$Call DosQueryProcAddr,DllHandle,1,0,offset DllProcAddr1
.IF eax != 0
$DosErrMsg " DosQueryProcAddr"
.ENDIF
$Call WinSetHook,hab,NULLHANDLE,HK_INPUT,DllProcAddr1,DllHandle
.IF eax == 0
$WinErrMsg " WinSetHook"
.ENDIF
;--------- CREATE MAIN MESSAGE LOOP -----------
mml: $Call WinGetMsg,hab,offset quemsg,0,0,0 ;Note: differs from usual
; .IF eax == TRUE ;msg loop - done so that program
$Call WinDispatchMsg,hab,offset quemsg ;can only be terminated
jmp mml ;by the keystroke Alt-Del
; .ENDIF ;as notified from the DLL
;Normally an Exit routine would go here, as indicated below but which
;is commented out. It is not required since it can never be reached.
;---------- EXIT ---------------
;$Call WinReleaseHook,hab,NULL,HK_INPUT,[DllProcAddr1],DllHandle
;.IF eax == 0
; $WinErrMsg " : WinReleaseHook"
;.ENDIF
;$Call DosFreeModule,DllHandle
;.IF eax != 0
; $WinErrMsg " : DosFreeModule"
;.ENDIF
;$Call DosFreeMem,memAddr
;$Call WinDestroyWindow,hwndMainFrame
;call ExitWin
;---------------- End of Main Program -------------------
;------------- PROCESS MESSAGE QUEUE FOR hook_KB ------------
;------------------ MainWinProc -----------------
;parm1 = hwnd,parm2 = msg,parm3 = mp1,parm4 = mp2
;this is called from System and so has to do everything itself
MainWinProc Proc Near
;----------- Get Passed Parameters from Stack ------------
push ebp ;return address is 4 bytes and this push is 4 bytes
mov ebp,esp ;so first parameter is 8 bytes from top
mov eax,[ebp+8]
mov parm1,eax ;hwnd
mov eax,[ebp+12]
mov parm2,eax ;msg
mov eax,[ebp+16]
mov parm3,eax ;mp1
mov eax,[ebp+20]
mov parm4,eax ;mp2
mov eax,[ebp+24]
mov parm5,eax ;time of message
;---------------- WM_CREATE ----------------
.IF parm2 == WM_CREATE
Invoke InitMainWindow,parm1,parm3,parm4
.ENDIF
;---------------- WM_PAINT ----------------
.IF parm2 == WM_PAINT ; && parm1 == hwndMain
Invoke MainPaint,parm1
.ENDIF
;---------------- WM_CHAR ----------------
.IF parm2 == WM_CHAR
Invoke MainKeyboard,parm3,parm4,parm1 ;sets return value
.ENDIF
;---------------- WM_USER+200H ----------------
.IF parm2 == WM_USER+200h ;broadcasted message
;------------ FILTER KEYSTROKES ------------
mov bits,0
mov ebx,parm3 ;[esi+8] ;ebx has mp1
mov ecx,KC_KEYUP
test bx,cx
jnz wm0 ;accept only on down strike
mov ecx,KC_SCANCODE
test bx,cx ;test scan code
jz wm0 ;jump if no valid scan code
mov ebx,parm3 ;[esi+8] ;mp1
xor edx,edx
shld edx,ebx,8 ;get high 8 bits of mp1 into dl
mov ecx,KC_ALT ;? Alt Key down when msg generated
test bx,cx ;test scan code
jz wm1 ;jump if Alt Key Not Hit
mov Alt_Ctrl,0 ;Alt Key Was Down
jmp wm2
wm1: mov ecx,KC_CTRL ;? Ctrl-Key down
test bx,cx
jz wm0 ;Was neither
mov Alt_Ctrl,1 ;Was Ctrl Key Down
wm2: ;if statement below is on this line gives assemble error
.IF edx >= 2 && edx <= 11 || edx == 83 ;scan code for keys <1> <-> <0>
mov bits,edx
.ELSE
jmp wm0 ;not in desired range
.ENDIF
.IF bits > 1 && edx != 83
sub bits,2 ;scan code 2,3,...,11 now 0,1,2,....,9
.ENDIF
;- Alt_Ctrl has Alt_Ctrl Flag and bits has offset number 0,1,2,3,4,5,6,7,8,9
; representing keys 1,2,3,4,5,6,7,8,9,0(10)
;---------- bits also has 83 scan code for white Del
.IF bits == 83 ; Alt-Del (White Del key)
$Call DosFreeMem,memAddr
$Call WinReleaseHook,hab,NULL,HK_INPUT,[DllProcAddr1],DllHandle
$Call DosFreeModule,DllHandle
$Call WinDestroyWindow,hwndMainFrame
Call ExitWin
.ELSE ;.ENDIF
mov esi,offset StartData ;data structure for DosStartSession
.IF Alt_Ctrl == 0
mov ebx,offset AltX ;Alt Definitions
.ENDIF
.IF Alt_Ctrl == 1
mov ebx,offset CtrlX ;Ctrl Definitions
.ENDIF
mov eax,bits
mul ten ;define in NUMBUFS
add ebx,eax ;element # of structure
.IF word ptr [ebx+8] != 9
$Call WinSetFocus,HWND_DESKTOP,hwndMainFrame ;required for program started to be in foreground
mov eax,[ebx] ;address of Exec program
mov [esi+12],eax
mov eax,[ebx+4] ;address of command line
mov [esi+16],eax
mov ax,word ptr [ebx+8] ;Session type
mov [esi+30],ax
$Call DosStartSession,esi,offset SessID,offset ProcID
.ENDIF
.ENDIF ;End ELSE
mov eax,TRUE ;Message Processed
.ENDIF ;End WM_USER+100h
wm0: ;not Processed
;----- Restore Stack Pointer and Stack Status
mov esp,ebp ;restore stack pointer
pop ebp
;----- Default Procedure * Return Value in eax ------
$Call WinDefWindowProc,parm1,parm2,parm3,parm4
ret
MainWinProc endp
;-------------- MainPaint * WM_PAINT --------------
MainPaint proc Near SYSCALL uses eax, var1:DWORD ;var1 = hwnd
mov eax,TRUE ; processed
ret
MainPaint endp
;-------------- InitMainWindow * WM_CREATE --------------
;var1 = hwnd, var2 = mp1, var3 = mp2
InitMainWindow Proc Near SYSCALL, var1:DWORD,var2:DWORD,var3:DWORD
mov eax,TRUE ; processed
ret
InitMainWindow Endp
;-------------- MainKeyBoard * WM_CHAR --------------
;var1 = mp1,var2 = mp2,var3 = hwnd
MainKeyBoard Proc Near SYSCALL uses ebx ecx,var1:DWORD,var2:DWORD,var3:DWORD
mov eax,TRUE ; processed
ret
MainKeyBoard Endp
;if there are spaces this exits pointing to next non-space else points to non-space
SkipSpaces proc ;just skips over spaces
.WHILE ecx < fsize && byte ptr [esi+ecx] == ' ' ;spaces
inc ecx
.ENDW
.IF ecx >= fsize
mov EOFflag,1
.ENDIF
ret
SkipSpaces endp
SkipCommentsToEOL proc ;skip from ; to beginning of next line
.IF byte ptr[esi+ecx] == ';' && ecx < fsize
inc ecx
.WHILE byte ptr [esi+ecx] != 0ah && ecx < fsize ;Line feed end of line
inc ecx
.ENDW ;points to 0ah if it exists if not EOF
.ELSE
mov eax,ecx ;bytes processed appears in message
$WinDebugMessage msgFileErr
call ExitWin
.ENDIF
.IF byte ptr[esi+ecx] != 0ah ;must be EOF
mov EOFflag,1
.ELSE ;it is end of line
inc ecx ;goto next line
.IF ecx >= fsize
mov EOFflag,1
.ENDIF
.ENDIF
ret
SkipCommentsToEOL endp
DataFormatErr Proc
mov eax,ecx ;bytes processed appears in message
$WinDebugMessage msgFileErr
call ExitWin
ret
DataFormatErr EndP
ExitWin Proc
$Call WinDestroyMsgQueue,hmq
$Call WinTerminate,hab
$DosExit ;so exit
ret
ExitWin Endp
END startup ;required