Windows API Tutorial - Part One
Hello and welcome to this groovy Windows API tutorial, hosted exclusively here on VB-World.net.
In this two part series, I'll be giving you enough ammo to start using and exploring that thing they call the Windows API. Yessiree, by this time next week you too could be an API guru!
I personally remember my first encounter with the Windows API. I was in a programming class and asked lecturer Mark Jones his thoughts on the curious acronym.
"The API?", laughed that huge mound of blubber. "Hah, you don't want to use that. There are already enough ways to crash your program without using the API!".
As his huge belly wobbled off a final chuckle, the first impressions of Windows API started to slowly set in my mind. Even though I wasn't entirely sure exactly what it was, I knew it was bad. It was evil. It held more crash potential than the F1 Grand Prix track on a snowy day.
A couple of years later, I stumbled across a problem that one book suggested I overcome using the Windows API. So I did. And it worked.
At last, my lifelong fear had vanished. And by the end of today, I hope you too will feel a lot more comfortable with this technology. In fact, I guarantee it*.
* The VB-World network cannot be held responsible for Karl's flatulent use of the word 'guarantee'. After all, readers of the last tutorial were never sent the promised three-year old Mars Bar. Please... please don't sue us. It's not our fault. Blame Karl. We're good people, really.
But enough blabber -- let's ask ourselves a fairly elementary question. What exactly is this API thing?
Note: This tutorial is designed for those using Visual Basic 5/6. The calls represented herein may not work with other versions
So what exactly is this 'Windows API' thing everybody's talking about?
Well, imagine a truck load of ice-cream. Let's pretend this ice-cream is available in many different flavours -- chocolate, mint and strawberry to name just a few. Then imagine consuming it all.
Sounds nice doesn't it? However, err, it has absolutely nothing to do with the API. Apologies to those who were expecting a rather tasty analogy -- and if I do enter dream mode again, feel free to send a digital <slap> this way.
Now, imagine a truck load of mint-chocolate-chip code. This code allows you to do many different things on a computer. One piece of code allows you to find out the attributes of a file. Another lets you to display a colour selection dialog box. Yet another enables you draw pretty circles on the screen.
Sound interesting? Well, that truck load of mint-choc-chip code is the Windows Application Programming Interface (API).
It's basically a mass of coded Windows features hidden inside a few DLL files. These files are split into three distinct flavours, depending on what the code inside them does:
- User32.dll -- handles user interface stuff
- Kernel32.dll -- file operations, memory management
- Gdi32.dll -- involved in graphical whatnots
These three files are collectively known as the 'Win32 API' -- in other words, the main collection of accessible Windows 95/98 code.
But there are a few other DLL files available too, including:
- Comdlg32.dll -- holds all regular dialog boxes, etc
- Winmm.dll -- does all the groovy multimedia thingies
There are plenty more -- boy, are there plenty more - but that will do for now.
So when someone talks about programming with the Windows API, they mean they're "plugging into" these DLL files and using some of their neat functions.
But why bother? Well, much of the time, these files do stuff that Visual Basic can't even think about. They perform low-level operations you never usually hear about. So when you need special stuff that Visual Basic just can't handle, it's probably time to turn to the API.
And another point -- I've seen continents move faster than certain Visual Basic functions. So although you may be able to perform particular operations within VB, you may find running code through the Windows API a lot faster. Sure, it's a little more work -- but it'll pay off in the long run.
Also, let's say you wanted to add a 'File Open' dialog box to your application. You can either distribute the hefty Microsoft Common Dialog OCX -- or directly access the dialogs via the API. The latter option requires no extra controls and hence reduces the final size of your application! Another advantage!
So you see, taking a geek peek at the API can be a jolly groovy move.
In the next section, we'll discover exactly how long your computer has been turned on via an API 'call' -- something you can't simply do in pure non-API Visual Basic code. After that, we'll move onto more complicated (yet infinitely more exciting!) API wizzy whatnots. And after that? Who knows - maybe THE WORLD!
Muhaha! Muhahahaha! MUHAHAHAAAHAAAA! <ahem>
So what are we waiting for? Let's get those hands dirty!
For your first API call, we're going to check out a highly cool yet cunningly simple function. It's called 'GetTickCount' and is a lovely little procedure that hides within kernel32.dll. It returns the number of milliseconds since you started Windows.
- Open Visual Basic and create a new Standard Exe
- Insert a Command Button on Form1
- Add a Module to the project by clicking on Project, Add Module, Open
You typically throw all your API code into a module, making it accessible throughout your application.
Type the following into the module:
Declare Function GetTickCount Lib "kernel32.dll" _Alias "GetTickCount" () As Long
Type the following behind the Form1 Command Button:
Private Sub Command1_Click()Dim lngTickCount As LonglngTickCount = GetTickCountCall MsgBox("You have been using your computer for:" & _vbNewLine & " * " & CStr(lngTickCount) & _" milliseconds, or" & _vbNewLine & " * " & CStr(Round(lngTickCount / 1000)) & _" seconds, or" & _vbNewLine & " * " & CStr(Round((lngTickCount / 1000) / 60)) & _" minutes", vbInformation)' Note to VB5 Users - the Round() function here simply' rids of any numbers after the decimal point. It is' only available in versions 6+. Therefore, VB5 users' should remove the 'Round' wordEnd Sub
Press F5 to run the application and try hitting the Command Button
See what happens? My message box says "You have been using your computer for: 2564012 milliseconds, or 2564 seconds, or 43 minutes". Now is that megacool or what?
Sure, it's about as useful as a solar-powered lamp, but it's something you wouldn't be able to do purely in Visual Basic -- without accessing those external DLLs.
Let's explain what we did here. First off, we declared our API call in the module. It went something like this:
Declare Function GetTickCount Lib "kernel32.dll" _Alias "GetTickCount" () As Long
Here, we're saying we want to declare a function called GetTickCount (or whatever name you want to give it). The workings of this function reside in the "library" of groovy chocolate chip code within "kernel32.dll". The piece of code we want to run inside the DLL has an Alias (name) of GetTickCount (basically, the function name in kernel32.dll). Right at the end, we're telling it the function returns a long data type (the milliseconds).
Top Tip: You may notice that when you type in this code, Visual Basic shortens it to "Declare Function GetTickCount Lib "kernel32.dll" () As Long". That's OK -- when VB notices that both the name of the declaration and the name of the code inside in the DLL are the same -- it removes the Alias bit.
So that's how you declare the GetTickCount API call. How do you 'run' it? Well, you just do something like this:
In other words, you access it just like any normal Visual Basic function!
The code behind our Command Button takes the millisecond information from GetTickCount, then does a little division to figure out the number of seconds and minutes since Windows loaded.
Note: You can download our GetTickCount example by clicking here.
Don't get me wrong -- not all API calls are as simple as this blighter -- but it's a start!
In general, you declare an API call like this:
[Public/Private] Declare Sub/Function name Lib "dllname" _[Alias "aliasname"] [(argumentlist)] [As type]
Looks pretty weird doesn't it? Don't worry about it all just yet -- simply use it as a basic template for declaring API calls. Stuff in [square brackets] means that bit is optional, whilst The/Slash means you should choose OptionOne/OrTwo. The argumentlist is a potential list of arguments that may or may not exist.
That's all well and good, but how did I find out about that GetTickCount function? In the next section, we'll answer this question and more... read on, good sir!
As you might expect, there's an alternative for the message box available via the Windows API. And although Visual Basic has its own excellent MsgBox() statement, I'd like to introduce this wizzy call to you as it helps us demonstrate a few new API concepts.
- Start Visual Basic and create a new Standard Exe
- Draw a Command Button onto Form1
- Add a Module to the project
Now I'd like to introduce you to a tool called the API Viewer. It ships with all versions of Visual Basic and is available by selecting the Microsoft Visual Basic (or Visual Studio) folder on the Start menu, and dipping into the Visual Basic Tools folder. If you can't find it, have a scour around its filename is APILOAD.EXE
The API Viewer is simply a program that lists virtually all of the available Windows API calls you can make in Visual Basic, along with their declaration format.
- Click File, Open
- Select the 'Win32api.txt' file and click Open
- If prompted to change to database format, accept the invitation
You've just loaded all the Windows API calls into the API Viewer program. Let's see whether it lists the GetTickCount() function we played around with just a few minutes ago:
- Select 'Declares' from the API Type combo box
- In the box just above the list of 'Available Items', type in GetTickCount
Do you see it in the list?
- Double-click on 'GetTickCount'
The format for declaring the GetTickCount() routine should appear exactly the same as we typed it. In fact, this is where I got the declaration myself.
For now, let's remove the GetTickCount() declaration from our screen:
- Click on the GetTickCount declaration in the 'Selected Items' box and click 'Remove'
Have a quick scroll up and down the 'Available Items' list. Now, you're probably wondering what all those other items do. Me too. Well, I think I can guess what 'GetTimeZoneInformation' does. And perhaps even 'AbortPrinter'. Erm, but 'WriteConsoleOutputAttribute'? Oh boy...
So don't concern yourself with knowing every single API call. Just be aware that the Windows API is available to help if things look impossible. When you need to do something and you think the API can lend a hand, you could try searching the Microsoft site and related API documentation (www.microsoft.com/api/), checking out the VB-World API section (www.vb-world.net/api/) or even the Complete and Categorised API Bible (www.vbapi.com/ref/). At the end of this tutorial, I'll also be recommending a couple o' books that list the most frequently used calls and how they can brighten your programming life.
Anyway, let's continue:
- Type 'MessageBox' in the box just above 'Available Items'
- Double-click on 'MessageBox' in the combo box
The following should appear in the 'Selected Items' box:
Public Declare Function MessageBox Lib "user32" Alias _"MessageBoxA" (ByVal hwnd As Long, ByVal lpText As String, _ByVal lpCaption As String, ByVal wType As Long) As Long
- Click the 'Copy' button
- Switch back to the copy of Visual Basic you have running
- Open your module and select Edit, Paste
The above code should appear in your module. Great!
Top Tip: You can also put your API declaration code behind a Form but be sure to replace the 'Declare' or 'Public' bit to 'Private' first. Also, bear in mind that only the current form will be able to 'see' it.
Now this declaration is a little different from the one we've seen so far. Let's take a peek.
This one is saying to Visual Basic I'm publicly declaring a function called MessageBox that uses the library called user32.dll (you don't always need to precede with the .DLL extension). Inside the user32.dll, the bit of code I want to run has an alias (name) of "MessageBoxA". Four different arguments are also required two strings, two longs. And as I'm a function, a value will be returned actually, a Long, as the last bit points out.
Phew! Sound complicated? It's a new way of coding and bound to be a little confusing at first, but please, bear with me.
Let's take a moment to analyse those four arguments the API call requires. The very first is devilishly difficult, so don't worry if you can't grasp the concept straight away:
- hwnd (Long) this is something you will see cropping up all over the place. Think of it as a number that points to a form or more geekishly, the 'handle' to a 'window'. You can access the 'handle' of a form, by using it's hWnd property (even seen that before? Go take a peek!). Why does the API call need this 'form handle'? Well, this is how it knows from which form the message box originated.
- lpText (String) this is the message you want to display in the box
- lpCaption (String) this is the message box title
- wType (Long) this is a number that represents what the message box should look like. Should it have an OK and Cancel button? Yes or No? What icons should it display? We'll cover this shortly.
Now if programming were real life, that last argument would be one of those weirdo punks with purple hair and a nose ring - so we'll be all civilised and ignore it for the moment. For now, let's just get a quick message box up and running!
- Open your Form
- Behind the Command Button, enter this code:
Call MessageBox(Form1.hwnd, "Did Video Kill the Radio Star?", _"Whodunnit?", 0)
You should understand pretty much everything here. The first argument is perhaps the most confusing really though, we're just passing the hWnd property of our form, a 'pointer' to it. That's how the API knows what program is displaying the message box.
As we've not discussed that last punk parameter yet wType I've just slotted a lonely '0' in its place.
- Press F5 and click the Command Button
<Me: Does it work?>
You should see a simple message box appear with an OK button. But what if you wanted Retry/Ignore? Or perhaps OK/Cancel? That's where our last parameter comes into play.
Hmm, I think it's time we explained it... sure, you may never use the API to display a message box, but what we'll learn here applies to virtually hundreds of other chocolate chip API calls.
The final argument in the Message Box API declaration is admittedly a little confusing. You're supposed to pass it a number that dictates how the message box should look.
Hmm, let's unravel the mystery now:
- Switch back to the API Viewer
- Change the API Type to 'Constants'
- Scroll down to the constants starting with 'MB'
These are all the Message Box constants, meaning they're words that represent a number. And you can pass these to the wType argument to tell it how it should look.
For instance, can you see the 'MB_ABORTRETRYIGNORE = &H2&' line? Try double-clicking on it. You should see this appear in the 'Selected Items' box:
Public Const MB_ABORTRETRYIGNORE = &H2&
If we add this line to our module, we could pass the word M_ABORTRETRYIGNORE as the wType parameter. Visual Basic translates this into &H2&, which is just a special number in disguise. The API knows that this number means it should displays an Abort/Retry/Ignore box so it does!
Let's add a few of those constants now:
- Add the following code to your module:
Public Const MB_ABORTRETRYIGNORE = &H2&Public Const MB_YESNO = &H4&Public Const MB_YESNOCANCEL = &H3&Public Const MB_RETRYCANCEL = &H5&Public Const MB_OKCANCEL = &H1&Public Const MB_OK = &H0&Public Const MB_ICONSTOP = MB_ICONHANDPublic Const MB_ICONQUESTION = &H20&Public Const MB_ICONASTERISK = &H40&Public Const MB_ICONEXCLAMATION = &H30&
These are all constants uncovered via the API Viewer.
Top Tip: If you were researching this function yourself, you'd find information on the existence of such constants at one of the sources mentioned so far perhaps a book or API reference site.
Now we've added this code, let's try it all again:
- Behind your Form's Command Button, change the code as follows:
Call MessageBox(Form1.hwnd, "Did Video Kill the Radio Star?", _"Whodunnit?", MB_ICONEXCLAMATION)
Here, we're telling the API to display the same message box but with a question icon, as represented by the MB_ICONQUESTION constant.
- Press F5 and test your code
You should see the message displayed, alongside a question mark bubble.
- Try changing that parameter from MB_ICONQUESTION to:
MB_ICONQUESTION Or MB_YESNO
See what happens? Even though you're passing it these two separate items, the API interprets it and realises it should display a yes/no message box with a question bubble icon. (See top tip at bottom of page regarding this)
Hold on a minute. Did Video Kill the Radio Star? If the user clicks No, understandably nothing happens. But if the user clicks Yes, then I think it's time to call in the cops.
In fact, it'd also be pretty cool if we could notify the judge, arrange a trial date, automatically inform members of the jury and schedule a press conference but we'll save that for next week.
So how do we figure out exactly what the user pressed? Well don't forget this is a function that returns a number (Long). And it's that Long value that specifies exactly what the user clicked.
- Alter the code behind your Command Button as follows:
Print MessageBox(Form1.hwnd, "Did Video Kill the Radio Star?", _"Whodunnit?", MB_YESNO Or MB_ICONEXCLAMATION)
Note that I've changed 'Call' to 'Print' which will basically print the Long returned by the MessageBox function onto your form.
- Now run your application and try clicking 'Yes'
The form displays 6.
- And now try clicking 'No'
...and the form displays 7. Great! We already know a couple of responses. A few others are listed in the API Viewer, starting with the letters 'ID'.
We're now going to throw those constants into our module to make life easier:
- Add the following constants to your module:
Public Const IDYES = 6Public Const IDNO = 7Public Const IDABORT = 3Public Const IDCANCEL = 2Public Const IDIGNORE = 5Public Const IDRETRY = 4Public Const IDOK = 1
Getting bored? Me too. Let's just get this darn thingy up and running:
- Enter the following code behind your Command Button:
Private Sub Command1_Click()If MessageBox(Form1.hwnd, "Did Video Kill the Radio Star?", _"Whodunnit?", MB_YESNO Or MB_ICONQUESTION) = IDYES Then Call MessageBox(Form1.hwnd, "The police will be round in five", _ "Thanks for the Tip-Off", MB_OK Or MB_ICONEXCLAMATION)End IfEnd Sub
Here, we're displaying a message box with a question icon and yes/no buttons. If the user clicks on yes, the function returns IDYES (the number 6) and so the code then goes on to display a second message box thanking the user and displaying a pretty yellow exclamation mark. Ahh, ain't that cute? ;-)
Top Tip: It might sound weird to type 'MB_OK Or MB_ICONEXCLAMATION' rather than 'MB_OK And MB_ICONEXCLAMATION' but don't worry about it. It's to do with something called bitwise comparison. And it's an awful subject, so I won't elucidate. Just remember that when putting two or more things together for the API, use 'Or' as the sticky tape.
Note: A download of this Message Box example is available here.
This week, we've taken a lightning tour of the API.
We've discussed what it is, when you would use it and even covered a few useful resource websites. We went on to get our hands dirty with two supercool API calls!
I hope you're feeling all proud. The majority of programmers never even touch the API out of fear. Well done!
Next week, I'll be concluding this mini tutorial. I'll be checking out another host of useful functions, plus introducing a few new concepts. And if you're looking for handy reference guides or online tutorials, I'll have just the information for you.
So until the next time, this is your host Karl Moore wishing you a good night tonight. Goodnight!