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