"Deep
in the sea are riches beyond compare.
But if you seek safety, it is on the shore."
API
(Application Program Interface)
Callbacks
A callback is a function you write and tell Windows
to call for some reason. You create your own function with a specified
number and type of parameters, then tell Windows that this function
should be called for some reason and its parameters filled with
some info you need. Then Windows calls you function, you handle
the parameters and exit from the function returning some kind
of value.
A typical use of callbacks is for receiving a
continuous stream of data from Windows. Here is the declaration
of a function that requires a callback:
Declare
Function EnumWindows Lib "User32" _
ByVal lpEnumFunc As Long, ByVal lParam As Long)
As Long
The
first parameter is the address of your callback function, and
the second is a whatever value you want. This value will be passed
to your function, so that you know what it is called for.
VB 5.0 has provided a useful operator called AddressOf
which returns the address of a function. It may be used only in
front of a parameter when you call a function and uses like
FuncP
= AddressOf MyFunction
are
wrong and cause error. So, you must call EnumWindows like that:
Success&
= EnumWindows(AddressOf cbFunc, 58&)
You
must also write the callback function. There are different type
of callbacks that have a different sets of parameters. Description
of this parameter can be found in a SDK Help file or MS SDK documentation.
Here is the declaration for the callback:
Function
cbFunc (ByVal Hwnd, ByVal lParam) as Long
Here
is a sample of callbacks:
Private
Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA"
_
(ByVal hwnd As Long, ByVal lpString As String,
_
ByVal cch As Long) As Long
Success&
= EnumWindows(AddressOf cbFunc, 58&)
Function
cbFunc (ByVal Hwnd, ByVal lParam) as Long
If lParam = 58 then 'enum windows
Str$ = Space(255)
Ret& = GetWindowText(Str$, Len(Str$))
Debug.Print Left(Str$, Ret&)
End If
End Function
This
sample enumerates the captions of all windows (no childs).
The
Window Procedure
Windows does not know anything about events. These
are shipped in VB to hide the actual way Windows informs your
window that something is happening with him. VB gently serves
as a interpreter and translates the Windows language into VBs.
But the reallity is different and you will soon
face it. Imagine you want to know when the user highlights you
menu item (not press, just highlight). VB does not provide such
event, but you've seen how other programs display some text in
the statusbar as you browse their menus. If they can, why you
don't.
OK, here is the rough reallity. Each window has
a special procedure called window procedure. It is actually a
callback function. This function is sent a message any time something
happens with you window. Thus a message (WM_COMMAND) is sent when
the use highlights a menu item.
Why then I can't see this message? This is because
VB creates the window procedure instead of you. When Windows sends
a message, this procedure dispatches it to a certain event and
converts its parametrs into some easier to use parameters of the
event. But, in some cases this procedures just ignores some messages
and can't receive the actual input. If you really need to get
this message, you must subclass your window, which discussed in
another topic.
Here is the declaration of a calback window procedure:
Function
WindowProc(ByVal Hwnd As Long, ByVal wMsg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As
Long
The
first parameter specifies the window handle, wMsg is a message
identifier (like WM_COMMAND or WM_MOUSEMOVE), wParam and lParam
are 32-bit values which meaning depends on the type of message
sent.
SubClassing
When you have already used the maximum VB offers
you and want to do something more, or just want to know something
more about what's going on with your window, now or then you will
find the advantages of subclassing.
Subclassing refers to changing the active window
procedure with a new one. Now this new procedure will receive
all messages coming to your window before the old one. But the
old procedure still exists, it's not lost. If you do not process
a given message, you should call the old procedure to process
it.
Subclassing is done by calling SetWindowLong.
This function changes a specified attribute of the given window.
Here is its declaration:
Declare
Function SetWindowLong Lib "user32" Alias "SetWindowLongA"
_
(ByVal hwnd As Long, ByVal nIndex As Long, _
ByVal dwNewLong As Long) As Long
The
first parameter specifies the window to be subclassed, the second
should be GWL_WNDPROC (-4) and the third should be the address
of the new window procedure. See Callbacks and The Window Procedure.
This function will be called literally every time
your window has the focus and something is going on and in some
other cases (like changing some system parameter by another process).
SetWindowLong return 0 if an error occurs, or
the address of the old window procedure. This address is especially
important and you should save it in a variable or else. It is
used to call the old function when you do not process a message
(in fact you will process less than 1% of all message and will
let the old procedure handle the rest).
Calling the old window procedure is accomplished
by CallWindowProc API function. Here is the declaration:
Declare
Function CallWindowProc Lib "user32" Alias "CallWindowProcA"
_
(ByVal lpPrevWndFunc As Long, ByVal hWnd As Long,
_
ByVal Msg As Long, ByVal wParam As Long, _
ByVal lParam As Long) As Long
The
first parameter is the address of the old procedure and rest are
just the same as the four parameter you receive. Note that you
may change some of the values to control the message process.
For example, when you receive WM_MOUSEMOVE, you get the coordinates
of the mouse from lParam and change them to some other coordinates.
Then the old window procedure will think the mouse is not where
it is actually and may for example show a tooltip of some distant
control or do some other funny things.
The ruturn value you specify is also meaningful.
It depends on the message sent.
It is very important to return the original window
procedure before ending you program. It is usually done in Form_Unload.
Here is how:
Ret&
= SetWindowLong(Me.Hwnd, GWL_WNDPROC, oldWndProcAddress)
If
you miss this line when starting your program through VB, the
result is a crash of VB and loss of any unsaved data. Be careful.
Here
is a simple example of subclassing:
Dim
oldWndProc As Long
Private
Sub Form_Load()
oldWndProc = SetWindowLong(Me.Hwnd, GWL_WNDPROC,
AddressOf MyWndProc)
End Sub
Private
Sub Form_Unload()
Ret& = SetWindowLong(Me.Hwnd, GWL_WNDPROC,
oldWndProc)
End Sub
Function
MyWndProc(ByVal Hwnd As Long, ByVal wMsg as Long, _
ByVal wParam As Long, ByVal lParam As Long)
Debug.Print wMsg & " " &
wParam & " " & lParam
Ret& = CallWindowProc(oldWndProc, Hwnd, wMsg,
wParam, lParam)
End Function
Handling
Parameters
Sometimes the functions do not return the information
you need the way you want it. Typical example is combining two
integer(2-byte) values specifying the mouse position into one
4-byte value. Another case is telling you that if bit 29 is on
it means something. Also, you may receive a Long value that is
the address of a structure.
Combining or separating values does not need any
description. APIMacro.bas you may find on our site (www.geocities.com/SiliconValley/Lab/1632/)
contains all functions you need to have.
To check if bit N of Value is on use the following:
If
Value and (2^N) then ...
To
set a bit on:
Value
= Value Or 2^N
To
set a bit off:
Value
= Value And Not 2^N
If
you set and get the state of a bit you know beforehand, its much
faster to replace 2^10 with 1024. This way VB won't have to calculate
it itself (VB "hates" ^).
If you receive a pointer to a type, then you must
do a bit more. To get the info, you use CopyMem function. Here
is the declaration:
Declare
Sub CopyMem Lib "kernel32" Alias "RtlMoveMemory"
_
(pDest As Any, pSource As Any, ByVal ByteLen As
Long)
If
you receive a pointer to RECT type in the Long variable Addr,
use the following:
Dim
Info As Rect
Call CopyMem(Info, ByVal Addr, len(Info))
Note
the ByVal keyword. Now, if you need to put the information back,
use:
Call
CopyMem(ByVal Addr, Info, Len(Info))
Closing
Words
I hope this tutorial has helped you understand
how to control the power of API functions and how to use them
properly. But BEWARE! It's like the fire, you let him out of control
and you are lost. And, of course, never forget that VB is designed
for easy and safe programing and API foes straight against. If
you are looking for more control and power, better move to VC++
or Delphi.
To expand your knowledge of APIs and get some
expeience, be sure to take a look at the sample on this site (www.geocities.com/SiliconValley/Lab/1632/).
Almost all of them are dedicated to API and its advantages.
Also, if you have any questions, something has
left misunderstood, or whatever it is, be sure to e-mail. I'll
try to solve your problem.
Good luck, happy exploring the API !!!
-by
Invincible(psycho@nepalimail.com) |