Lesson 8: API's and DLL's

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

Lesson 8 Exercises

  1. Write an MSProject macro that starts a new instance of the Calculator application that comes with Windows (Calc.exe) and makes it a top most window.


  2. Write an MSProject macro that clears the Clipboard and then waits in a DoEvents loop until it sees the word "hello" on the Clipboard. At that point, it activates the Microsoft Project window and displays a message "goodbye". Use the appropriate API calls to do this - see the GetTextFromClipBoard example. Use lstrcpyn where nchars=6 (1 + length of "hello").