Calling DLL routines from LotusScript. Part I: Windows API

Domino/Notes Version: 4.x and higher
Platform: Windows 95, 98, NT 4

Like me, you probably find LotusScript just cannot do everything you want at times. After seeing the article titled "Changing Drivers on the Road" in the February 1998 Lotus Notes & Domino Advisor a whole new world became obvious to me. The article described how to call the Windows API functions from within LotusScript. I have found countless uses for this including:

· Modifying the default printer (as described in the above mentioned article)
· Getting a list of Windows printers to present to the user before modifying the default printer
· Using the Windows Common Dialogs
· Accessing the Windows clipboard
· Getting Windows configuration settings (temp directory, computer name, etc.)
· and quite a few others...

A natural and quite possibly even more interesting extension to this is using the same method to call the Notes API. Again, I must given credit where credit is due. An article in The View sparked this idea. It appeared in the July/August 1998 issue and was called "Manipulating Rich Text Fields with LotusScript and the HiTest C API - Without C Programming". When I have done this I have chosen to use the C API over the HiTest C API and I will tell you why later. Part I of this document will describe writing declares and using the Windows API DLLs. Part II will cover using the Lotus Notes C API.

Part I: The Windows API
My intent here is not to cover programming the Windows API as there are many good books on the subject but to cover integrating it into your Lotus Notes/Domino applications. I have included several good references on more information that I found useful as I was getting started doing this and I also have a couple of examples to show you what can be done through the API.

Declaring the DLL Functions
Often, the trickiest part of using the WIndows API is writing the counterpart of the API routine in Notes; the declare statement for the function in the DLL. It takes some practice to get things right. Without the right declare statement get prepared for lots of memory exceptions and crashes within the Notes client. VB programmers do this all of the time so there are a few good references that you will find handy:

First and foremost if you have Visual Basic, it contains a Text API Viewer which allows you to browse the Windows API functions and their associated declares. You can copy them to the clipboard and paste then into your (Declarations). The declares are also stored in a plain text file, WIN32API.TXT, that you can open with any text editor. Personally, I find this more effective for finding what I want.

If you don’t have VB then there is another alternative. First, find the Windows DLL (e.g. kernel32.dll) you would like to investigate functions for in the Windows System directory using Windows Explorer. Right click it and select Quick View. This will bring up, among other things, an Export Table that will list the names of all of the functions in the DLL. Knowing what function you want to call, you just need the syntax for each function. The Microsoft Developers Network comes in handy here. On it, you’ll want to go to the MSDN Library Online but first you will have to register and tell MS about yourself. I think it is well worth it. Once on the site, a search on WIN32API.TXT turns up several good articles some of which are listed below. (The URLs are included here but I imagine they might change and that is why I told you how I found the documents.)

Declaring a DLL procedure
This article introduces you Declaring DLL routines for Word Basic but it applies equally to VB and LotusScript. Most importantly, it also covers how to convert C Language declarations to Word Basic/LotusScript equivalents.

Calling Routine in DLLs
An introduction to calling DLL procedures from VB. Listed here are important Windows DLL files and what types of routines they contain.

I have provided a couple of examples here to discuss the main points in writing these solutions. The examples cover using the Windows Common Dialogs and using the Windows clipboard.

Example 1 - Windows Common Dialogs
The Lotus macro language has @Prompt([LocalBrowse]) but LotusScript programmers aren’t so lucky. In trying to solve this problem I could not help but think about using the Windows Common Dialogs. Here I’ll show you how to use the Open File dialog and this method can be extended to other common dialogs as well.

To demonstrate this I wrote an Agent that had the settings "Manually from the Actions menu" and "Run Once (@Commands may be used)"


(Declarations)

Type OPENFILENAME
lStructSize As Long
hwndOwner As Long
hInstance As Long
lpstrFilter As String
lpstrCustomFilter As String
nMaxCustFilter As Long
nFilterIndex As Long
lpstrFile As String
nMaxFile As Long
lpstrFileTitle As String
nMaxFileTitle As Long
lpstrInitialDir As String
lpstrTitle As String
flags As Long
nFileOffset As Integer
nFileExtension As Integer
lpstrDefExt As String
lCustData As Long
lpfnHook As Long
lpTemplateName As String
End Type

Declare Function GetOpenFileName Lib "comdlg32.dll" Alias "GetOpenFileNameA" (pOpenfilename As OPENFILENAME) As Long


Sub Initialize
Dim OpenFile As OPENFILENAME
Dim Returnl As Long
Dim Filterstr As String

OpenFile.lStructSize = Len(OpenFile)
Filterstr = "Word Documents (*.doc)" & Chr(0) & "*.doc" & Chr(0) & "All Files (*.*)" & Chr(0) & "*.*" & Chr(0)
OpenFile.lpstrFilter = Filterstr
OpenFile.nFilterIndex = 1
OpenFile.lpstrFile = String(257, 0)
OpenFile.nMaxFile = Len(OpenFile.lpstrFile) - 1
OpenFile.lpstrFileTitle = OpenFile.lpstrFile
OpenFile.nMaxFileTitle = OpenFile.nMaxFile
OpenFile.lpstrInitialDir = "C:\My Documents"
OpenFile.lpstrTitle = "Open File"
OpenFile.flags = 0
Returnl = GetOpenFileName(OpenFile)

If Returnl = 0 Then
Msgbox "The user pressed the Cancel Button"
Else
Msgbox "The user chose " & Trim(OpenFile.lpstrFile)
End If
End Sub


This example demonstrates several important things about declaring and calling DLL routines.

Often structures are used to pass data to and from these routines. Not only do you need to declare the routine but you must also define the structures to LotusScript. I have done that here with the OPENFILENAME structure in the (Declarations) section of my Agent. I just copied the declarations straight from the WIN32API.TXT file. In my call to the routine I have chosen to default the folder to "My Documents" and to display only Word (.doc) documents by default and also giving the user the option to change the Files of Type selection to "All Files (*.*)". For more information on the syntax of the options for GetOpenFileName see the developer's network and search of GetOpenFileName. My program simply calls the dialog box and returns, via a message box, the path and filename that the user has selected. This information can then be used for a file operation such as a NotesEmbeddedObject.ExtractFile

Example 2 - Accessing the Windows clipboard
I originally wrote this when I was using OLE Automation from LotusScript to work in another application (MS Word) and needed to bring back text from the other application into a Notes backend document. This routine accomplishes that although it just brings back plain text. I am sure there are lots of other uses for and extensions of it.


(Declarations)

'Clipboard Manager Functions
Declare Function GetClipboardData Lib "User32" (Byval wFormat As Long) As Long 'Note: GetClipboardDataA as aliased in Win32api.txt did not work!
Declare Function OpenClipboard Lib "User32" Alias "OpenClipboard" (Byval hwnd As Long) As Long
Declare Function CloseClipboard Lib "User32" Alias "CloseClipboard" () As Long

'Predefined Clipboard Formats
Public Const CF_TEXT = 1

'Memory management functions
Declare Function GlobalLock Lib "kernel32" Alias "GlobalLock" (Byval hMem As Long) As Long
Declare Function GlobalUnlock Lib "kernel32" Alias "GlobalUnlock" (Byval hMem As Long) As Long

'String Copy function for each of its various parameter configurations lstrcpy(to_string, from_string)
Declare Function lstrcpyLP2Str Lib "kernel32" Alias "lstrcpyA" (Byval lpString1 As String, Byval lpString2 As Long) As Long
Declare Function lstrcpyStr2Str Lib "kernel32" Alias "lstrcpyA" (Byval lpString1 As String, Byval lpString2 As String) As Long
Declare Function lstrcpyStr2LP Lib "kernel32" Alias "lstrcpyA" (Byval lpString1 As Long, Byval lpString2 As String) As Long

'String Length function for each of its various parameter configurations
Declare Function lstrlenLP Lib "kernel32" Alias "lstrlenA" (Byval lpString As Long) As Long
Declare Function lstrlenStr Lib "kernel32" Alias "lstrlenA" (Byval lpString As String) As Long

'Keyboard input functions
Declare Function GetFocus Lib "User32" Alias "GetFocus" () As Long


Function GetClipboardContents() As String

Dim ClipboardHandlel As Long
Dim LpStrl As Long
Dim HWndl As Long
Dim Resultl As Long
Dim Clipboardstr As String

'Get a handle for the current window that has keyboard focus
HWndl = GetFocus()

'Open the clipboard, specify this window as the owner so no one else modifies it while we use it
If (OpenClipboard(HWndl) <> 0) Then

'Get the data off of the clipboard in text format
ClipboardHandlel = GetClipboardData(CF_TEXT)

'Get the text out of Windows memory into a string we can work with here
If (ClipboardHandlel <> 0) Then
'Lock the memory we will work on and get a handle to it
LpStrl = GlobalLock(ClipboardHandlel)
'Pre-allocate some space for our String
Clipboardstr = Space$(lstrlenLP(LpStrl))
'Copy it in
Resultl = lstrcpyLP2Str(Clipboardstr, LpStrl)
'Release the lock
GlobalUnlock(ClipboardHandlel)
Else
Clipboardstr = "NULL"
End If
'Close the clipboard so other applications may use it
Resultl = CloseClipboard
Else
Print "Could not open Clipboard"
End If

GetClipboardContents = Clipboardstr
End Function


There are some important points about this code worth mentioning. There are sometimes more that one way to alias a DLL routine depending on the types of parameters that will be passed to it. In Example 2 I have done this for the lstrlenA routine which computes the length of a string.

Often the result of a call to a DLL routine will be a pointer to a block of memory that contains a result; in our case a string. This is a little different from a block of memory that has been allocated via LotusScript for a string variable. When using lstrlenA providing it pointers to each of these blocks of memory is done differently. In the first case, the pointer is passed as a Long. Is the second case the pointer is passed for the String. Each of these two difference interactions with the single routine require a different declaration in LotusScript as I have done for lstrlenA. I have named them so it is easy to tell them apart when using them. For the version that is used when the pointer is a long pointer obtained from another routine I have appended "LP" to its name and declared the parameter as ByVal Long. For the version that is used when a LotusScript string variable is used I have appended "Str" to its name and declared the parameter as ByVal String..

Conclusion
I think it goes without saying that careful planning must be done when using these types of solutions since you are tying your application to not only the Lotus Notes platform but the Operating System version. These types of calls would not result in cross platform solutions that would work on other Operating Systems. But, if you work primarily in the Windows enviroment as many people do the power here is very appealing. And, although Windows versions are usually backward compatible it is not always the case that things behave exactly the same from one version to another.

Hopefully this document arms you with enough information and inspiration to incorporate this type of functionality into your solutions.