My.ADVISOR.com Sign-In
ID
Password

Member Center / Sign-Up
   
SUBSCRIPTION STATUS
If you are a subscriber to this publication, sign-in to access locked articles. To subscribe or renew go to www.AdvisorStore.com.
Go to Article
Advanced Search 

NOTES DEVELOPMENT

Take Advantage of the C++ API

Create a useful API program from scratch that can go where Formula language and LotusScript can't.

By Brad Balassaitis and W. Colin Judge

You're working on a solution to a challenging problem and find yourself up against the wall. You just can't implement a practical workaround -- except for that one option which, seemingly, calls for overcoming an even bigger obstacle -- using the Notes C++ API.

In the July 1999 issue of Lotus Notes & Domino Advisor, Henry Newberry and Rocky Oliver introduced readers to the C API. Subsequent articles showed how to create some great utilities for everyday use. As Henry and Rocky stated, the Notes API isn't for the faint of heart. But by stretching yourself a little bit, you can add more functionality to your applications and get into places that Formula language and LotusScript just can't reach. The C++ API not only offers more robust functionality, it does so with improved performance, because API programs run outside the overhead of the Notes client.

This article explains how to set up your compiler and run a simple API program. It then demonstrates a practical use of the C++ API by building an executable program and a DLL that you may find useful around your Notes shop.

This article assumes you have written at least very basic C/C++ programs and are familiar with its syntax. Even if you aren't familiar with C/C++, we hope the techniques and the functionality highlighted in this article encourage you to learn a little C++ just for the sake of extending your Notes applications and maybe even writing the next cool third party Notes tool.

The C++ API

While the C API acts directly on the Notes program files, the C++ API has an additional degree of separation because the C++ API is really a wrapper for the C API. The C++ API makes calls to a C API DLL that, in turn, acts on the Notes program files. The advantage to this separation is that the C++ API is easier to use because, similar to LotusScript, the C++ API is an object-oriented interface to Notes. Looking at the architecture of the C++ API (figure 1), it doesn't look much different from a LotusScript poster. This makes the C++ API more intuitive to use for the experienced LotusScripter.



Figure 1: The architecture of the C++ API -- This looks much like the R5 back end classes.

There are a couple noticeable differences in the architecture. One is the object array classes, which really bring home the object-oriented abstraction of the C++ API. The other is the LN data types. While LotusScript has a string, integer, and other data types transparently built in, with the Notes API you must use types like LNString, LNText and LNNumber. If you try passing a vanilla C++ type int into an API method that's expecting an LNNumber, you'll get an error.

Creating that first API program can be intimidating. That's why Lotus has done it for you. Code libraries, sample programs, and documentation have been put into the C++ API Toolkit, which you can find at http://www.lotus.com/techzone. There are two downloads for each release -- one has the API libraries and sample programs; the other is an invaluable Notes database with a user's guide for using the API and a reference guide for the API classes.

If this is your first C++ API program, see the sidebar, "Setting Up and Running a C++ API Program," below for step-by-step instructions on compiling and running an example program. Otherwise, read on to see how we overcame a challenging problem with the C++ API.

The scenario

The Computer Services group at Acme Inc. has a database application, aptly named Computer Services, that lets the company track where each of Acme's 10,000 employees is located and what kind of computer and peripherals each employee has. The Computer Services application gets updated every night with information on personnel location moves and new hires. An agent, which does the update, goes into the Acme Directory database and searches for changed documents (figure 2). The agent then matches up the changed documents with corresponding documents in the Computer Services database and updates them accordingly. To use a reliable key, the Universal Note ID (UNID) of the Acme Directory person document is stored in its sister document in the Computer Services database. Pretty straightforward, so far (listing 1).



Figure 2: The two databases in our scenario -- The Computer Services database is updated nightly with changes in the Acme Directory.

The problem

The problem is when people at Acme Inc. are terminated. Company policy dictates that when an employees leaves, his person documents in the Acme Directory are immediately deleted; Acme would just like to forget about them as soon as possible, and since there are no rehires at Acme, the 'Allow Soft Deletions' database property hasn't been selected.

Listing 1: Code to update Computer Services database with changes from Acme Directory -- This code is straightforward, but what if someone leaves the company?

Options
Option Public
Option Declare

Declarations
'customize these settings for your use
Const DBSOURCE = "AcmeDirectory.nsf"
Const VIEWTARGET = "PeopleByADUNID"
Const SOURCEUNID = "AcmeDirectoryUNID"

Initialize
Sub Initialize

Dim session As New notessession
Dim dbTrgt As notesdatabase
Dim dbSrc As New Notesdatabase("","")
Dim view As notesview
Dim agent As NotesAgent
Dim item As NotesItem

Dim varLastRun As Variant
Dim collection As notesdocumentcollection
Dim docTrgt As notesdocument
Dim docSrc As notesdocument
Dim intCount As Integer

Dim strPath As String
Dim strServer As String

Set agent = session.CurrentAgent
VarLastRun = agent.LastRun
Dim dt As New notesdatetime(varLastRun)

Set dbTrgt = session.CurrentDatabase
Set view = dbTrgt.GetView(VIEWTARGET)

StrServer = dbTrgt.Server

'to use this, dbs must be in same directory
strPath = Left(dbTrgt.FilePath,_
Instr(dbTrgt.FilePath,dbTrgt.FileName)-2)+"\"

Call dbSrc.Open(strServer,strPath+_
DBSOURCE)

'get all docs since the agent last ran
Set collection =_
dbSrc.Search("Form=""Person""", dt, 0)

' for each document in the collection
For intCount=1 To collection.count

 Set docSrc=collection.GetNthdocument(intCount)

 'get the corresponding person doc by UNID
 Set docTrgt=_
 view.GetDocumentByKey(docSrc.UniversalID)

 'if there is a corresponding doc
 If Not docTrgt Is Nothing Then
  'check to see if the person has moved
  '(even if not, the whole doc will be updated)
  If docSrc.Location(0)<>docTrgt.Location(0) Then
   docTrgt.ChangeStatus="Location Change"
   docTrgt.LocationOld=docTrgt.Location(0)
  End If

  'update the changes

  Call docSrc.CopyAllItems(docTrgt,True)
  Call docTrgt.Save(True,True)

 Else
 'there isn't a corresponding doc so
 'create one and store the UNID of the
 'corresponding AcmeDirectory doc in it
  Set docTrgt=dbTrgt.CreateDocument()
  Call docSrc.CopyAllItems(docTrgt,True)
  docTrgt.ChangeStatus="New Employee"

  Set item=New NotesItem(docTrgt, SOURCEUNID,_
  docSrc.UniversalID)
  item.IsSummary=True

  Call docTrgt.Save(True,True)
 End If
 Next
End Sub

For the Computer Services application to know that a person has been terminated, it needs to know that a person document has been deleted from the Acme Directory. There are a few ways to do this. Every night an agent could go through each person document in the Computer Services database and attempt a look up to its sister document in the Acme Directory. When a look up failed, you would know that a document had been deleted, meaning that person had been terminated, so the document in the Computer Services database would be marked as "Terminated."

However, there is no view by UNID in the Acme Directory, and to have one put into such an important, heavily-used database takes a directive from the CIO. A customized replica is not an option either. If you used the Employee ID as a key instead of the UNID, it would be available for a database search. But it would take 10,000 separate searches to find all the possible terminations and your server just can't handle that load.

Another alternative is a data transfer tool such as Lotus Enterprise Integrator (Notes Pump), which can get all documents including deletion stubs. But they start at about US$8,000 dollars; and since your department's budget is all overhead expense to your company, that's $8,000 more than your in-house solution will cost.

The solution

When a document is deleted, it leaves behind a deletion stub. This is so when replication occurs, the other replica knows to delete its corresponding document. Using Notespeek, a utility that you can download right from the Lotus Knowledge Base, you can see what's left when a document is deleted and becomes a stub (figure 3). As expected, the document UNID is still in there along with the date of last modification, and, in this case, deletion. If you can access these stubs, there will be a much smaller number of documents to look through, and you can then efficiently update the Computer Services database.



Figure 3: The contents of a deletion stub viewed with Notespeek -- The trick is to get to the information programmatically with the code.

So, in a nutshell, you need to run an agent to get all the deletion stubs since the last update, see if any of their UNIDs match the UNIDs stored in the documents in the Computer Services database, and mark the related Computer Services documents "Terminated."

We'll show you the code and then go into some detail about where the LotusScript classes and API classes diverge (listing 2). To keep the code generic, we refer to Acme Directory as the source database (the source of the deletion stubs), and Computer Services as the target database (the one that contains the sister documents for which we are searching).

Listing 2: The code to get the deletion stubs from the Acme Directory and update the Computer Services database with Terminations -- Besides some changes in syntax, this isn't much different than some LotusScript code.

/*
Purpose:
This function checks all deletion stubs in the
Src Database. For each stub, it looks for a
document in the Target View of the Target
Database for documents with the Source document's UNID.

Parameters:
SrcDBName
The path of the Source database, relative to the
Data directory if a Server Name is specified.

TrgtDBName:
The path of the Target database, relative to the
Data directory if a ServerName is specified.

TrgtViewName:
The lookup view in the Target database, sorted
by the Source Document's UNID.

LastRunTime:
The last run time of this program, which serves as the cutoff date for checking stubs.

SrvrName:
The name of the server on which the databases
reside.
*/
#include <lncppapi.h>
#include <iostream.h>
#define ERR_BUF_SIZE 512

//argc is the number of arguments passed
//argv is the array of objects passed
void main(int argc, char *argv[])
{
char * SrcDBPath=NULL;
char * TrgtDBPath=NULL;
char * TrgtViewName=NULL;
char * LastRunTime=NULL;
char * SrvrName=NULL;

LNNotesSession Session;
LNDatabase dbSrc;
LNDatabase dbTrgt;
LNViewFolder viewTrgt;
LNNoteArray arrayNotes;
LNNote noteSrc;
UNID * ptr_UNID=NULL;
LNUniversalID unidNote;
LNString strUNID;

LNVFFindOptions findOptions;
LNVFNavigator navEntries;
LNVFEntry entryTrgt;
LNDocument docTrgt;
LNText textStatus;
LNINT i;
LNINT count;

//Verify that the proper number of
//parameters were passed.
if (argc < 5 || argc > 6)

 {
 //wrong number of arguments
 //remind them of the syntax
 char  CommandBuf[10];
 cout <<"\nFunction Syntax:\n\t"
     << argv[0]
     <<" <Src db> <Trgt db> <Trgt view> "
     <<" <last run time> \n"
     << endl
     << "Hit Return To Exit";
 cin.getline(CommandBuf, 10);
 //get out of the program
 return;
 }

// Get the parameter values.
SrcDBPath = argv[1];
TrgtDBPath = argv[2];
TrgtViewName = argv[3];
LastRunTime = argv[4];
if (argc == 6) SrvrName = argv[5];

// Make the error handler throw all errors
// encountered during execution.
LNSetThrowAllErrors(TRUE);

try
 {
 // Initialize the C++ API session.
 Session.Init(argc, argv);

 // Get the Src and Trgt databases.
 Session.GetDatabase(SrcDBPath,&dbSrc,SrvrName);
Session.GetDatabase(TrgtDBPath,&dbTrgt,SrvrName);
 // Open the databases so they can be accessed.
 dbSrc.Open();
 dbTrgt.Open();

 //Get and open the lookup view sorted by UNID of
 // Src document.
 dbTrgt.GetViewFolder(TrgtViewName, &viewTrgt);
 viewTrgt.Open();
 // Get all documents modified since LastRunTime.
 dbSrc.GetModifiedNotes(LastRunTime, &arrayNotes);
 for (i=0; i < arrayNotes.GetCount(); i++)
  {
  // Get a pointer to the current note
  // in the array.
  noteSrc = arrayNotes[i];

  //Check whether the note is a deletion stub.
  //before continuing.
  if (noteSrc.IsDeleted())
   {
   // Get a pointer to the UNID of the current  
   //deletion stub.
   ptr_UNID = noteSrc.GetUniversalID();

   // Create a LNUniversalID object with the
   //pointer and get the UNID as a string.
   unidNote = LNUniversalID (ptr_UNID);
   strUNID = unidNote.GetText();

   // Search the view for all the document
   //storing this UNID and update ChangeStatus.
   viewTrgt.Find(strUNID, &entryTrgt,&count,
   &navEntries);
   if (count>0)
    {  
    entryTrgt.GetDocument(&docTrgt);
    docTrgt.Open();

    //get change status item
    docTrgt.GetItem("ChangeStatus", &textStatus);

    //set text
    textStatus.SetValue("Terminated");

    //save the doc and the text change
    //will save with it
    docTrgt.Save();
    docTrgt.Close();
    }
   }
  }
 }

 //If an error occurred display the error message.
 catch (LNSTATUS Lnerror)
 {
 char  CommandBuf[10];
 char ErrorBuf[ERR_BUF_SIZE];
LNGetErrorMessage(Lnerror,ErrorBuf,ERR_BUF_SIZE);
 cout << "Error: " << ErrorBuf << endl
      << "Hit Return To Exit";
 cin.getline(CommandBuf, 10);
 }

 // Close the view and databases.
 viewTrgt.Close();
 dbSrc.Close();
 dbTrgt.Close();

 // Terminate the API session.
 Session.Term();
}



API bug and workaround

The best way to search for deletion stubs from a database is to use db.GetModifiedNotes( ) with a third parameter LNNOTETYPE_NOTIFY_DELETION, which should only retrieve deletion stubs since the LastRunDate -- but it doesn't work properly. At the Spring 1999 Lotus Notes & Domino Advisor DevCon, the Lotus representative handing out the API architecture posters told us that it wasn't working because of a bug. The workaround is to leave off the third parameter and get an array of all notes modified since the LastRunDate. You then need to loop through the array of notes and check the IsDeleted( ) method to determine whether it is a deletion stub and then update the sister document.



Just like in LotusScript, we started a session, opened some databases, performed a search and a view look up, and updated some documents. However, a few of the objects don't directly correspond to LotusScript classes.

LNNote

Behind the scenes, Lotus Notes treats every object in a database as a note (hence the product name). LNNote is a superclass that contains methods that you can use on all database objects, such as documents, forms, and views. IsDeleted is one of the properties of a Note and if its value is True, it's a deletion stub.

LNVFNavigator

The LNViewFolder.Find( ) method populates an LNVFNavigator object. LNVFNavigator is similar to a document collection in that it contains a subset of the documents in the view (those retrieved by the Find( ) method). However, it differs from a document collection in that the subset of documents is treated as though it were its own view. Some methods available on the LNVFNavigator object include GotoFirst( ), GotoNext( ), GotoNextMain( ), and GotoNextUnread( ). The LNVFNavigator object is an array of LNVFEntry objects.

LNVFEntry

This is analogous to LotusScript's new NotesViewEntry class in R5. It represents a row in a view or folder and provides access to the column values displayed in the row. Its GetDocument( ) method can also be used to obtain the underlying document.

LNUNIVERSALID and UNID

Once you have an LNNote object, you can use the GetUniversalID( ) method, but it returns a pointer to a UNID object, which isn't described anywhere in the documentation. The UNID object, even when dereferenced with an asterisk, is the eight-digit memory address of a pointer. To obtain the 32-digit hexadecimal UNID as a string, you must create an LNUniversalID object using the UNID object. You can then use the GetText( ) method of the LNUniversalID object to return the string needed for the lookup in the target database.

LNText

To change or append the value of an item in LotusScript, you could use Item.AppendToTextList(), and when the document is saved, the change in the item is also saved. In the API there is a bit more of a jump that has to do with the fact that LNText is derived from the LNItem class. An LNText object is returned from the LNDocument.GetItem() method. By setting LNText with SetValue and then saving the document, the change to the item is also saved.

Running the code from the command line

To try this code, open a text editor such as WordPad, type in the code, and save the file with a .cpp extension. You can then create another project and compile it following the same instructions from the sidebar. We called our project, GetStubsAndTerminate. You can make you own target and source databases or download ours at http://www.advisor .com/ wHome.nsf/w/MLNfiles, file: judgc05.zip. You can then populate the databases with related documents, delete some documents from the source database and run the program from DOS in your Notes directory. At the command line, enter the program name and arguments for the source database file name, target database file name, last run time, the name of the view in the target database by which the SOURCE UNIDs are categorized, and the server name which is optional (figure 4). A white space character defines a separation between the arguments, so use underscores in place of spaces within a single argument or enclose each argument in double quotes.



Figure 4: Calling the GetStubsAndTerminate executable from the command line -- Test the code this way before calling it from script.

When you run this code and your databases have some level of access control, the DOS prompt box displays the user name of the ID specified in your Notes.ini file. This is because you're starting a new process with a thread in Notes (if the program is run on a server whose ID has no password, there will be no prompt). After entering your password and hitting Return, your program should crunch for a couple of seconds and the sister docs of the documents you deleted in the source database should be marked "Terminated."

Running the code from LotusScript

Once you check that the code works from the command line, you can use the more efficient and less mistake-prone method of calling it with LotusScript. Calling a Notes API program from Notes runs two threads of Notes concurrently: one thread being your Notes client and the other initialized by the API program. This is not recommended by Lotus as stated in the documentation, but it works (listing 3).

Listing 3: Script to call the GetStubsAndTerminate program -- Since this runs two threads of Notes concurrently, only use it to test your code.

Options
Option Public
Option Declare

Declarations
'customize these settings for your use
Const DBSOURCE= "AcmeDirectory.nsf"
Const VIEWTARGET="PeopleByADUNID"

Initialize
Sub Initialize
Dim session As New notessession
Dim dbTrgt As notesdatabase
Dim view As notesview
Dim agent As NotesAgent
Dim varLastRun As Variant

Dim strPath As String
Dim strServer As String
Dim strLastRun As String
Dim intSuccessfulStart As Integer
Dim strArguments As String
Set dbTrgt=session.CurrentDatabase
Set agent=session.CurrentAgent

varLastRun=agent.LastRun
strLastRun=Str(varLastRun)
'for testing, you can roll back the time
strLastRun="09/10/2000 12:01:01 AM"

strServer=dbTrgt.Server

'to use this, dbs must be in same directory
strPath=Left(dbTrgt.FilePath,_
Instr(dbTrgt.FilePath,dbTrgt.FileName)-2)+"\"

'the name of the exe and all its arguments
'needs to be in one string expression.

strArguments=|"GetStubsAndTerminate" "| & _
strPATH+DBSOURCE & |" "| & dbTrgt.FilePath &_
|" "| & VIEWTARGET & |" "| & strLastRun &_
|" "| & strServer & |"|

'the return value for shell is OS specific
'and only lets us know that the program started OK
intSuccessfulStart= Shell(strArguments, 1)
End Sub

This executable program works fine, but again, it's not recommended by Lotus. Also, this procedure should be part of a scheduled agent and chances are your administrator isn't going to let it near his server in its present form.

Running the API code as a DLL function

The next step is to make a few simple changes to the C++ code and compile it into a DLL. Then we can declare it and call it in our script, just like Henry and Rocky showed you in their articles previously mentioned. This means that instead of GetStubsAndTerminate being its own executable program, it will be a function within a DLL we will call 'StubMaintenance' (listing 4). We don't know what else we'll do with deletion stubs, but if we think of something, we can add a function to our StubMaintenance DLL. Using MS Visual Studio and Windows, our compiler procedure and settings didn't change except that out project was a Win32 Dynamic-Link Library instead of a Win32 Console Application. Listing 5 shows the code to call the DLL.

Listing 4: The StubMaintenance DLL -- GetStubsAndTerminate has become a function within the DLL.

/*
Purpose:
This dll performs maintenance related to deletion stubs. The only function, GetStubsAndTerminate, checks all deletion stubs in the Source Database. For each stub, it looks for a document in the Target View of the Target Database for documents with the source document's UNID.
*/
#include <lncppapi.h>
#include <iostream.h>
#define ERR_BUF_SIZE 512

//this is the Linkage Directive
//it allows the code to be called by LotusScript
extern "C"
{
extern __declspec( dllexport )
int GetStubsAndTerminate(char * SrcDBPath,
char * TrgtDBPath, char * TrgtViewName,
char * LastRunTime, char * SrvrName);
}

//this is how we will declare the function in LS
//except we will use string instead of char *
int GetStubsAndTerminate(char * SrcDBPath,
char * TrgtDBPath, char * TrgtViewName,
char * LastRunTime, char * SrvrName)
{
LNNotesSession Session;
Session.Init ();

LNDatabase dbSrc;
LNDatabase dbTrgt;
LNViewFolder viewTrgt;
LNNoteArray arrayNotes;
LNNote noteSrc;
UNID * ptr_UNID=NULL;
LNUniversalID unidNote;
LNString strUNID;

LNVFFindOptions findOptions;
LNVFNavigator navEntries;
LNVFEntry entryTrgt;
LNDocument docTrgt;
LNText textStatus;
LNINT i;
LNINT count;

// Make the error handler throw all errors
// encountered during execution.
LNSetThrowAllErrors(TRUE);

try
 {
 // Get the Src and Trgt databases.
 Session.GetDatabase(SrcDBPath,&dbSrc,SrvrName);
 Session.GetDatabase(TrgtDBPath,&dbTrgt,SrvrName);
 // Open the databases so they can be accessed.
 dbSrc.Open();
 dbTrgt.Open();

 //Get and open the lookup view sorted by UNID of
 // Src document.

 dbTrgt.GetViewFolder(TrgtViewName, &viewTrgt);
 viewTrgt.Open();

 // Get all documents modified since LastRunTime.
 dbSrc.GetModifiedNotes(LastRunTime, &arrayNotes);
 for (i=0; i < arrayNotes.GetCount(); i++)
  {
  // Get a pointer to the current note
  // in the array.
  noteSrc = arrayNotes[i];

  //Check whether the note is a deletion stub.
  //before continuing.
  if (noteSrc.IsDeleted())
   {
   // Get a pointer to the UNID of the current  
   //deletion stub.
   ptr_UNID = noteSrc.GetUniversalID();

   // Create a LNUniversalID object with the
   //pointer and get the UNID as a string.
   unidNote = LNUniversalID (ptr_UNID);
   strUNID = unidNote.GetText();

   // Search the view for all the document
   //storing this UNID and update ChangeStatus.
   viewTrgt.Find(strUNID, findOptions,
   &entryTrgt,&count,&navEntries);
 
   if (count>0)
    {
    entryTrgt.GetDocument(&docTrgt);
    docTrgt.Open();

    //get change status item
    docTrgt.GetItem("ChangeStatus", &textStatus);

    //set text
    textStatus.SetValue("Terminated");

    //save the doc and the text change
    //will save with it
    docTrgt.Save();
    docTrgt.Close();
    }
   }
  }

 // Close the view and databases.
 viewTrgt.Close();
 dbSrc.Close();
 dbTrgt.Close();

 // Terminate the API session.
 Session.Term();

 // Exit the function returning a success code
 // of 1 back to the LS call.
 return (1);
 }

 //If an error occurred, return 0.
 //Not referencing the Lnerror will give a
 //warning at compile time, but Catch
 //expects it to be declared and we
 //don't need to use it.
 catch (LNSTATUS Lnerror)
 {
 // Close the view and databases.
 viewTrgt.Close();
 dbSrc.Close();
 dbTrgt.Close();
 
 // Terminate the API session.
 Session.Term();
 
 // Exit the function returning a failure code
 // of 0 back to the LS call.
 return (0);
 }
}

Listing 5: LotusScript code to call the GetStubsAndTerminate function inside the StubsMaintenance DLL -- This time, it's a function call; not starting another program.

Options
Option Public
Option Declare

Declarations
'declare the function and the dll where it
'is located
Declare Function GetStubsAndTerminate _
Lib "StubMaintenance.dll" _
(Byval SourceDBPath _
As String, Byval TargetDBPath As String, _
Byval TargetViewName As String, _
Byval LastRunTime As String,_
Byval ServerName As String) As Integer

'customize these settings for your use
Const DBSOURCE= "AcmeDirectory.nsf"
Const VIEWTARGET="PeopleByADUNID"

Initialize
Sub Initialize
Dim session As New notessession
Dim dbTrgt As notesdatabase
Dim view As notesview
Dim agent As NotesAgent
Dim varLastRun As Variant

Dim strPath As String
Dim strServer As String
Dim strLastRun As String
Dim intSuccess As Integer

Set dbTrgt=session.CurrentDatabase
Set agent=session.CurrentAgent

varLastRun=agent.LastRun
'strLastRun=Str(varLastRun)
'for testing you can roll back the time
strLastRun="09/10/2000 12:01:01 AM"

strServer=dbTrgt.Server

'to use this, dbs must be in same directory
strPath=Left(dbTrgt.FilePath,_
Instr(dbTrgt.FilePath,dbTrgt.FileName)-2)+"\"

'this time the return code let's us know that
'the dll call went successfully to completion
intSuccess=GetStubsAndTerminate(strPATH+DBSOURCE,_
dbTrgt.FilePath,VIEWTARGET,strLastRun,strServer)
End Sub

Notice when you execute this code, there's no DOS prompt because you're working within the same process in Notes. And it's thread safe, since the LotusScript code waits for the DLL to return before continuing to execute.

Distributing the code

After you have your code working and tested, you can distribute it to any user by placing the C API lcppn21.dll (that's for the MS Visual Studio compiler -- check the documentation for other compilers) and the DLL you made into the user's Notes directory. This could be as simple as attaching the files to a Notes document and having a script unload them onto their machines. Since this particular code is most effective as part of a scheduled agent, you can also place the files in the Domino directory of a server. This assumes a default and registered install of Notes; otherwise you might need some additions to the user's environment settings.

Caveats

Even deletion stubs are eventually deleted. If you go to the Replication Settings area of the database properties (figure 5), you'll see a check box labeled, "Remove documents not modified in the last:". This setting not only controls how often unmodified documents are deleted, it also determines how often deletion stubs are purged, regardless of whether or not the box is checked. See Lotus Knowledge Base Technote 158404 for details. The deletion stub code only works if it's run while the deletion stub is still in the database, so schedule the run interval accordingly. If the above setting is '0', for example, the deletion stubs are purged as soon as they are created. In that case, you couldn't use the code at all.



Figure 5: Settings to control deletion stub purge -- The code must be run while the deletion stub is still in the database.

What else can you do?

This article just scratches the surface of the C++ API. What else can you do with the C++ API? Well, here's a list straight from the Toolkit
documentation:
  • Create, copy, replicate, and delete databases
  • Get and set many database design properties
  • Create, copy, and delete database help documents
  • Copy and delete database icons
  • Create, copy, modify, and delete folders, views, forms, subforms, shared fields, agents, actions, formulas, LotusScript, and simple actions
  • Manipulate folder contents and move folders
  • Read and modify design properties for views, folders, agents, actions, forms, and subforms
  • Work with rich text

Did it say you can create views? Yes, it sure did.


Setting Up and Running a C++ API Program

Before delving into the code, you'll need to install the C++ API and configure the environment. There are a lot of settings, and missing any one of them is sure to cause you headaches.

Download the C++ API Toolkit at http://www.lotus.com/techzone. Choose the API specific to your operating system and your version of Notes. We use R5, so we downloaded version 2.1 If you are using R4, you'll need to download version 2.01. While you are there, download Toolkit documentation which is a database titled, The C++ API Release Guide. This is not only a great reference for all the API classes, functions, and data types, but also has a User's Guide for setup and use of the API, including descriptions of the sample programs. This sidebar is a distillation of the set up documentation in the guide. Here, we'll go over a setup with 32 bit Windows and a clean, default install of Microsoft Visual C++ 6.0 and Notes R5. Other operating systems and compilers are also supported and detailed in the Guide. After downloading the zipped API files, extract them in the root of your hard drive, which will create a NOTESCPP directory.

Environment Variable Setup

If you let Visual Studio set up your environment variables when you installed it, you're all set. Otherwise, refer to the "About environment settings" for your operating system in Chapter 02 of the User's Guide database.

Compiler Setup and API Test

Our first program will be one of the sample programs in the Toolkit, DBTitle. We are even going to go over the code for this one; we just want to make sure the environment and compiler are set up correctly. This will greatly improve development time, since you're reducing your debugging scope to programming
errors only.

1) Launch Visual Studio

2) Create a New Project

a) Select File > New
b) Select Win32 Console Application (we aren't getting fancy with a GUI)
c) Enter 'GetDBTitle' for the project name and click OK
d) Click Finish, then OK.

3) Add the source code to the project

a) Select Project > Add to Project > Files
b) Select the source code, C:\notescpp\samples\dbtitle\dbtitle.cpp, and click OK

4) Add the API library to the project

a) Select Project > Add to Project > Files
b) Add the lib file, C:\notescpp\lib\mswin32\notescpp.lib and click OK.

Note: Make sure that "Files of Type" is set to "All Files (*.*)" so you can see the file.

5) Project Settings

a) Select Build > Set Active Configuration > Win32 Release > OK
b) Select Project >Settings, choose the C/C++ tab, and make the following changes:

i) In the General category, add W32 to the list of preprocessor definitions
ii) In the Code Generation category, set Struct Member Alignment to 1 Byte
iii) In the Preprocessor category, add 'C:\NOTESCPP\INCLUDE' to Additional Include Directories
iv) Click OK

6) Build the Program

a) Select Build > Build GetDBTitle.exe
b) Wait to see if you get any errors or warnings

If the program didn't compile, recheck your settings and, if needed, go to the Toolkit documentation for more detailed instructions. There are dozens of settings and any one accidentally changed will give you problems.

To test your program, first make a copy of GetDBTitle.exe -- you'll find it in C:\Program Files\Microsoft Visual Studio\MyProjects\GetDBTitle\Release -- and paste it into your Notes directory. Don't drag and drop it -- because it's an exe, Windows will think you're trying to create a shortcut to it.



Figure 1a: Testing the sample program from the command line -- Making sure this sample program works is key to knowing your compiler and environment settings are set up properly.

Before running the program, include the C API dll that the program uses to interface with Notes. For us, that's the lcppn21.dll. You'll find it in C:\notescpp\lib\mswin32\. Copy it into the Notes directory.

To run your program, open the DOS prompt, move to the Notes directory, type in 'GetDBTitle names.nsf' and hit return (figure 1a). The program should return the title of your Personal Address Book. Remember, you can't just double click on GetDBTitle.exe in Explorer to start it -- you have to call it from the command line along with a Notes file name argument.

Now that you have a working program, you can be sure that the environment and compiler are set up correctly. If your program did not compile and run, check the Toolkit Guide -- it has more detailed information than supplied here.

Brad Balassaitis is a senior developer for the e-business consulting division of Palarco, Inc. He is an R4 and R5 PCLP application developer with experience in projects ranging from workflow to Web integration. balassaitis@yahoo.com. W. Colin Judge is dual principally certified in R4 and R5. mncjudge@altavista.com. Brad and Colin welcome comments on this article and are interested in hearing from other developers about how they have used the C++ API.

Printer-friendly
page layout

Take Advantage of the C++ API

No reader comments ... yet.

    What do YOU think about this topic? Share your advice and thoughts using this form.

    Your Name

    REQUIRED : PUBLIC

    Your E-Mail

    REQUIRED : PRIVATE

    Job, Company

    OPTIONAL : PUBLIC

    City, State, Country

    OPTIONAL : PUBLIC

    Your Web Site

    OPTIONAL : PUBLIC

    Your Comment

    Please help everyone by keeping your comments on-topic, using clean language, and not defaming or making personal attacks.


    Your e-mail address is required, but it will not be displayed to the public or given to anyone. See our Privacy Policy. Comments become visible after they pass our spam filter, and spammers and abusers are permanently blocked. Please report spam or abuse.

    ARTICLE INFO

    FREE ACCESS FREE ACCESS



    DOWNLOAD: 174,418 bytes

    Keyword Tags: Application Development, Business Software, C Language, C++ API, C++ Language, Database, Database Development, Database Management, Development, E-Business, IBM, IBM Lotus, Lotus, Lotus Formula Language, Lotus Notes, Lotusscript, LotusScript, Microsoft Visual Studio, Microsoft Windows, Tech: Development

    Use of this or any other site, content, product or service of Advisor Media constitutes acceptance of Terms of Use.
    Portions copyright ©1983-2010 Advisor Media, LLC. All Rights Reserved.
    Reuse or reproduction of any portion or quantity of Advisor Media's copyrighted content, in any form, for any purpose, requires written permission.
    ADVISOR®, the ADVISOR logo, and other names and logos that incorporate ADVISOR are registered trademarks, trademarks or service marks of Advisor Media, LLC in the United States and/or other countries.
    Other trademarks are used for identification, editorial or descriptive purposes and are the property of their owners.
    Hosted by Prominic.NET Website powered by
    LOTUS SOFTWARE
    mln0012 JUDGC05 posted 2000-12-1 mod 03/05/2010 03:13:12 AM ztdbms/ztdbms
    domino-144.advisor.com my.advisor.com 03/11/2010 06:41:37 PM