Notes API Tips

API Constants
Translating API Data Types to LotusScript
Learning How To Call the Notes API from LotusScript
Performing a Stubless Delete Using LotusScript
A Trick for Creating API TIMEDATE Structures in LotusScript
Translating Notes API Errors
Getting Server Stats Using API Calls
Synchronizing Notes Time With The OS Time
Listing the ND6 Restriction Level for All Agents
LotusScript Class To Get Unread Docs In A Database Or View
Get Selected Docs in a View Using the Notes API


API Constants
August 31, 2002

Here's a bit of LotusScript code I wrote to extract the constant values used by the Notes API, as defined in the header files. It reads the headers and sends all the #define values to a text file, in comma-delimited format. This is a handy reference when you're making API calls from LotusScript, so you know what all those constant values mean.

I've also included an Excel spreadsheet with the values from the headers in the 5.06 API distribution, with a little formatting thrown in to make it easier to look at.

Apiconst.zip


Translating API Data Types to LotusScript
September 4, 2002 (updated January 2, 2003)

When you're making C-Declarations in LotusScript in order to call a Lotus Notes API function, you need to translate the API data type to an equivalent LotusScript data type. Here's a partial list of conversions for you (thanks to Normunds Kalnberzins for a few clarifications):

API Data TypeLotusScript Data Type
char, char*ByVal String
char far*ByVal String
intLong (4-byte value)
longLong
WORD, SWORDInteger
DWORDLong
INT, UINTInteger
SHORT, USHORTInteger
LONG, ULONGLong
NUMBERDouble
BOOLLong (in general,
sometimes Integer)
BYTE, BOOLBYTEByte in R5+,
cannot convert pre-R5
STATUSInteger
HANDLELong (Integer on Mac
and some UNIX)
HMODULELong
NULLByVal Integer (0)

In general, when you have a pointer (*) to a variable, you'll want to pass that parameter ByRef, otherwise you should pass it ByVal -- the exceptions are Strings (which you should pretty much always pass ByVal) and user-defined data types (which you have to pass ByRef). If you want to pass a NULL value to an API function, declare the parameter as an Integer and pass a zero ByVal.

As an example, these two commonly-used API functions:

STATUS LNPUBLIC NSFDbOpen(char far *PathName, DBHANDLE far *rethDB);
STATUS LNPUBLIC NSFDbClose(DBHANDLE hDB);

are defined like this in LotusScript (on a Windows platform):

Declare Function NSFDbOpen Lib "nnotes.dll" (Byval PathName As String, _
rethDB As Long) As Integer
Declare Function NSFDbClose Lib "nnotes.dll" (Byval hDB As Long) As Integer

Note that the function's return value (STATUS, in this case) is at the beginning of the function declaration in C, and the LNPUBLIC part of the function gets thrown away (that's just the calling convention in C, and has nothing to do with your LotusScript calls). If a C function returns "void", you can treat it like a Sub.


Learning How To Call the Notes API from LotusScript
from my blog entry of June 16, 2003

Occasionally I get asked about where to find instructions and examples of how to call the Notes API from LotusScript. Here's my standard response:


Performing a Stubless Delete Using LotusScript
October 1, 2003

Here's an example of using the C-API to perform a "stubless" delete of a document -- in other words, you delete the document without creating a deletion stub in the database. You shouldn't normally have a need for this, but just in case... (Stan Rogers posted one possible use for this on the LDD forums)

This method can also be used to remove a deletion stub from a database, if you happen to know what the NoteID of the stub is (you can use DelStubs or NotesPeek to find out).

(By the way, jonvon wrote a wrapper for this technique, in case you need to perform mass deletions.)

ApiStublessDelete.lss (html)


A Trick for Creating API TIMEDATE Structures in LotusScript
October 3, 2003 (updated March 10, 2004)

I've never been able to get ConvertTextToTIMEDATE to work right in LotusScript, so whenever I've had to create my own API TIMEDATE values I've always had to make an OSCurrentTIMEDATE call and then do a series of calls to TimeDateAdjust (if you try to adjust more than one time element at once, your results might not be correct, so you have to make a call to adjust the seconds, then a call to adjust the minutes, then the hours, then the days...).

It turns out that you can also create a TIMEDATE value directly with an undocumented @Function call and a little trickery. I found the reference to the undocumented call in this post to LDD made by Mike Barlow, and deduced the rest using my sketchy knowledge of the TIMEDATE structure.

Type TIMEDATE
	Innards(0 To 1) As Long
End Type

	Dim apiTime As TIMEDATE
	Dim formulaTime As Variant
	
	formulaTime = Evaluate( |@Text([09/13/2003 10:30:00 AM]; "*")| )
	apiTime.Innards(0) = Val("&H" & Strright(formulaTime(0), ":") & "L")
	apiTime.Innards(1) = Val("&H" & Strleft(formulaTime(0), ":") & "L")

And there you go, the apiTime variable is now a valid TIMEDATE value for Notes API calls. I couldn't figure out a good non-API way of converting back, though. I still end up using ConvertTIMEDATEToText (with 0 as the first two parameters), although that's a fairly trivial call to make.

Declare Function ConvertTIMEDATEToText Lib "nnotes.dll" (Byval IntlFormat As Integer, _
Byval TimeFormat As Integer, InputTime As TIMEDATE, Byval retTextBuffer As String, _
Byval TextBufferLength As Integer, retTextLength As Integer) As Integer

	Dim retStr As String*81
	Dim retLen As Integer
	Dim retCode As Integer
	retCode = ConvertTIMEDATEToText(0, 0, apiTime, retStr, Len(retStr)-1, retLen)

As an interesting shortcut, I think you can reliably calculate the date part by doing:

	Cdat(apiTime.Innards(1) + 2061182549)

but the Time part is much more elusive. (by the way, -2061182549 is the Innards[1] value of December 30, 1899, which is Cdat(0) in LotusScript -- that's where that number came from.)

You can also use this technique to convert a Notes Database Replica ID to a time/date (a Replica ID is actually a TIMEDATE representing the exact moment that the original copy of the replica database was created):

Type TIMEDATE
	Innards(0 To 1) As Long
End Type

Declare Function ConvertTIMEDATEToText Lib "nnotes.dll" (Byval IntlFormat As Integer, _
Byval TimeFormat As Integer, InputTime As TIMEDATE, Byval retTextBuffer As String, _
Byval TextBufferLength As Integer, retTextLength As Integer) As Integer

Sub Initialize
	Dim apiTime As TIMEDATE
	Dim replicaID As String
	Dim session As New NotesSession

	replicaID = session.CurrentDatabase.ReplicaID
	apiTime.Innards(0) = Val("&H" & Right(replicaID, 8) & "L")
	apiTime.Innards(1) = Val("&H" & Left(replicaID, 8) & "L")

	Dim retStr As String*81
	Dim retLen As Integer
	Dim retCode As Integer
	retCode = ConvertTIMEDATEToText(0, 0, apiTime, retStr, Len(retStr)-1, retLen)

	Print "Replica ID = " & replicaID & "; Original Replica Creation Time = " & _
	Left$(retStr, retLen)
End Sub

If you want a little more information about the TIMEDATE structure in the Notes API, there's some good information in the "Data Types" Appendix of the C API User Guide database that comes with the API toolkit.


Translating Notes API Errors
October 7, 2003

Many of the Notes API functions return a status code to indicate whether or not the call was successful. A successful call will normally return a status of 0 (NOERROR), while an unsuccessful call will normally return a numeric error code. To determine what the error code actually means, you can call OSLoadString to translate it to a text string.

Below is an example of how to call OSLoadString from a LotusScript agent. The agent itself simply makes a call to the function for every integer value from 0 to 3FFF (which is the highest error value possible, based on the API documentation), and for every value that returns a non-empty string, it writes the value to a CSV file. For your LotusScript API routines, you can simple copy and paste the GetApiError function for general use.

You should note that some of the strings that are returned by this agent are not true errors, but are simply informational text that is stored in the Notes string tables for use by other functions. For example, a call to OSLoadString using an error code of 1 will return the version of Notes that is being used, along with the release date of that version.

ApiErrList.lss (html)


Getting Server Stats Using API Calls
October 14, 2003

This is a short agent that demonstrates two things:

The more important concept is actually copying a memory buffer to a String (or any other variable type, really), because that comes up every once in a while when calling the API functions. I'm using an undocumented Notes API function called Cmovmem to do this.

ApiStats.lss (html)


Synchronizing Notes Time With The OS Time
December 28, 2003

This is an agent that will sync your Notes time with the OS time on the machine. This can be a problem with high-availability servers that have long periods of uptime, because the Domino server time will often start to drift away from the operating system time. This agent uses an undocumented Notes API function (OSSetCurrentDateTime) to make the two times the same. As with all undocumented functions, please be careful, especially if you decide to try to sync the time on a server that happens to be in the middle of running one or more server tasks.

Instructions for testing this out on a Notes client are provided in the comments of the script. You can also use the command-line utility ntimeset to perform this functionality.

SetNotesTime.lss (html)


Listing the ND6 Restriction Level for All Agents
January 19, 2003

This is a tip that uses the Notes API to check the new ND6 restriction level for all agents in all databases on a server. This is useful because it tells you which agents you need to check to make sure that the runtime security setting is correct (see the list of restricted operations for reference, but keep in mind that CreateObject and GetObject seem to be restricted operations as well). Take particular note of scheduled agents with an effective runtime security level of 1. I also wrote a blog entry that discusses this and other ND6 upgrade issues.

This agent can also be used as a template for writing your own agent that gets a list of agents and/or script libraries in a database and returns their NoteIDs so you can read and write to them like regular NotesDocuments. See Damien Katz's DatabaseDesign library for a class that uses the same technique (and is much more flexible), and which also presents an interesting way to do this by creating a temporary view and manipulating the $FormulaClass field.

(UPDATE: Richard Schwartz mentioned that you can also use the NotesNoteCollection class in ND6 to do this sort of thing much more easily -- if you're already using an ND6 client, you should check out the Designer help for that class.)

AgentRestictionList.lss (html)