Hooks and Sockets, the Low Side of VO
This article will present some concepts that will help move Visual Objects programmers to the next level. The following topics will be covered:
Hooks
Hooks provide a method for you to intercept and selectively control the messages destined to other apps (or the current running app).
Why use hooks? Hooks can be very useful. As you can attach to any thread, you could write an application that can interact with another application that you do not have the source code for. A fine example of this is the VOPP. Paul Piko did not have the source to the Source Code Editor but he wanted to write an add-on for it. He accomplished this via hooks.
Another use for hooks are parental controls. With them, you can intercept URLs before they are processed from IE and make a choice on if the request is processed. Unfortunately, there is also spyware and viruses. They also use the hook API to steal information.
Why use hooks?
Types of hooks
There are several types of hooks that you can access. There are different types of hooks depending on what you are trying to do. Below is a table that shows the different types of hooks and what they attach to.
| WH_MSGFILTER |
Messages targeted at a dialog box, message box, menu or scroll bar within an application |
| WH_SYSMSGFILTER |
Messages for all applications in the system targeted at dialog boxes, message boxes, menus or scroll bars |
| WH_SHELL |
Shell related messages, e.g. window creation/destroy |
| WH_KEYBOARD |
Keystroke messages |
| WH_MOUSE |
Mouse messages |
Now that we have seen the constants, lets look at a keyboard hook and see what it can do.
Object Inspector
One of the most useful utilities that I have seen is the Object Inspector from Paul Piko. With it included as a library, you can get runtime information about your app without having to run it in the debugger. It is a good example of how to hook the keyboard in an application.
IF Upper(Trim(SELF:ogsUser)) == "WCM"
SetKeyHook(@KeyWatch())
ENDIF
FUNCTION SetKeyHook(lpfn AS PTR) AS PTR
gpKeyFunc := lpfn
gKeyHook := SetWindowsHookEx(WH_KEYBOARD,@KeyHook(),;
_GetInst(), GetCurrentThreadid())
RETURN gKeyHook
FUNCTION KeyWatch(wParam AS DWORD,lAltKey AS LOGIC,
lExtKey AS LOGIC, lWasDown AS LOGIC,;
lReleased AS LOGIC) AS LONG PASCAL
LOCAL cChar AS STRING
LOCAL lShift, lCtrl, lAlt AS LOGIC
LOCAL lKeyDone AS LOGIC
LOCAL h AS PTR
LOCAL o AS OBJECT
LOCAL oWinOI AS OBJECT
IF ! lWasDown
lShift := LOGIC(_CAST,_And(GetKeyState(VK_SHIFT),;
SHORT(_CAST,0x8000)))
lCtrl := LOGIC(_CAST,_And(GetKeyState(VK_CONTROL),;
SHORT(_CAST,0x8000)))
lAlt := LOGIC(_CAST,_And(GetKeyState(VK_MENU),;
SHORT(_CAST,0x8000)))
cChar := CHR(wParam)
IF wParam == VK_F11 .and. ! lWasDown
lKeyDone := TRUE
h := GetForegroundWindow()
o := GetObjectByHandle(h)
oWinOI := WinObjectInspector{}
oWinOI:show()
oWinOI:AddItem(o)
ENDIF
ENDIF
RETURN if(lKeyDone,-1,0)Linking Hooks
Listing 1: Setting a key hook
Multiple hooks can be attached and you chain them via CallNextHookEx(). However, if you want to eat the keystroke and not process it, you simply do not call CallNextHookEx.
Unloading a Hook
Once you are finished with a hook, you unload it via UnhookWindowsHookEx().
Handling “Special” Keys
Sometimes there are reasons you want to disable CTRL-ESC or ALT-TAB. Here is a way to do this. Please note that this requires NT4 SP3 or higher. There is a different procedure for Windows 98 and Windows 95. With NT, you use a keyboard hook called WH_KEYBOARD_LL. Below is the code to stop CTRL-ESC and ALT-TAB
METHOD Start() CLASS app
LOCAL hHook AS PTR
hHook := SetWindowsHookEx(WH_KEYBOARD_LL,@MyKey(),;
_GetInst(),0)
TEXTBox{,"Waiting…","Ctrl-Esc/Alt-Tab are disabled.”+;
“Close this window to re-enable them."}:show()
UnhookWindowsHookEx(hHook)
RETURN NIL
FUNCTION MyKey(iCode AS INT, wParam AS DWORD,;
lParam AS LONG) AS LONG PASCAL
LOCAL lKillIt AS LOGIC
LOCAL lAltOn AS LOGIC
LOCAL lCtrlOn AS LOGIC
LOCAL pKeyInfo AS _WinKbDLLHookStruct
IF iCode == HC_ACTION
IF wParam == WM_KEYDOWN .or. wParam = 260
pKeyInfo := PTR(_CAST,lParam)
lAltOn := LOGIC(_CAST, _And(GetKeyState(VK_MENU),;
SHORT(_CAST, 0x8000)))
lCtrlOn:= LOGIC(_CAST,;
_And(GetKeyState(VK_CONTROL),;
SHORT(_CAST, 0x8000)))
IF pKeyInfo.vkCode == VK_ESCAPE .and. lCtrlOn
lKillIt := TRUE
ENDIF
IF pKeyInfo.vkCode == VK_TAB .and. lAltOn
lKillIt := TRUE
ENDIF
ENDIF
ENDIF
RETURN if(lKillIt,1,;
CallNextHookEx(NULL,iCode,wParam,lParam))
STRUCTURE _WinKbDLLHookStruct
MEMBER vkCode AS DWORD
MEMBER scanCode AS DWORD
MEMBER flags AS DWORD
MEMBER dwTime AS DWORD
MEMBER dwExtraInfo AS DWORD
DEFINE WH_KEYBOARD_LL := 13Sockets
Listing 2:Intercepting CTRL-ESC and ALT-TAB keystrokes
To do the same thing for Windows 95 or Windows 98 you would have to call SystemParametersInfo (SPI_SETSCREENSAVERRUNNING). To disable ALT+TAB and CTRL+ESC, set the uiParam parameter to TRUE; to enable the key combinations, set the parameter to FALSE. You can also do the same thing by registering hotkeys for ALT-TAB and CTRL-ESC.
Why the difference?
The reason you have to handle special keys differently between the Windows 95, Windows 98, and the higher operating systems (NT SP3, 2000, and XP) is that Microsoft did not implement low level hooks until NT SP3.
Sockets
You hear the term sockets all the time in books and online. But what is a socket and how can you use it? This section of the paper will go into the socket, where it fits on the OSI model, and what you can do with it.
What is a socket?
At it’s simplest level a socket is the combination of a TCP/IP address and a port. There are two basic types of sockets, connection based or “stream” socket and connectionless based or “datagram” socket.
A stream sockets is based on TCP or Transmission Control Protocol. It is used in applications like telnet, HTTP, etc.
A datagram socket is based on UDP or User Datagram Protocol. UDP is used to routing. The big difference between UDP and TCP is that UPD does not support sequencing and is usually used for packet transmissions such as DHCP.
Both TCP and UDP runs at layer 4 of the OSI model.
A socket is the combination of a TCP/IP address and a port
OSI model
The OSI model describes different layers of an application. At the highest layer is the presentation layer. This is the GUI that we are all familiar with. But as we go lower, the data is broken up into smaller pieces so it can eventually be placed on the wire. That layer is layer 1, the physical layer. Here are out 7 Layers:
| 7 |
Application |
GUI / end user applications. |
| 6 |
Presentation |
Converts the information from application to network format. |
| 5 |
Session |
Handles and manages communications between the workstation and the network. |
| 4 |
Transport |
Provides end to end communication control. This layer adds flow control (in the case of TCP) and divides the stream of data into packets. |
| 3 |
Network |
Routes the information in the network. |
| 2 |
Data Link |
Provides error control between adjacent nodes. This layer is divided into 2 subsections, the LLC (logical Link Layer) and the MAC (Media Access Control. |
| 1 |
Physical |
Connects the entity to the transmission media. |
Figure 1 shows the data flow. As the data starts down from layer 7 on computer A, it is broken into a stream. That stream is broken into packets. As we go down layers, additional information is added to the packets so the packets can be tracked across the network. While all data is physically moved at layer 1, the type of network that you are using will actually determine the OSI layer that the transmission request is made on. For example, if you are using a hub, you will be using layer 2 transmissions. Newer switches are layer 3 capable which means that they can route in addition to switch traffic.

Fig. 1: How OSI layers Interact
What does this have to do with Visual Objects
Now that we have had a little background on the packets and what the OSI layers look like, we can move on to controlling sockets from Visual Objects.
Ping
We will start with ping. It is a simple protocol to implement as it uses ICMP (Internet Control Message Protocol). When working with ICMP, the firs thing that you have to do is to get a handle to the ICMP file.
SELF:hFile := IcmpCreateFile()
IF SELF:hFile == INVALID_HANDLE_VALUE
// see if we had an error
SELF:nErrCode := GetLastError()
SELF:cLastError := "Unable to Create File Handle"
ENDIF
Listing 3: Get the ICMP filehandle
The application here is a very simple form. It will default the IP to the IP of the current machine. However, you can enter IP in either numeric or url format. The GetIPAddress function is Visual Objects will convert the IP entered into the correct format.
Flow of Ping
Basically, with ping the flow is as follows:
- Get the IP address of the machine;
- Set up the structures;
- Set the options such as TTL;
- Allocate memory for the return packet;
- Issue the icmpsendecho Windows API call;
- Check the result codes.
Here is the code for ping:
METHOD Ping(cHost AS STRING) AS LONG PASCAL CLASS tcpip
LOCAL nResult AS WORD
LOCAL nIP AS DWORD
LOCAL nBufferSize AS DWORD
LOCAL cIP AS STRING
LOCAL cBuffer AS PSZ
LOCAL pOptInfo IS IPOptionInformation
LOCAL pEchoReply AS ICMPEchoReply
try
cIP := GetIPAddress(cHost)
nIP := inet_addr(String2Psz(cIP))
IF nIP == 0
SELF:cLastError := "Error resolving IP"
SELF:cResponse := SELF:cLastError
SELF:nErrCode := -9
BREAK S_FALSE
ENDIF
pOptInfo.TTL := SELF:nTimeToLive
SELF:cLastError := ""
SELF:nErrCode := 0
SELF:cResponse := ""
nBufferSize := _sizeof( IcmpEchoReply ) + SELF:nSize
pEchoReply := MemAlloc( nBufferSize )
cBuffer := PSZ(PadR("Ping from VO 2.7",SELF:nSize))
nResult := IcmpSendEcho( SELF:hFile, nIP, cBuffer, ;
WORD(SELF:nSize),@pOptInfo,pEchoReply,;
nBufferSize,SELF:nTimeOut)
IF GetLastError() == 0
SELF:cResponse := "Reply from " + cIP + ;
": time =" + NTrim(SELF:nRoundTrip) + " ms"
ELSE
SELF:cLastError := "Timeout"
SELF:cResponse := SELF:cLastError
SELF:nErrCode := -10
BREAK S_FALSE
END IF
IF pEchoReply.Status = 0
SELF:nRoundTrip := pEchoReply.RoundTripTime
ELSE
SELF:cLastError := "Failure ..."
SELF:nErrCode := -11
SELF:cResponse := SELF:cLastError
BREAK S_FALSE
END IF
MemFree( pEchoReply )
catch
IF !IsNumeric(uxError)
SELF:nErrCode := GetLastError()
IF SELF:nErrCode > 0
SELF:cLastError :=;
SystemErrorString(SELF:nErrCode,"test")
ELSE
SELF:cLastError := "Failure ..."
ENDIF
SELF:cResponse := SELF:cLastError
ENDIF
Endtry
NBTStat
Listing 4: Ping!
NBTStat is a much more complex sockets application, but it really shows the power of a single packet call. NBTStat uses the WinSock library which is installed on every Windows machine. NBTStat needs basic structure information. The following structures are used by NBTStat:
STRUCT NBPkt
MEMBER XactionID AS WORD
MEMBER Flags AS WORD
MEMBER QCount AS WORD
MEMBER ACount AS WORD
MEMBER NSCount AS WORD
MEMBER ARCount AS WORD
MEMBER DIM QName[33] AS BYTE
MEMBER QType AS WORD
MEMBER QClass AS WORD
STRUCT NodeName
MEMBER DIM bName[16] AS BYTE
MEMBER RRFlag AS WORD
STRUCT Response
MEMBER TTL AS LONG
MEMBER RDLength AS WORD
MEMBER Num_Names AS BYTE
Listing 5: Defining structures for NBTStat
NBTPkt is the packet that will be placed on the wire. NodeName is the node information that will be brought back and Response is the response packed that will come back.
Flow
Here is the flow for NBTStat:
- Get IP address to poll;
- Start up the socket and tell it that it is a Datagram socket;
- Set the timeout;
- Bind the socket;
- Connect the socket to the port (137);
- Set up our packet information;
- Send the packet;
- Receive return packet;
- Process the results;
- Disconnect the socket.
The sourcecode of NBStat can be downloaded from www.sdn.nl
Other Uses
Once you are familiar with sockets, you will begin to realize the power that a language such as Visual Objects provides. Being able to handle low level events makes it possible to create applications that sit on your server and listen for a socket connection. Once it gets the connection, you can communicate and perform set tasks. One example of this would be a remote RDD. One that sits on your server and listens for your connection and then serves DBF data back to your application via IP.
Another common use for sockets is to set up your application to “phone home”. When your application starts, it creates a sockets connection to your server and sends a simple message to let your server know that it is alive and logged in.