Calling the Domino C-API from an XPage or a Java agent


Nathan Freeman recently wrote on his blog about IBM has made some Java classes which calls the Domino C-API with JNI. This apparently gives a 3-4 times boost in speed.

This is really great on one hand because it shows the possibilities, but on the other it is not public classes to Notes/Domino and is only reflecting a small portion of the API.



To be fair I have done little work including the Domino C-API, and it has all been about calling the API from LotusScript.
I highly recommend the book (PDF) "LotusScript to Lotus C API Programming Guide" by Normunds Kalnberzins in this context.

All Notes/Domino work in the future will be based around Java and Javascript, so it seems natural to investigate how the C-API could be called from Java in an easy way.

If you want to call the Domino C-API (or any other shared library) there is another way than to use JNI (Java Native Interface)
The answer is called JNA (Java Native Access) and is a project on java.net.

JNA uses dynamic access at runtime without code generation, no JNI or native code is required. Just java!
Calling the C-API from an other language often requires a bit of work because C is so "flexible".

I will give you a Proof of Concept and let you dive further into it.

The example - Proof of Concept

1. First lesson - you will blow up and crash your Notes Client
Working with the Domino API is more or less, either you get it right or your client crashes.
Fortunately there is setting in JNA
Native.setProtected(true);
Put this in your code and the problems will be kept within the JVM and not crash the whole client. You will love this.

2. Pick a simple example - I chose NSFDbSpaceUsage.
NSFDbSpaceUsage is used for getting information on how large the database file is and how much free space it contains.
It is used in the db properties.


3. Java does NOT pass method arguments by reference; it passes them by value, "always".
Java does manipulate objects by reference, and all object variables are references. However, Java doesn't pass method arguments by reference; it passes them by value.
This is obviously a problem since a whole lot of the Domino calls are passed by reference.
Actually it is not completely the truth because you could use a Java array with a single element of the desired type, but JNA has a ByReference convention which better conveys the intent of the code.
So in a by reference call you would used "IntByReference" for a int returned.

4. Let's get started. Download JNA.
First you need JNA, so go and download it
Copy the jna.jar file to your ext library.



Restart the Notes client and designer.

5. Create the Java interface and the type conversions
In using JNA you need to create an interface for the definitions for the Domino C-API or any other API you would call.
In my simple example you need definitions for

NSFDbOpen
NSFDbClose
NSFDbSpaceUsage

You need to download the Domino C-API reference to get going.
From this you get:

NSFDbSpaceUsage - Returns information about the usage of space in a database.
----------------------------------------------------------------------------------------------------------


#include <nsfdb.h>


STATUS LNPUBLIC
NSFDbSpaceUsage(
DBHANDLE hDB,
DWORD far *retAllocatedBytes,
DWORD far *retFreeByes);

NSFDbOpen - Opens an existing Notes database or a directory.
----------------------------------------------------------------------------------------------------------


#include <nsfdb.h>


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

NSFDbClose - Closes a database.
----------------------------------------------------------------------------------------------------------


#include <nsfdb.h>


STATUS LNPUBLIC
NSFDbClose(
DBHANDLE hDB);

What you get from this by digging into the Domino C-API reference is:
NSFDbOpen needs a string to the path (by copy), and a dbhandle of type unsigned int (by reference) and it returns a type of WORD

You need to get these definitions and translations right or Notes WILL crash (or be limited to the JVM failing.)
This is the hard part until you get the hang of it (I will NEVER fall in love with C!)

These native C types must be translated into Java types and JNA has a table for this.
C TypeNative RepresentationJava Type
char8-bit integerbyte
wchar_tplatform-dependentchar
short16-bit integershort
int32-bit integerint
intboolean flagboolean
enumenumeration typeint (usually)
long long, __int6464-bit integerlong
float32-bit floating pointfloat
double64-bit floating pointdouble
pointer (e.g. void*)platform-dependent (32- or 64-bit pointer to memory)Buffer
Pointer
pointer (e.g. void*),
array
32- or 64-bit pointer to memory (argument/return)
contiguous memory (struct member)
<P>[] (array of primitive type)
In addition to the above types, which are supported at the native layer, the JNA Java library automatically handles the following types. All but NativeMapped and NativeLong are converted to Pointer before being passed to the native layer.
longplatform-dependent (32- or 64-bit integer)NativeLong
const char*NUL-terminated array (native encoding or jna.encoding)String
const wchar_t*NUL-terminated array (unicode)WString
char**NULL-terminated array of C stringsString[]
wchar_t**NULL-terminated array of wide C stringsWString[]
void**NULL-terminated array of pointersPointer[]
struct*
struct
pointer to struct (argument or return) (or explicitly)
struct by value (member of struct) (
or explicitly)
Structure
unionsame as StructureUnion
struct[]array of structs, contiguous in memoryStructure[]
void (*FP)()function pointer (Java or native)Callback
pointer (<T> *)same as PointerPointerType
otherinteger typeIntegerType
othercustom mapping, depends on definitionNativeMapped
As you can see it does not contain information about what DWORD or WORD is, but it can be translated into int and short

So what you will get is this:
short NSFDbOpen(String dbName, IntByReference dbHandle);

NSFDbClose is easy now:
short NSFDbClose(IntByReference dbHandle);

What about NSFDbSpaceUsage then?

Well it would be something like:
NSFDbSpaceUsage needs a dbhandle of unsigned int (by copy), and a pointer to type of DWORD for retAllocatedBytes (by reference), and a pointer to type DWORD for retFreeByes (by reference);

This will translate into
short NSFDbSpaceUsage(int dbHandle, IntByReference retAllocatedBytes, IntByReference retFreeBytes);

6. Calling NSFDbSpaceUsage from a Java agent
Now we are ready for actually calling the API

With the new interface, define an instance of the native library using the Native.loadLibrary(Class) method
nnotes lib = (nnotes) Native.loadLibrary("nnotes", nnotes.class);

You can now call the methods defined by:
short errorint = lib.NSFDbOpen("anotesserver!!log.nsf", dbHandle);

-----------------------------------------------------------------

Here is the full example
(This is for win32, a very small change is needed for Linux):

The interface

import com.sun.jna.*;


import com.sun.jna.ptr.IntByReference;
import com.sun.jna.win32.*;

public interface nnotes extends StdCallLibrary {


short NSFDbOpen(String dbName, IntByReference dbHandle);
short NSFDbClose(IntByReference dbHandle);
short NSFDbSpaceUsage(int dbHandle, IntByReference retAllocatedBytes, IntByReference retFreeBytes);
}


The agent:

import lotus.domino.*;


import com.sun.jna.*;
import com.sun.jna.ptr.IntByReference;

public class JavaAgent extends AgentBase {

IntByReference dbHandle;
IntByReference retAllocatedBytes;
IntByReference retFreeBytes;
SimpleConsole console;

public void NotesMain() {

try {
Session session = getSession();
AgentContext agentContext = session.getAgentContext();
console = new SimpleConsole();

Native.setProtected(true);
nnotes lib = (nnotes) Native.loadLibrary("nnotes", nnotes.class);

dbHandle = new IntByReference();
retAllocatedBytes = new IntByReference();
retFreeBytes = new IntByReference();


short errorint = lib.NSFDbOpen("anotesserver!!log.nsf", dbHandle);

errorint = lib.NSFDbSpaceUsage(dbHandle.getValue(),
retAllocatedBytes, retFreeBytes);
errorint = lib.NSFDbClose(dbHandle);
float percentused = 100 * (float) retAllocatedBytes.getValue()
/ (retFreeBytes.getValue() + retAllocatedBytes.getValue());
console.addText(Float.toString(percentused));


} catch (Exception e) {
console.addText(e.getMessage());
e.printStackTrace();
}
}
}


The agent displays this:

Which matches the value in the properties box.

(The SimpleConsole code can be downloaded here)

Conclusion
Using JNA for accessing the Domino C-API or any other library is way too cool!
Nothing more than defining the interface for the calling the C-API is needed. That is it!
(Most can actually be done automatically)

This is needed done only once and everybody in the Notes/Domino community are happy !! (or less sad)
Do I hear a OpenNTF project starting up?

Is this better than calling the C-API from LotusScript?

I some situations yes

- You may need to call the c-api from an Xpage or a Java agent
- you may need to do callbacks

The catch?

The performance may suffer, but I have not done any testing.
I will leave that to the cool guys.

javaagent.java javaagent.java nnotes.java nnotes.java


Posted on 03/21/2010 08:33:16 PM CET