Getting started - OSLoadProgram sample
We will take a simple example of the use of Notes C API. A common task nearly everyone meets sooner or later is to start another program from Notes, load a library, start a Domino task from agent, etc. A good way to do that is shell function.
hFile = shell ("myprog.exe arg1 arg2", window_style)
However it will not load a dll, it does not allow to define working directory, the behaviour of launching program by file association has changed between releases, we may want to control if Notes wait until the program is closed -- in short we may need another alternative.
One may look for OS API function to do the task – but then Domino is multiplatform system and it always is better to use Notes API that may be used on all platform Domino runs.
A good starting point when trying to solve this kind of problem is Notes.net with FAQs, Knowledge base, Notes Gold Forum or BP Technical Forum for Lotus Business partners (or these days IBM PWSW or PWD) However if you know what you are looking for it could be as well Notes API reference database. It is included in Notes API Toolkit that everyone can download free of charge from Lotus website.
The one Notes API function that foots the bill is OSLoadProgram.
This is how the OSLoadProgram entry starts in Notes API reference.
The mythical #include describes the C header file the function is defined in. All external functions, constants and structures they use must be declared before we attempt to use them, be it the C code or LotusScript program. C programmers must include appropriate header files provided with C toolkit. LotusScript programmers must take those header files, transcribe the necessary declarations, constants and structures in LotusScript friendly manner and include them in declarations section of the script module.
#include <osmisc.h> means that the definition of the OSLoadProgram is located in ../include/osmisc.h file. In fact it is the same declaration as in API reference, so for starters we may skip the header file. We will come back to it later.
In order to use the function we must at first convert C declaration to LotusScript declaration. We must find out if it is a subroutine or function, what does it return and what are the parameters. We must also add a library name that will depend on what platform are we going to use it in.
The C notation starts with a returned value or function type. In this case it is STATUS NPUBLIC.
The Notes API functions in majority come in two flavours VOID and STATUS LNPUBLIC. The formers are subroutines – they do not return a value, the latter are proper functions. We may take this as a universal truth or ask what the hell VOID and STATUS and LNPUBLIC mean and how do we know what the function returns.
In fact C language allows extensive definition, i.e. definition of shorthand notation for pieces of code or creation of macros. VOID, STATUS and LNPUBLIC are not a part of C language, hence they are defined and can mean practically anything. Probably we may skip the hypothesis that they define some especially malicious virus or a bitmap of zodiac signs. We can use either header files or Notes C API reference to look up the meaning.
We leave the header files for the time being as Notes C API reference provides a reasonable explanation:
· VOID is void (of C – C is case sensitive, so we must be careful) and is used as a default type for some code like function that does not return a value (LotusScript sub);
With all this knowledge we have half of the LotusScript declaration ready:
Declare Function OSLoadProgram Lib "…" (…) As Integer
We are missing “only” the library name and parameters. With library we deal rather straightforward. It is the Notes shared library or Notes core library. Nearly all Os-es have different library names, so we will need to define as many declarations as there are OS-es we want to use our function in and then call the correct one depending on OS. Especially as we will see in subsequent chapters that the Library name unfortunately is not the only difference to be taken into account when calling Notes API functions in different OS-es.
For historical compatibility reasons the calls may also be made to other libraries (you may have seen it used in some inherited/borrowed code). However today it could be a good idea to stick to unique library names – one for each OS naturally.
To get the appropriate names we check the API Users Guide, chapter 4 and find a table with library names – most probably nnotes -- for Windows (32-bit) or NotesLib for Macs.
Expecting that we want to use our call on both surviving Notes clients – Windows or Macintosh we need to declare two definitions:
Declare Function W32_OSLoadProgram Lib "nnotes" Alias "OSLoadProgram" (…) As Integer
Declare Function MAC_OSLoadProgram Lib "NotesLib" Alias "OSLoadProgram" (…) As Integer
We need to define two distinct function names – so we use different names with platform prefixes and the real name (one defined in library itself) gets moved to Alias clause. Alias is useful for a number of different purposes – in case we want to declare the same function for different OS-es like in this case, or use several calls with different parameters – sometimes we are forced to do this (one more fun for LotusScript developers), the OS is case sensitive (LotusScript converts all function names to uppercase and if Alias is not provided uses them to call the library), the function “real name” (C name) is longer than 13 characters that is the limit for LotusScript function names.
Now the parameters:
The C definition char * parmName means that the parameter passed is a pointer to the string (character array in C). According to the Designer help, in order to pass a pointer to the string it must be declared: ByVal somestring as String. It does not make much sense, but it seems that the regular definition by reference is used for internal referencing scheme for strings in Visual Basic and LotusScript). Amen. For the 3 strings the format is clear (or nearly clear – if we want to use most of national – non-ascii characters, we need to tweak a declaration a little bit, but for the most purposes it is good enough).
The last parameter WORD flags means that variable flags of WORD type (or Integer as we discussed before) is being passed by value or “ByVal Flags As Integer”. The declarations are ready:
Declare Function W32_OSLoadProgram Lib "nnotes" Alias "OSLoadProgram" (Byval filename As String, Byval WorkingDir As String, Byval Arguments As String, Byval Flags As Integer) As Integer
We declared the parameters the same way for Windows and Macintosh. It is not always the case and we need to be careful. Usually and also in this case it is OK.
We put the declarations in declaration section of the code and we are ready to use the function.
A quick test (assuming we are in Windows) :
W32_OSLoadProgram "notepad.exe", "", "newfile.txt",0
does not work. What is wrong? And how can we find it out? What can be the reasons?
The notepad.exe is entered without full path. We try to load it the same way from command line – it works as expected – it is in the path. Next suggestion.
We entered 0 for flags. It is normally a default value. Nearly always we are safe providing 0 for flags or other values we do not bother to find out the meaning and use. We must be careful NOT to provide 0 for handles and pointers – except when it is explicitly stated to provide NULL pointer. 0 is a valid value for the Flags. Next suggestion.
It may be useful to note that the value returned by the functions nearly always is a status or error code (0 means -- success), we may use it to look-up the error message. How do we implement it – we will look into this a bit later in chapter Error handling. Here may note that we are getting error 330 or “Unable to invoke program” – not much of information.
Now we have got to the point – when nothing helps read the manual. And it says – “WorkingDir - If "WorkingDir" is not NULL, the specified directory is made to be the current directory before program execution, else it is the directory that the program lives in.”
The key words are “is not NULL”. If we do not provide working directory, we must provide NULL pointer or Long 0 that is not the same as empty string. That leaves us with a choice to redefine a function so that we can pass 0 or provide some working directory.
Being lazy we provide some directory, say “c:\temp”. Also it usually is a good idea to supply a directory name where the user has writing privileges, or we may need a specific working directory for other purposes – location of files, etc. The alternative to pass NULL or 0 will be discussed later – we will need another declaration with a different parameter for WorkingDir.
To make a long story short – the second problem (and there is always a second problem;-) is the filepath. Domino does not make much out of a system path, so either notepad.exe is in Domino executable directory or we need to provide a full path.
So, the final version:
Call W32_OSLoadProgram ("c:\windows\notepad.exe", "c:\temp", "newfile.txt",0)
Now it works. Notepad starts and asks us if we intend to create a new file. Congratulations -- the first sample is put on the right foot.
At this point – if we have not done it earlier we may have look at possible Flags values. The Notes API reference says:
Symbolic Values :
Again – a reference to osmisc.h and some meaningful explanation. The C guy may have finished here, but the LotusScript programmer must make one step further and finally have a look in the header file. You may have tried to use some of these values. Alas, they should be defined explicitly, so the only way to declare them is to get in the osmisc.h file in include directory of Notes API Toolkit and look up the values:
#define OS_LOADPROG_ICONIC 0x0001 /* Don't show application window */
LotsuScript-wise it will be:
Const OS_LOADPROG_ICONIC = 1
0x0010 notation is hexadecimal notation, so 0x0010 we can enter as 16 or &h10.
Put these constant declarations in declaration section or anywhere in scope if you wish. Now we can use the flags to define the mode we want the notes to load the program. Say using OS_LOADPROG_ASSOCIATE we can start the program by association (assuming we created newfile.txt during our first attempt):
Call W32_OSLoadProgram ("newfile.txt", "c:\temp", "", OS_LOADPROG_ASSOCIATE)
A couple of additional remarks. It could be a good idea to capture returned error code, instead of ignoring the returned value, like we have been doing by using the simple call.
Other consideration – if we really want to use our code in several OS-es – the best way is to put them in a wrapper function:
Function OSLoadProgram (filename As String, WorkingDir As String, Arguments As String, Flags As Integer) As Integer
We do not need to declare ByVal in wrapper function – it must be done only in external function declaration.
There is no overhead involved in declaring the session each time we use it as there is just one session instance you get a pointer to. In some cases you may need to distinguish not only OS, but also the platform, like Solaris Sparc vs. Solaris x86 or Intel Alpha (any takers?) and Intel Pentium. In this case you will need to evaluate @Platform([Specific]) function. We will look at this case in Chapter on Multiplatform aspects.
You may want to process the returned error code or add a code to be executed if called on other platform, say to produce a warning message.
Otherwise the first sample is ready and shipping. Happy program loading!
In this chapter we converted declarations necessary to use Notes API OSLoadProgram function from LotusScript. The approach used may be applied for more complex cases we will discuss in other samples. The next chapter examines the particularities characteristic to calling Notes API from LotusScript in more detail.
You may also start right with the Samples chapter and return to the overview for complementary information.