Calling DLL routines from LotusScript. Part I: Windows API
Last Updated: February 8, 1999
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 dont 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, youll 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 arent so lucky. In trying to solve this problem I
could not help but think about using the Windows Common Dialogs. Here
Ill 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.
