C API programming for Lotus Notes/Domino

Learn the ins and outs of programming with the Lotus Notes/Domino C API. This article explains several important features found in the C API toolkit and offers working examples you can adapt to your own needs.

Share:

Nagendra Nyamgondalu, Advisory Software Engineer, IBM, Software Group

Nagendra Nyamgondalu is an Advisory Software Engineer in the Lotus Notes/Domino group at IBM. The primary purpose of his existence is to fix bugs escalated by customers, mostly related to C&S features in Lotus Notes. Nagen worked as a Lotus Notes consultant for half a decade before joining IBM and enjoys developing applications and tools for his group as a secondary charter. He likes to describe himself as a huge fan of his young daughter, the New England Patriots, and the California weather.



01 February 2005

Also available in Japanese

Those who use the Lotus C API for Lotus Notes/Domino are often reminded of a handy old Swiss Army knife: a snazzy little toolset that has an undocumented number of uses! This article is intended to highlight the capabilities of the Lotus C API for Lotus Notes/Domino and to help developers rediscover its immense potential. A basic understanding of Lotus Notes/Domino and familiarity with programming languages should suffice to follow along. Working knowledge of the C programming language can also help to give you a better understanding of some of the concepts we discuss.

Meet the Lotus C API toolkit

You can download the Lotus C API toolkit for Lotus Notes/Domino by visiting the Toolkits & Drivers page. For the purposes of this article, we will use the toolkit for Lotus Notes/Domino 6.5 on the Windows platform. After you extract the files from the downloaded archive file, you will have the documentation, the header files, the library files, compiled OBJ files, sample programs, and the databases used in the samples.

The documentation contains a user guide and a reference guide both in the form of individual Notes databases. You can obtain a lot of information from the documentation. While the user guide covers what you can achieve with the toolkit and how to go about doing it, the reference guide documents all the functions available for use. Considering the volume of information contained in these databases, it would be very useful to create full-text indexes for them to enable quick and accurate searches.

The header files are typically found in the Include folder. They contain definitions for all the constants, structures, macros, and public functions available as part of the toolkit. Depending on the API calls you use in your programs, you need to include the corresponding header files in your source. The library files and the compiled OBJ files are typically found in the Lib folder under different operating system specific folders. While the LIB files are the DLL import libraries needed to link your API program, the OBJ files are the bootstrap objects needed for programs using the NotesMain entry point or for add-in server tasks. (We will re-visit these a little later in the article.)

There is an exhaustive list of sample programs provided in the samples folders. The notedata folder typically contains all the databases used in the sample programs. Each function or symbolic value described in the reference guide refers to at least one sample program that you can look up to learn how the function you are examining can be used in an actual program.

Having seen what we have to work with, the best way to continue our exploration of the toolkit is to dive into an actual program. In the next sections, we do just that by walking through not one, but two different programs.


Creating a simple program

Let's start with a simple one: a program to print the full path of the Notes data directory. First, let's include the header files from the C library that we need in our program:

#include <stdio.h> #include <string.h>

Next, add the header files from the Lotus C API for Lotus Notes/Domino:

#include <global.h> #include <osfile.h>

Now for the main function: The call to the NotesInitExtended() function initializes the Notes runtime. We need to explicitly call this unless the NotesMain() function is used instead of main():

int main(int argc, char *argv[])
	{
		char       DataDir[256];		
		STATUS   error = NOERROR;       
		WORD	wlength;				

		if (error = NotesInitExtended (argc, argv))
		{
		  printf("\n Unable to initialize Notes.\n");
		  return (1);
		}

Finally, we get the data directory and print it. OSGetDataDirectory() is the key Lotus C API function used in this program. As the very intuitive name implies, it obtains the full path of the data directory. To wrap up, the NotesTerm() function shuts down the Notes runtime. We need to explicitly call this only if we used NotesInitExtended() to start the runtime:

wlength = OSGetDataDirectory(DataDir);

		if (wlength > 0)
			printf("\n The data directory is %s", DataDir);


		NotesTerm();
		return (0); 
    }

Compile and link

Next we compile and link the program we just wrote. For this, we need to make sure that the environment is set up correctly. The most important requirement, of course, is that Lotus Notes and a matching version of the Lotus C API toolkit for Lotus Notes/Domino are installed. You also need the Microsoft Visual C++ development environment and the Microsoft C/C++ compiler and libraries that come with it. Finally, there are three environment variables that we need to setup:

  • The PATH variable value should contain the Notes program directory and the directory that the Microsoft C Compiler is located in.
  • The INCLUDE variable should contain the Lotus C API installation's Include directory and the Microsoft C Include directory.
  • The LIB variable should contain the Lotus C API installation's Lib directory for the Windows 32 platform and the Microsoft C Lib directory.

If you prefer setting up the environment variables on the fly, you can use a batch file. Here is an example batch file:

@echo off

rem *** Comments:
rem *** C:\Lotus\Notes is the program directory for Lotus Notes 6.5;
rem *** C:\Program Files\Microsoft Visual Studio\VC98\ is the 
rem *** directory where Microsoft Visual C++ is installed; and 
rem *** c:\notesapi is the directory where the Lotus C API for 
rem *** Lotus Notes/Domino 6.5 is installed.

set Path=.;C:\Program Files\Microsoft Visual Studio\VC98\bin;
C:\Program Files\Microsoft Visual Studio\VC98\..\Common\MSDEV98\bin;
C:\Program Files\Microsoft Visual Studio\VC98\..\Common\Tools;c:\Lotus\Notes

set LIB=C:\Program Files\Microsoft Visual Studio\VC98\lib;
C:\Program Files\Microsoft Visual Studio\VC98\Platformsdk\Lib;
C:\Program Files\Microsoft Visual Studio\VC98\mfc\lib;C:\notesapi\lib\mswin32

Set INCLUDE=.;C:\Program Files\Microsoft Visual Studio\VC98\PlatformSDK\include;
C:\Program Files\Microsoft Visual Studio\VC98\include;
C:\Program Files\Microsoft Visual Studio\VC98\atl\include;
C:\Program Files\Microsoft Visual Studio\VC98\mfc\include;C:\notesapi\include

The nmake tool

After running the batch file, our environment is ready for compiling and linking our program. The nmake tool, which is part of the Microsoft Visual C++ installation, can come in very handy here. Supplying a MAK file to this tool "makes" the application for you. The MAK file specifies the deliverables, their dependencies, and the command to build the deliverables if they don't exist (or if they are older than the dependencies). Let's call the program we've constructed simple.c. Here is a sample of the MAK file we can use for building simple.exe:

# Comment:
# The MAK file for simple.c - a simple Notes API program 

!include <ntwin32.mak>

# The name of our program.
PROGNAME = simple

# Deliverables and dependencies

$(PROGNAME).EXE: $(PROGNAME).OBJ
$(PROGNAME).OBJ: $(PROGNAME).C

# Compile our program

.C.OBJ:
    $(cc) $(cdebug) $(cflags) $(cpuflags) /DNT $(cvars) $*.c

# Link our program
           
.OBJ.EXE:
    $(link) $(linkdebug) $(conflags) -out:$@ $** $(conlibs) \
    	notes.lib user32.lib

To examine all the options available with the nmake tool, you can use the command nmake -help. The command that we use to build our simple.exe program that prints the data directory is:

nmake /f simple.mak /a

where simple.mak is the MAK file that we created previously, and the /a option indicates that we want to build everything.

NotesMain() function

You can write the same program using the NotesMain() function as the entry point. The only difference is that we don't need to call NotesInitExtended() and NotesTerm() as we did in the previous example. Here is how it looks:

/* Simple program to find the data directory using 
the Lotus C API for Lotus Notes/Domino Using NotesMain() as the entry point*/

/* Include header files that we need from the C library */

#include <stdio.h>
#include <string.h>


/* Include header files that we need from the Lotus C API 
for Lotus Notes/Domino */

#include <global.h>
#include <osfile.h>

/* Functions defined in this file */

void APIErrHandler (STATUS);

/* The NotesMain() function */

STATUS LNPUBLIC NotesMain(int argc, char far *argv[])
{

	/* Local variables */
	char DataDir[256]; /* The data directory for Lotus Notes*/
	STATUS error = NOERROR; /* Return type for most Lotus C API functions 
	for Lotus Notes/Domino - defined in global.h */
	WORD	wlength; /* Unsigned integer - defined in global.h */

	/* Get the full path of the data directory which is returned by the 
	OSGetDataDirectory() function in the text buffer whose address is 
	passed in as the argument. The function return value is the length 
	of the buffer returned. */

	wlength = OSGetDataDirectory(DataDir);

	/* Print the data directory path. */
	if (wlength > 0)
		printf("\n The data directory is %s", DataDir);

	return (NOERROR); 
}

If you think this changes the MAK file, you are right. Remember the bootstrap objects we talked about earlier? This is where we need them. So, the MAK file for this program looks like this:

# Comment:
# The MAK file for simplever2.c - a simple Notes API program that uses NotesMain()

!include <ntwin32.mak>

# The name of our program
PROGNAME = simplever2

# Deliverables and dependencies

$(PROGNAME).EXE: $(PROGNAME).OBJ
$(PROGNAME).OBJ: $(PROGNAME).C

# Compile our program

.C.OBJ:
    $(cc) $(cdebug) $(cflags) $(cpuflags) /DNT $(cvars) $*.c

# Link our program (notice the bootstrap object)
           
.OBJ.EXE:
    $(link) $(linkdebug) $(conflags) -out:$@ $** notes0.obj $(conlibs) \
        notes.lib user32.lib

A more complicated example

Let's move on to a slightly bigger program, one that uses more of the Lotus C API than our simple example. This program looks up a name in the local address book and returns the office phone and address if available. Does that sound like fun? As always, we start with the header files from the C library that we need in our program, followed by the header files from the Lotus C API:

#include <stdio.h>
#include <string.h>

#include <global.h>
#include <nsfdb.h>
#include <nif.h>
#include <osmem.h>
#include <miscerr.h>
#include <osmisc.h>

In this program, we add another function for error handling. Declare its prototype here and start with the main() function. In the main() function, of course, we start by declaring all the local variables that we need. We find out the purpose of each one of them as we build our program. Initializing the Notes runtime comes next:

void APIErrHandler (STATUS);

int main(int argc, char *argv[])
{
	
   char			*dbname = "names.nsf";	   
   char			*viewname = "($users)";				
   char			firstname[256] = "";
   char			lastname[256] = "";
   char			key[256];
   DBHANDLE			dbhandle;           			   
   NOTEHANDLE		notehandle;
   NOTEID			viewid;          			   
   HCOLLECTION		collhandle;      			   
   COLLECTIONPOSITION	collpos;                
   HANDLE			bufferhandle;                  
   NOTEID			*nid;                       
   DWORD			count;                      
   DWORD			matches;                    
   DWORD			whichnote = 0;              
   STATUS			error = NOERROR;             
   WORD			flg;                       
   BOOL			found;                  
   char			*itemname = "";
   char			itemvalue[256];
   WORD			itemlen;


   if (error = NotesInitExtended (argc, argv))
   {
     printf("\n Unable to initialize Notes.\n");
     return (1);
   }

We want the command line for the program to be lookup <firstname> <lastname>, so we make sure that we have the desired number of command line arguments:

   if (argc != 3)
   {       
      printf("The syntax is: lookup <firstname> <lastname>");   
      NotesTerm();
      return (1);
   }  
   else
   {
      strcpy(firstname, argv[1]);
      strcpy(lastname, argv[2]);
      strcpy(key, firstname);      
      strcat(key, " ");
      strcat(key, lastname);
      printf("\nContact information for %s :", key);
      printf("\n--------------------------------------------");
   }

Next, we need to open the address book and get a handle to it. The NSFDbOpen() function does just that:

   if (error = NSFDbOpen (dbname, &dbhandle))
   {
      APIErrHandler (error);  
      NotesTerm();
      return (1);
   }

Reading the design note for ($users)

After we have a handle to the database, we need to get the design note for the view ($users) using the NIFFindView() function. The NIFOpenCollection() function obtains a handle to the collection of documents based on the view note. If we run into an error during either operation, we close the database and exit:

   if (error = NIFFindView (dbhandle, viewname, &viewid))
   {
      NSFDbClose (dbhandle);
      APIErrHandler (error);  
      NotesTerm();
      return (1);
   }

   if (error = NIFOpenCollection(
         dbhandle,      
         dbhandle,      
         viewid,        
         0,             
         NULLHANDLE,    
         &collhandle,   
         NULLHANDLE,    
         NULL,          
         NULLHANDLE,    
         NULLHANDLE))   
   {
      NSFDbClose (dbhandle);
      APIErrHandler (error);  
      NotesTerm();
      return (1);
   }

Finding the name

Now we need to find the name that we want to look up in the collection. NIFFindByName() searches a collection based on the primary sort key, which is the first column of the view. (It must be sorted.) If the name is found, its position is placed in the pointer to the COLLECTIONPOSITION that is passed in. If the function runs into an error, it could mean that the name does not exist or that some other error occurred:

   error = NIFFindByName (
           collhandle,       
           key,          
           FIND_CASE_INSENSITIVE | FIND_FIRST_EQUAL, 
           &collpos,         
           &matches);      

   if (ERR(error) == ERR_NOT_FOUND) 
   {
      printf ("\nNo such name in the address book.\n");
      NIFCloseCollection (collhandle);
      NSFDbClose (dbhandle);
      NotesTerm();
      return (0); 
   }
   
   if (error)
   {
      NIFCloseCollection (collhandle);
      NSFDbClose (dbhandle);
      APIErrHandler (error);  
      NotesTerm();
      return (1);
   }

Using the COLLECTIONPOSITION obtained, we can fetch the NoteID of the document that we are interested in by calling the NIFReadEntries() function. The argument READ_MASK_NOTEID passed into this function specifies the information about the document we want. The function places the requested information in the bufferhandle, whose pointer we are passing in. We will ignore some arguments (such as count and flg), which are not required in this context. However, if the information expected to be returned in the buffer is non-trivial in size, we will need to enclose the call to NIFReadEntries() in a loop and test the value of the flg argument. The SIGNAL_MORE_TO_DO bit is set in the flg argument if there was more data than could fit into the buffer. If the buffer is NULL after the function returns, we need to exit:

   if (error = NIFReadEntries(
             collhandle,         
             &collpos,           
             (WORD) (NAVIGATE_CURRENT),                
             0L, 
             NAVIGATE_NEXT,       
             matches - whichnote, 
             READ_MASK_NOTEID,    
             &bufferhandle,      
             NULL,               
             NULL,               
             &count,        
             &flg))       
   {
         NIFCloseCollection (collhandle);
         NSFDbClose (dbhandle);
         APIErrHandler (error);  
         NotesTerm();
         return (1);
   }

   if (bufferhandle == NULLHANDLE)
   {
         NIFCloseCollection (collhandle);
         NSFDbClose (dbhandle);
         printf ("\nEmpty buffer returned by NIFReadEntries.\n");
         NotesTerm();
         return (0); 
   }

Now that we have the NoteID of the note that we want, we can open it. First, we need to lock the buffer in memory and obtain its address, using the OSLockObject() function. We cast this as a NOTEID:

   nid = (NOTEID *) OSLockObject (bufferhandle);
   
   if (error = NSFNoteOpenExt(
             dbhandle,         
             nid[0],           
             0,        
             &notehandle))       
   {
         OSUnlockObject (bufferhandle);
               OSMemFree (bufferhandle);
         NIFCloseCollection (collhandle);
         NSFDbClose (dbhandle);
         APIErrHandler (error);  
         NotesTerm();
         return (1);
   }

Almost done! All we need to do now is to check whether or not the address and phone number exist and print them if they do. NSFItemIsPresent() is used to check that the items exist, and NSFItemGetText() obtains the value of the items:

   found = FALSE;	
   itemname = "MailAddress";
   found = NSFItemIsPresent (notehandle, itemname,
                                  (WORD)strlen (itemname));
   if (found)
   {
      itemlen = NSFItemGetText ( 
                            notehandle, 
                            itemname,
                            itemvalue,
                            sizeof (itemvalue));
      printf("\nMail Address: %s", itemvalue);
  }
  else
  {
      printf("\nNo Mail Address found");
  }


  found = FALSE;	
  itemname = "OfficePhoneNumber";
  found = NSFItemIsPresent (notehandle, itemname,
                                  (WORD)strlen (itemname));
  if (found)
  {
      itemlen = NSFItemGetText ( 
                            notehandle, 
                            itemname,
                            itemvalue,
                            sizeof (itemvalue));
      printf("\nOffice Phone Number: %s", itemvalue);
  }
  else
  {
      printf("\nNo Office Phone Number found");
  }

Wrap up by unlocking the buffer and freeing its memory, closing the note, collection, and database:

  OSUnlockObject (bufferhandle);

  OSMemFree (bufferhandle);

  if (error = NSFNoteClose (notehandle))
  {
      NIFCloseCollection(collhandle);
      NSFDbClose (dbhandle);
      APIErrHandler (error);  
      NotesTerm();
      return (1);
  }
    
  if (error = NIFCloseCollection(collhandle))
  {
      NSFDbClose (dbhandle);
      APIErrHandler (error);  
      NotesTerm();
      return (1);
  }

  if (error = NSFDbClose (dbhandle))
  {     
      APIErrHandler (error);  
      NotesTerm();
      return (1);
  }

  NotesTerm();
  return (0); 
}

The final piece to the puzzle is the APIErrHandler() function. This obtains the string associated with the error that is passed in and prints it.

void APIErrHandler (STATUS error)

{
    STATUS  errorid = ERR(error);
    char    errorstring[200];
    WORD    len;

    len = OSLoadString (NULLHANDLE,
                             errorid,
                             errorstring,
                             sizeof(errorstring));

    
    printf ("Encountered this error : %s", errorstring);
}

You may have noticed a pattern in the names of Lotus C API functions. All the functions whose names start with NSF have to do with databases, notes, or items. Those beginning with NIF typically deal with views and collections. Functions that deal with operating system level information (such as locking objects in memory) have names beginning with OS. This naming convention is very useful when looking for functions to match what you want to achieve.

You can download the full program described in this section from the Sandbox.


Useful Lotus C API options

The Lotus C API can be useful in several contexts both on the client side and on the server side. In this section, we highlight some of the options available.

Design elements

Using the Lotus C API, it is possible to create and manipulate design elements such as agents, forms, views, and navigators. The NOTE_CLASS_xxx value is important when creating design elements because it identifies the type of note we are creating. NOTE_CLASS_DOCUMENT is used for documents, NOTE_CLASS_FORM for forms, NOTE_CLASS_VIEW for views, NOTE_CLASS_FILTER for agents and macros, and so on. For example, you can use the following function calls to create a view design note:

WORD ClassView = NOTE_CLASS_VIEW; NSFNoteCreate(hDB, &hNote); NSFNoteSetInfo(hNote, _NOTE_CLASS, &ClassView);

You can even compile and evaluate formulas using the NSFFormulaCompile() and the NSFComputeEvaluate() functions.

Server add-in tasks

You can create a server task that runs on the server like other tasks. Although you can have an add-in task perform an operation and quit, typically add-in tasks are used for operations that need to be performed periodically. The Lotus C API provides specific functions for building an add-in task. The main entry point for the program is the AddInMain() function. The function AddInIdle() is useful to control the main loop in the program.

AddInDayHasElapsed(), AddInMinutesHaveElapsed(), and AddInSecondsHaveElapsed() help decide whether or not it is time to perform our periodic operation. You can start and stop the add-in task manually from the server console using the load <taskname> and tell <taskname> quit commands. You can also set it up to start automatically with server startup and shutdown when the server is shutdown by including the program name in the value of the ServerTasks variable on the server's Notes.ini file.

Custom Actions menu programs

You can add your own actions to the menu option Actions in the Notes client. To do this, the entry point must be a function in this format:

NAMRESULT LNCALLBACK FunctionName (WORD wMsg, LONG lParam)

The function can have any name and must be declared in the EXPORTS function of the module definition (DEF) file with an ordinal value of 1. The first argument indicates the operation being performed, and the second argument is operation-specific information. The operation can have the values:

NAMM_INIT
NAMM_INITMENU
NAMM_COMMAND
NAMM_TERM

NAMM_INIT indicates that an Action menu item can now be added. NAMM_INITMENU allows your program to modify your menu item, such as enable or disable. NAMM_COMMAND indicates that your Action menu item has been selected, and you can now perform the associated operation. NAMM_TERM provides an opportunity to perform wrap-up operations such as freeing memory.

Calendar and scheduling

The Lotus C API provides the capability of performing calendar and scheduling operations, such as creating calendar entries or looking up busytime. Creating calendar entries such as meetings, appointments, reminders, and all-day events involves creating a note in the desired mail database using the NSFNoteCreate() function and adding the required calendar items to the note.

You can use the SchRetrieve() function to retrieve the schedule of a user for a particular time. After the schedule is retrieved, use the SchContainer_GetFirstSchedule() function to get the first schedule object. Then use the Schedule_ExtractFreeTimeRange() function to retrieve a freetime range from the schedule object or use the Schedule_ExtractBusyTimeRange() function to get a busytime range from the schedule object.


Extending capabilities: the Extension Manager

There is one service provided by the Lotus C API that deserves special mention: the Extension Manager. It allows you to run custom processes before or after certain internal Notes or Domino operations are performed by registering callback routines. The entry point for your program should be a function in this format:

STATUS LNPUBLIC FunctionName(void)

The function can have any name and must be declared in the EXPORTS function of the module definition file with an ordinal value of 1. The callback function must be in this format:

STATUS LNPUBLIC FunctionName(EMRECORD FAR * pExRecord);

Before registering the callback routine, it is useful to obtain a recursion ID using the EMCreateRecursionID() function. This is recommended because it prevents the same extension from being called again if it has already been called. The EMRegister() function is used to register the callback routine. It is best explained with an example:

EMRegister (
             EM_NSFDBCLOSE,
             EM_REG_BEFORE,
             (EMHANDLER)gHandlerProc,
             gRecursionID,
             &hHandler);

The first argument, EM_NSFDBCLOSE, identifies the operation we want to register the callback for. The second argument, EM_REG_BEFORE, indicates that we are interested in running our program just before the operation is called. So, our program is called as soon as the NSFDBClose() operation, which closes a database, is called. There are a number of operations that you can register callback routines for and the value to indicate each operation begins with EM_ (the complete list is available in the API reference guide). The third argument is our custom function that we want to call. The fourth argument is the recursion ID (if there is one), and the last argument is a handle returned by the function that we will need when it is time to de-register.

When wrapping up the program, the EMDeregister() function is used to de-register the callback routine. To specify the extension that you built to Lotus Notes/Domino, you need to use the EXTMGR_ADDINS variable in the server's or client's Notes.ini file (depending on where the extension has to run).

The Calendar profile trap

One of our code samples demonstrates the use of the Extension Manager by using a routine to trap updates to a specific mail file's calendar profile. This program registers a callback routine for NSFNoteUpdateExtended(). In the callback routine, we check to see whether or not the note being updated is the mail file we are interested in and whether or not the note is a calendar profile. If it is, we log the date and time to a log file. After the program is compiled and linked, you need to list it in the server's Notes.ini file in this format:

EXTMGR_ADDINS=<dllname>

You can download the complete code for this program (including the MAK file and the module definition file) from the Sandbox.


Conclusion

The ground we covered in this article is only a sample of what the Lotus C API for Lotus Notes/Domino has to offer. By showcasing some of its capabilities, we hope that this article serves as a first step for Notes/Domino users and developers to explore and fully exploit the potential of the toolkit.

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into IBM collaboration and social software on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Lotus
ArticleID=33520
ArticleTitle=C API programming for Lotus Notes/Domino
publish-date=02012005