Using API in VB
What's API - description of API
API declarations - how to declare APIs
- API functions - types of function
API messages
Handles, Coordinates, Structs, ...
API parameter types - VB equivalents
Any
Passing parameters - ByVal, ByRef, structs,
strings, arrays...
Callbacks
WinProc
Subclassing
-Getting Error result
-Combining flags
Handling parameters
How to know the functions?
-An example
Closing Words
What's API
API (Application Program Interface) is
a set of predefined Windows functions used to control the appearance and
behavior of every Windows element (from the outlook of the desktop window to
the allocation of memory for a new process). Every user action causes the
execution of several or more API function telling Windows what's happened.
It is something like the native code
of Windows. Other languages just act as a shell to provide an automated and
easier way to access APIs. VB has done a lot in this direction. It has
completely hidden the APIs and provided a quite different approach for
programming under Windows.
Here is the place to say that, every
line of code you write in VB is being translated by VB into API function and
sent to Windows. Thus calling something like Form1.Print ... causes VB to call
TextOut API function with the needed parameters (either given by you in the
code, or taken by some defaults).
Also, when the user click a button on
your form, Windows sends a message to your windows procedure (that is
eventually hidden for you), VB gets the call, analyses it and raises a given
event (Command1_Click) for you.
The API functions reside in DLLs (like
User32.dll, GDI32.dll, Shell32.dll...) in the Windows system directory.
API Declaration
As said in “What's API”, functions are
declared in DLLs located in the Windows System directory. You can type in the
declaration of an API just as you do with any other function exported from a
DLL, but VB has provided an easier way to do it. It is called API Text Viewer.
To have some API declared in your
project, just launch API Text Viewer, open Win32Api.txt (or .MDB if you have
converted it into a database to speed it up), choose Declares, find the
function, click Add and then Copy. Go to your project and paste it in. Do the
same to have a predefined constant or type.
There are few problems you may face:
Suppose you want to declare a function
in your form module. You paste the declaration and run the program. VB says
Compile Error, ...and Declare statements not allowed as Public members of ... .
Sounds bad, but it isn't, all you need to do is add Private in front of the
declaration (like Private Declare Function ...). Do not forget, though, that
the function will be visible only within this form module.
In some cases, you may get the message
Ambiguous name detected by VB. It means you have two functions, constants or
whatsoever sharing one name. As most of the functions (maybe all of them, I
haven't checked that) are aliased, which means they are given different names,
and not their original using Alias clause, you may simply change the name of
the function and it will still work.
You may read the Declare statement
help topic of Visual Basic’s for description of Alias.
Messages
OK, now you know what API function is,
but you have definitely heard of messages (if you haven't you will soon do) and
wonder what is this. Messages are the basic way Windows tells your program that
some kind of input has occurred and you must process it. A message to your form
is sent when the user clicks on a button, moves the mouse over it or types text
in a textbox.
All messages are sent along with four
parameters - a window handle, a message identifier and two 32-bit (Long)
values. The window handle contains the handle of the window the message is
going to. The identifier is actually the type of input occurred (click,
mousemove) and the two value specify an additional information for the message
(like where is the mouse cursor when the mouse is been moved).
But, when messages are sent to you,
why don't you see them, looks like someone is stealing your mail. And before
you get angry enough, let me tell you.
The theft is actually VB. But he does
not steal your mail, but instead read it for you and give you just the most
important in a better look (with some information hidden from time to time).
This better look is the events you write code for.
So, when the user moves the mouse over
your form, Windows sends WM_MOUSEMOVE to your window, VB get the message and
its parameters and executes the code you've entered for Button_MouseMove event.
Along the way, VB has transformed the second 32-bit value of the message (it
contains the x and y position in pixels, 16-bit each) into two Single type
value of twips.
Now, say you need the coordinates of
the mouse in pixels. VB converted them into twips, and now you will convert
them into pixels again. Apart from losing time, it is somehow irritating to
know Windows give you what you need and VB "favorably" alters it so
that you must re-alter it. Here you will ask - Can't I receive the messages
myself. OK, there is a way called Subclassing, but you should use it only if
really necessary as it goes a bit against the concepts of safe programming of
VB.
Something else that need to be said:
You can send massages to your own window or to another one yourself. You just
call SendMessage or PostMessage (SendMessage will cause the window to process
the message immediately and Post message will post it onto a queue, called
message queue, after any other messages waiting to be processed (it will return
after the message is processed, i.e. with some delay)). You must specify the
window handle to send the message to, the message identifier (all message
identifiers are available as constants in VB API Text Viewer) and the two
32-bit values.
Some Windows specifics
This topic is intended to give you a
clue about some Windows specifics that are not the same under VB.
Windows identifies every form,
control, menu, menu item or whatever you can think of by its handle. When your
application is run, every control on it is assigned a handle which is used
later to separate the button from the rest of the controls. If you want to
perform any operation on the button through an API you must use this handle.
Where to get it from? Well VB has provided a Hwnd property for all controls
that have handles in Windows.
Windows works with pixels, not twips.
So, it is a good idea to have the controls you'll use API functions over set
their ScaleMode properties to Pixel(3) so that you can use ScaleXXX properties
to get their metrics. But even though, you have this opportunity, you may still
need to convert twips to pixels and vice versa. You do it using TwipsPerPixelX
and TwipsPerPixelY ot the global Screen object. Here it is:
pixXValue = twipXValue \
Screen.TwipsPerPixelX
pixYValue = twipYValue \ Screen.TwipsPerPixelY
twipXValue = pixXValue *
Screen.TwipsPerPixelX
twipYValue = pixYValue *
Screen.TwipsPerPixelY
I haven't
really seen the TwipsPerPixelX and TwipsPerPixelY value to be different, but
its always better to make difference, at least for the good programing style.
Also note that \ (for integer division) is used instead of / as pixels must
always be whole numbers.
Another thing to mention is that
Windows uses different coordinate systems for the functions. So, be careful.
And lastly, don't forget that VB is
safe till the moment you begin to use APIs. A single syntax error in an API
call may cause VB to crash (save often!). Also VB cannot debug APIs and if your
program is crashing or behaving awkwardly, firstly check the API calls - for missed
ByVal, for mistaken type or parameter, everything).
Where to get the functions description from
This topics won't tell how to change
the button text through API or how to find a file quickly. It is not a API
functions documentation.
To get the description of an API
function, you need to have either SDK help file or the Microsoft SDK
documentation (it's more that 40MB I think - how can I place it here?). Such
SDK helps are shipped with Borland Delphi 3.0 package or MS Visual C++ 5.0 for
example. Search the internet are ask your friends to get one. The newer it is
the better.
Note that SDK help for Win 3.x won't
help you as some functions are obsolete, though most of them still exist for
compatibility in Win95.
API parameter types
When you already have an SDK Help, you
will surely notice that functions return values and parameters have some
strange types like VOID, LPCSTR or DWORD. If you are familiar with C, then you
know what they mean. For the rest, here is a table taken from VB Books Online
(topic is ‘Converting C Declarations to Visual Basic’):
C language data type |
In Visual Basic declare as |
Call with |
ATOM |
ByVal variable
As Integer |
An expression
that evaluates to an Int. |
BOOL |
ByVal variable As Long |
An expression
that evaluates to a Long |
BYTE |
ByVal variable
As Byte |
An expression
that evaluates to a Byte |
CHAR |
ByVal variable
As Byte |
An expression
that evaluates to a Byte |
COLORREF |
ByVal variable As Long |
An expression
that evaluates to a Long |
DWORD |
ByVal variable As Long |
An expression
that evaluates to a Long |
HWND, HDC,
HMENU, etc. (Windows handles) |
ByVal variable As Long |
An expression that evaluates to a Long |
INT, UINT |
ByVal variable As Long |
An expression
that evaluates to a Long |
LONG |
ByVal variable As Long |
An expression
that evaluates to a Long |
LPARAM |
ByVal variable As Long |
An expression
that evaluates to a Long |
LPDWORD |
variable As Long |
An expression
that evaluates to a Long |
LPINT, LPUINT |
variable As Long |
An expression
that evaluates to a Long |
LPRECT |
variable As type |
Any variable of
that user-defined type |
LPSTR, LPCSTR |
ByVal variable As String |
An expression that evaluates to a String |
LPVOID |
variable As Any |
Any variable (use ByVal when passing a
string) |
LPWORD |
variable As Integer |
An expression
that evaluates to an Int. |
LRESULT |
ByVal variable As Long |
An expression
that evaluates to a Long |
NULL |
As Any or ByVal variable As Long |
ByVal Nothing
or ByVal 0& or vbNullString |
SHORT |
ByVal variable As Integer |
An expression
that evaluates to an Int. |
VOID |
Sub procedure |
Not applicable |
WORD |
ByVal variable As Integer |
An expression
that evaluates to an Int. |
WPARAM |
ByVal variable As Long |
An expression
that evaluates to a Long |
Notes:
You should notice that the BOOL type
(boolean) evaluates to Long and not Boolean. So, 0 refers to False and any
other value to True.
HWND, HDC, HMENU, etc. - etc. means
there also other types like these. All of them begin with H and stand for
handles for different type of objects. For example HBITMAP is a bitmap handle,
HBRUSH is a brush handle and so on. They all evaluate to Long and should be
passes ByVal.
Notice also that LPVOID is declared as
variable As Any. There is a separate topic dedicated to Any.
Some types begin with LP. It is an
abbreviation of Long Pointer to. So LPWORD is actually a memory location where
the data is stored. No, you won't have to call a function to get this address.
When you pass your argument ByRef (the defaul) you actually pass its address.
The thing to remember here is that, if you parameter type begins with LP - you
should pass it ByRef. By the way LPARAM is like Lparam and not LParam. It is
not a pointer. You must pass the actual value here, so it is passed ByVal.
There is also some strange type NULL.
You know from VB so I won't discuss it here. Just choose a way you will pass it
when needed. In most of the cases I see passing it as ByVal 0& or as
vbNullString.
And lastly, VOID is used for functions
return value to specify that there is no such value. API doen not have Subs so
this is the it implements them. Just remember - if the function is declared as
VOID - you must declare it as Sub in your VB code.
Any
Some messages contain parameters
declared as Any. It means this parameter can be a variety of types (you may
pass an integer, a string, or a user-defined type, or else). So, here is an
example of a function (SendMessage) which contains a parameter of type Any:
Public Declare
Function SendMessage Lib "User32" Alias "SendMessageA" _
ByVal Hwnd as Long, ByVal wMsg as
Long, _
ByVal wParam as Long, lParam as Any) as Long
lParam is declared ByRef (default) and as Any. Now, here are some rules to
follow when passing different type of values to this function as lParam.
If the value
is: Pass it As:
numeric ByVal
(as Long, or as Any)
Null
ByVal (as Long, or as Any)
string ByRef (as String, or as Any)
Type
ByRef (as Any)
array of Type ByRef
(as Any)
If your function declaration looks like the one
above, and you need to pass a Long, write something like:
Call SendMessage(Me.Hwnd, WM_XXXX, 0&, ByVal LongValue)
Note that there is nothing in front of the first three parameter although
they are numeric values. This is so, because in the function declaration they
are declared as ByVal. The fourth parameter, though, is declared ByRef (VB
doesn't know what kind of values you are going to pass) and you must explicitly
specify ByVal in front of it.
Sometimes it's much simpler
to just declare several versions of one function and use a different one for
different calls. You may declare something like:
Public Declare Function SendMessageLng Lib "User32" Alias
"SendMessageA" _
(ByVal Hwnd as Long, ByVal
wMsg as Long, _
ByVal wParam as Long, ByVal lParam as Long) as Long
OR:
Public Declare Function SendMessageStr Lib "User32" Alias
"SendMessageA" _
(ByVal Hwnd as Long, ByVal
wMsg as Long, _
ByVal wParam as Long, lParam as String) as Long
Notice that the parameter type does not change for API. The fourth
parameter is always a 4-byte Long value. When you pass a Long or Null ByVal, a
the 4-byte value is passed directly to the function. If you pass a String or
else, you pass it ByRef and so VB actually passes the address of you variable,
and it is a 4-byte value again.
Passing Parameter
Of course you know how to
pass parameters, just put it in the function call and its done! Well there are
some details you should be aware of when passing parameters to API function.
ByVal or ByRef. Usually you
don't have to bother about these keywords as VB API Text Viewer declares the
function parameters as API wants them and when you just enter your value, it is
passed as is declared. Generally, when a value is passed ByVal, the actual
value is passed directly to the function, and when passed ByRef, the address of
the value is passed. The only thing you may encounter is the Any type.
Passing strings to API
function isn't difficult too. The API expects the address of the first
character of the string and reads ahead of this address till it reaches a Null
character. Sound bad, but this the way VB actually handles strings. The only
thing to remember is always to pass the String ByRef.
The situation is slightly
different when you expect some information to be returned by the function. Here
is the declaration of GetComputerName API function:
Declare Function GetComputerName Lib "kernel32" Alias
"GetComputerNameA" _
(ByVal lpBuffer As String,
nSize As Long) As Long
The first parameter is a long pointer to string, and the second the length
of the string. If you just declare a variable as String and pass it to this
function, an error occurs. So, you need to initialize the string first. Here is
how to get the computername:
Dim Buffer As String
Buffer = Space(255)
Ret& = GetComputerName(Buffer, Len(Buffer))
if Ret& > 0 then CompName$ = Left(Buffer, Ret&)
Here, the string is initialized as 255-spaces string. We pass it to the
function and give it the length too. The function returns 0 for an error or the
actual length of the computer name otherwise. CompName$ will contain the
computer name.
Some functions also expect
arrays. Here an exmaple:
Declare Function SetSysColors Lib "user32" Alias
"SetSysColors" _
(ByVal nChanges As Long,
lpSysColor As Long, _
lpColorValues As Long) As
Long
The last two parameter are arrays of Long. To pass an array to a function,
you pass just the first element. Here is a sample code:
Const COLOR_ACTIVECAPTION = 2
Const COLOR_INACTIVECAPTION = 3
Const COLOR_CAPTIONTEXT = 9
Const COLOR_INACTIVECAPTIONTEXT = 19
Dim SysColor(3) As Long
Dim ColorValues(3) As Long
SysColor(0) = COLOR_ACTIVECAPTION
SysColor(1) = COLOR_INACTIVECAPTION
SysColor(2) = COLOR_CAPTIONTEXT
SysColor(3) = COLOR_INACTIVECAPTIONTEXT
ColorValues(0) = RGB(58, 158, 58) 'dark green
ColorValues(1) = RGB(93, 193, 93) 'light green
ColorValues(2) = 0 'black
ColorValues(3) = RGB(126, 126, 126) 'gray
Ret& = SetSysColors(4&, SysColor(0), ColorValues(0))
This sample changes the system colors for the active window caption
background and text and for the inactive window caption background and text.
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, exploring the API !!!