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
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.
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.
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.
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.
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 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 |
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.
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
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.
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.
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).
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.
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
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))
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 !!!