Lesson Objectives
Upon completion of this lesson, the participant will be able to:
Some Topics to be introduced in this lesson include:
API's and DLL's
A DLL (Dynamic-link library) is a set of procedures that can be called from windows applications. There are plenty of standard DLL's available. The Windows API includes the User, GDI, and Kernel libraries. Other examples of API's are MAPI (messaging API) and TAPI (telephony API). Documentation about what procedures are in various API libraries is usually found in the appropriate SDK (software development kit). VB3 Professional Edition includes Win31wh.hlp, a help file that ships with the Windows SDK. It's in the WinAPI subdirectory of VB3, and documents Windows API procedures. Win31wh.hlp uses C language syntax, but you need not be a C programmer to get the gist of what a procedure is for. To call a DLL procedure from a macro, you first declare it in the declarations area at the top of a module. Declarations are technical statements telling your macro where to find the DLL procedure, how to pass data to it (if it takes arguments), and what data the procedure will return, if any. Instead of entering declarations manually, you usually copy them from some documentation text or help file. VB3 also includes Win31API.hlp, which has declarations that you can copy and paste into the declarations area of one of your modules. VB3 and applications that support Visual Basic for Applications can call DLL procedures.
API DECLARATIONS
Before you can use a procedure from a DLL, you must declare it. The declarations are entered or pasted into the declaration area in a module. A Declaration must be on one line, unless the macro editor in the application has a continuation character. For example, in Visual Basic for Applications you can use the underscore character to continue a logical line to the next physical line. You can't do that in VB3.
Most declarations include arguments. When you call the procedure in your macro, you replace the arguments just like you would when calling a macro procedure. Some of the API procedures have flag arguments that must be set to one of a few predefined values. The same documentation that contains the declaration statements also contains a list of global constants that you can copy and paste into the declaration area of a module. You can then use the named constants when you call the procedure. In some cases, you are allowed to pass the sum of two constants for a flag to combined two actions, for example:
SWP_NOMOVE + SWP_NOSIZE
Most of the API declarations are function procedures instead of sub procedures. Often they return only a success/failure flag. You can ignore the return value if you have no use for it, by not assigning a variable when you call the function, and not using ( )'s around the argument list. For example, both of the following are legal:
nPreviousState = ShowWindow(handle, SW_SHOWMAXIMIZED)
ShowWindow handle, SW_SHOWMAXIMIZED
In the declaration examples below, return values that are usually ignored are not discussed. See the API documentation for information on these return values.
There are some examples of API declarations below, along with some comments about what the procedures do. There are also some examples of MSProject and Excel procedures that use them. Most of these examples could be run from any application that supports Visual Basic for Applications (like Excel and MSProject) or from VB3, except where indicated otherwise. These examples require that the API procedures used be declared in the declaration area of a module.
Some Window-Related Procedures
The first set of API procedures manipulate or get information about windows:
GetActiveWindow
Declare Function GetActiveWindow Lib "USER" () As Integer
GetActiveWindow returns the handle to the active window. A window handle is an integer that identifies a window. It's not of much use by itself, but the window handle is usually passed to other procedures when talking actions or requesting data about a specific window. You normally assign it to an integer variable, for example,
handle = GetActiveWindow()
SetWindowPos
Declare Sub SetWindowPos Lib "USER" _
(ByVal hWnd As Integer, _
ByVal hWndInsertAfter As Integer, _
ByVal x As Integer, ByVal y As Integer, _
ByVal cx As Integer, ByVal cy As Integer, _
ByVal wFlags As Integer)
SetWindowPos can be used to move or resize a window, and change the position (zorder) of the specified window within the stack of windows. Below is a description of its arguments:
hWnd
Handle to the window you want to effect.
hWndInsertAfter
A value that specifies the desired zorder. You can either declare and pass one of the constants below, or pass the actual number. If you use the named constants, you must put them in the declaration area of a module:
Global Const HWND_TOP = 0
Global Const HWND_BOTTOM = 1
Global Const HWND_TOPMOST = -1
Global Const HWND_NOTOPMOST = -2
wFlags
A flag that helps describe what you want to do with the window. Here's a few of the possible constants it can be:
Global Const SWP_NOSIZE = &h1
Global Const SWP_NOMOVE = &h2
Global Const SWP_SHOWWINDOW = &h40
Global Const SWP_HIDEWINDOW = &h80
x,y
Cordinates where you want the upper left corner of the window. Ignored if you include SWP_NOSIZE in wFlags.
cx,cy
Width and height you want for the window. Ignored if you include SWP_NOMOVE in wFlags.
ShowWindow
Declare Function ShowWindow Lib "USER" _
(ByVal hWnd As Integer, ByVal nCmdShow As Integer) As Integer
The ShowWindow procedure can make the specified window visible or invisible, as well as maximize, minimize, or normalize it. If you use ShowWindow to hide an application window, then that application will no longer be in the Windows Task List. The return value tells what state the window was in before you called the procedure. You normally won't use the returned value. Below is a description of its arguments:
hWnd
Handle to the window you want to effect.
nCmdShow
A flag that helps describe what you want to do with the window. Here's a few of the possible constants it can be:
Global Const SW_HIDE = 0
Global Const SW_SHOWNORMAL = 1
Global Const SW_SHOWMINIMIZED = 2
Global Const SW_SHOWMAXIMIZED = 3
GetClassname
Declare Function GetClassname Lib "USER" _
(ByVal hWnd As Integer, _
ByVal lpClassname As String, _
ByVal nMaxCount As Integer) As Integer
Each window has a Classname associated with it that can be used in other procedures to get information about the window. The API GetClassname function returns the Classname of the window that you specify. For example, by using this function, you would discover that the Classname of the MSProject application window is "JWinproj-WhimperMainClass".
hWnd
Handle to the window you want to effect.
lpClassname
Memory address or string variable name where you want the Classname placed. You usually pass it the name of a fixed length string variable. When the procedure is done, it will have assigned the Classname to your variable. When you declare the fixed length string variable, give it a length equal to the maximum number of characters you want it to return (in case the Classname is really long). Here's an example of a variable declaration that reserves room for 10 characters:
Dim s As String * 10
After the procedure is done, you could see the Classname with the command:
MsgBox s
nMaxCount
Maximum number of characters you want it to place in lpClassname.
FindWindow
Declare Function FindWindow Lib "USER" _
(ByVal lpClassname As Any, ByVal lpWindowName As Any) As Integer
FindWindow can tell if a specific application is running, even if the application is running invisibly (not in the Windows Task List). You need to provide a Classname or window title bar caption (see GetClassname for a method for discovering application Classnames). The return value is a handle to the window, or 0 if it can't find it. You normally assign the return value to an integer variable, for example,
handle = FindWindow("JWinproj-WhimperMainClass", 0&)
lpClassname
String with the Classname, or 0& to ignore.
lpWindowName
String with the window title, or 0& to ignore
IsWindowVisible
Declare Function IsWindowVisible Lib "User" _
(ByVal hWnd As Integer) As Integer
IsWindowVisible can tell if an application window is visible (in the Windows Task List) or not.
hWnd
Handle to the window you are inquiring about.
You need to provide a handle to the window of interest. Normally the handle returned by FindWindow is used. IsWindowVisible returns True if the window is visible or False if it is not, so IsWindowVisible is normally used in an IF statement.
Examples of Window-Related Procedures
The examples below assume the appropriate declarations and global constants, as described above, have be placed in the declaration area of a module.
Example
This Excel macro gives a message that tells if MSProject is running, and if it is, it tells whether it is in the Windows Task List (running visibly) or not.
Sub WhatAboutMSProject()
Dim handle As Integer
handle = FindWindow("JWinproj-WhimperMainClass", 0&)
If handle = 0 Then
MsgBox "MSProject is not running"
ElseIf IsWindowVisible(handle) then
MsgBox "MSProject is in the Windows Task List"
Else
MsgBox "MSProject is running, but is not " & _
"in the Windows Task List"
End If
End Sub
Example
This macro makes the active window topmost, i.e. "always on top". The coordinates for moving and sizing are ignored, so it passes zeroes for those arguments.
Sub MakeActiveWindowTopMost()
SetWindowPos GetActiveWindow(), _
HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE + SWP_NOSIZE
End Sub
Example
This macro makes the active window not topmost, i.e. "not always on top". The coordinates for moving and sizing are ignored, so it passes zeroes for those arguments.
Sub MakeActiveWindowNotTopMost()
SetWindowPos GetActiveWindow(), _
HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE + SWP_NOSIZE
End Sub
Example
This MSProject macro allows you to discover the Classname of any window. When it runs, it minimizes MSProject to a topmost icon and the caption under the icon will say "Microsoft Project - " followed by the Classname of whatever window is active, for example you would see the following caption under it if Excel were active:
Microsoft Project - XLMAIN
It works by going into a DoEvents loop which uses the API procedures GetActiveWindow and GetClassname, and displays the returned Classname in the MSProject window caption. The fixed length string sClassname is used to hold the returned Classname (maximum 30 characters), and the string sOldClassname is used to tell when the Classname has changed.
Sub ShowClassnames()
'DECLARE A STRING VARIABLE WITH ROOM FOR 30 CHARACTERS
Dim sClassname As String * 30
Dim sOldClassname As String
MsgBox "This MSProject macro displays Classnames " & _
"of the active window under a minimized " & _
"MSProject icon, after the hyphen. " & _
"To end the macro, maximize MSProject."
MakeActiveWindowTopMost
sOldClassname = ""
AppMinimize
Do
DoEvents
GetClassname GetActiveWindow(), sClassname, 30
If sClassname <> sOldClassname Then
ActiveWindow.Caption = sClassname
sOldClassname = sClassname
End If
Loop While WindowState = pjMinimized
ActiveWindow.Caption = ActiveProject.Name
MakeActiveWindowNotTopMost
End Sub
Example
This MSProject macro checks if Excel is running, and if it is, it makes it invisible (removes it from the Windows Task List). If more than one instance of Excel is running, it only effects one instance.
Sub HideExcel()
Dim handle As Integer
handle = FindWindow("XLMAIN", 0&)
If handle = 0 Then
MsgBox "Excel is not running"
Else
ShowWindow handle, SW_HIDE
End If
End Sub
Example
This MSProject macro checks if Excel is running, and if it is, it makes it visible (puts it in the Windows Task List) and maximizes it. If more than one instance of Excel is running, it only effects one instance.
Sub ShowExcel()
Dim handle As Integer
handle = FindWindow("XLMAIN", 0&)
If handle = 0 Then
MsgBox "Excel is not running"
Else
ShowWindow handle, SW_SHOWMAXIMIZED
End If
End Sub
Some Memory Allocation and Clipboard Procedures
These procedures are used for passing data between applications.
GlobalAlloc
Declare Function GlobalAlloc Lib "KERNEL" _
(ByVal wFlags As Integer, ByVal dwBytes As Long) As Integer
Allocates global memory of the specified kind and amount and returns a memory block id.
In Windows, global blocks of memory that can be shared by applications are identified in two ways: By a block id number, and by a memory address. There are also different kinds of memory blocks, for example, moveable blocks that windows is allowed to move around in memory to optimize system performance, fixed blocks that it can't move, and blocks with other characteristics. You tell the GloballAlloc function what kind of memory you want and how much. It returns 0 if there isn't enough available, otherwise it returns the id number for the block. You normally assign it to an integer variable, for example:
MemoryBlockId = GlobalAlloc(GMEM_DDESHARE, 100&)
If MemoryBlockId = 0 Then
MsgBox "Not enough free memory"
End
End If
dwBytes
Long integer that tells how many bytes to allocate.
wFlags
Determines what "kind" of memory. The safest kind to use is DDE share memory, because windows will automatically free this memory when the application that allocated it quits. The corresponding constant for this type of memory is shown below. See the API documentation for other possible kinds of memory.
Global Const GMEM_DDESHARE = &h2000
GlobalLock
Declare Function GlobalLock Lib "KERNEL" _
(ByVal hMem As Integer) As Long
Locks the specified memory block and returns an address. You must lock it before you can use it. When locked, Windows will insure that no other applications will mess with the contents of that memory block. It's analogous to record locking. In addition, GlobalAlloc is used to convert memory block id numbers into memory block addresses. Some procedures must be passed ids, while others require addresses. You normally assign it to a long integer variable, for example:
MemoryBlockAddress = GlobalLock(MemoryBlockId)
where MemoryBlockId might have been the result of a GlobalAlloc call.
hMem
Id of the memory block you want to lock.
GlobalUnlock
Declare Function GlobalUnlock Lib "KERNEL" _
(ByVal hMem As Integer) As Integer
hMem
Id of the memory block you want to unlock. You must unlock a global memory block before you can free it.
GlobalFree
Declare Function GlobalFree Lib "KERNEL" _
(ByVal hMem As Integer) As Integer
hMem
Id of the memory block you want to free.
lstrcpy
Declare Function lstrcpy Lib "KERNEL" _
(ByVal lpString1 As Any, ByVal lpString2 As Any) As Long
This function is used to copy data from one string (or memory block) to another. The content of lpString2 is copied to into the string lpString1; lpString1 must have enough room to hold lpString2. The character 0 marks the end of a string or the data to be copied from a memory block, up to a maximum of 64K characters (bytes).
When you pass a string variable or constant to this function, you are actually passing a memory block address. The terms "string" and "memory block address" are interchangeable in this context.
lpString1
Target memory address or string.
lpString2
Source memory address or string.
lstrcpyn
Declare Function lstrcpyn Lib "KERNEL" _
(ByVal lpString1 As Any, ByVal lpString2 As Any, _
ByVal nChars As Integer) As Long
Like lstrcpy, but it allows you to specify a maximum number of characters to copy. Much safer.
lpString1
Target memory address or string. You must reserve at least as much room (minus 1) as you specify by the nChars argument.
lpString2
Source memory address or string.
nChars
The maximum number of characters you want to copy. You actually have to specify one more than the number of characters you want, because it counts the 0 character. For example, if lpString2 is the string "Happy" and you specify nChars=3, then lpString1 becomes "Ha".
lstrlen
Declare Function lstrlen Lib "Kernel" _
(ByVal lpString As Any) As Integer
This function is used to get the length of a string .The character 0 marks the end the string. The length returned does not count the 0.
lpString
Memory address or string you want to measure.
OpenClipboard
Declare Function OpenClipboard Lib "USER" _
(ByVal hWnd As Integer) As Integer
Opens the Clipboard so it can be accessed.
hWnd
Handle of the window accessing the Clipboard; you usually pass the result returned from GetActiveWindow().
EmptyClipboard
Declare Function EmptyClipboard Lib "USER" () As Integer
Used to clear the Clipboard.
SetClipboardData
Declare Function SetClipboardData Lib "USER" _
(ByVal wFormat As Integer, ByVal hMem As Integer) As Integer
Used to put data on the Clipboard. You tell it the format of the data and where it's at (pass a memory block id).
wFormat
Constant that determines the kind of data you are copying to the Clipboard, i.e. the Clipboard format. See the API documentation for the possible choices. For text, use:
Global Const CF_TEXT = 1
hMem
Id of the memory block you want "copied to the Clipboard"
GetClipboardData
Declare Function GetClipboardData Lib "USER" _
(ByVal wFormat As Integer) As Integer
Used to get data from the Clipboard. You tell it what kind (format) of data you want. It returns a memory block id for the memory where the Clipboard data is located. You normally assign it to an integer variable, for example:
MemoryBlockId = GetClipboardData(CF_TEXT)
You normally use GlobalLock to convert the id to an address and then use lstrcpy to copy the data into a string variable.
wFormat
Same as in SetClipboardData
CloseClipboard
Declare Function CloseClipboard Lib "USER" () As Integer
You must close the Clipboard so other applications can use it. You can place this before any OpenClipboard call to insure that the Clipboard isn't already open.
Examples of Memory Allocation and Clipboard Procedures
The examples below assume the appropriate declarations and global constants, as described above, have be placed in the declaration area of a module.
Example
This macro allows you to enter text in an InputBox and assign it to a string variable. The contents of the string variable is then placed on the Clipboard, (without placing it in any cell and without using EditCopy). The string variable value is copied to a global memory block, and then the Clipboard is told to point there. Note that you don't use GlobalFree to free up this memory block - the Clipboard is responsible for the memory after you use the SetClipboardData procedure.
Sub PutTextOnClipboard()
Dim MemoryBlockId As Integer
Dim MemoryBlockAddress As Long
Dim s As String
s = InputBox("Enter text to put on Clipboard")
OpenClipboard GetActiveWindow()
EmptyClipboard
'ALLOCATE A GLOBAL MEMORY BLOCK
MemoryBlockId = GlobalAlloc(GMEM_DDESHARE, Len(s))
'CONVERT THE MEMORY BLOCK ID INTO AN ADDRESS
MemoryBlockAddress = GlobalLock(MemoryBlockId)
'COPY THE STRING VARIABLE S INTO THE MEMORY BLOCK
lstrcpy MemoryBlockAddress, s
'TELL THE CLIPBOARD WHERE TO GET ITS DATA FROM
SetClipboardData CF_TEXT, MemoryBlockId
CloseClipboard
MsgBox "Open up the Clipboard. Your string should be there."
End Sub
Example
This macro copies a maximum of 100 characters from the Clipboard directly into a string variable (without placing it in any cell and without using EditPaste). Then the macro shows the contents of the string variable.
Sub GetTextFromClipboard()
Dim MemoryBlockId As Integer
Dim MemoryBlockAddress As Long
Dim s As String * 100 'Must reserve room in the string
OpenClipboard GetActiveWindow()
MemoryBlockId = GetClipboardData(CF_TEXT)
MemoryBlockAddress = GlobalLock(MemoryBlockId)
lstrcpyn s, MemoryBlockAddress, 101 'Plus 1 for the 0 character
GlobalUnlock MemoryBlockId
CloseClipboard
MsgBox "The text on the Clipboard is: " & s
End Sub