Bob Balaban's Blog

     
    alt

    Bob Balaban

     

    Calling Notes CAPI from C-sharp/Visual Studio

    Bob Balaban  May 5 2008 12:11:47 PM
    Greetings, Geeks!

    How many of you have ever written LotusScript code that needed to call C entry points in some DLL (such as, maybe, the Notes C API, in nnotes.dll)? I've done it, many of you probably have too.

    Well, I spent a few days recently trying to figure out how to do the same thing, but from a C# ("c-sharp") program running as "managed code" in .NET. I'm using Visual Studio 2005 as my IDE. In the end I cracked it, and it wasn't too difficult. The real secret was to find the magic incantation equivalent to "DECLARE" in LotusScript.

    Background:

    I inherited some C# code that uses the Notes COM interfaces to do some stuff with user mail files. The COM interfaces are pretty much the same as the LotusScript back-end classes, they work great in a .NET environment for accessing NSF data. As an enhancement to the existing code (which loops over all documents in a user mail NSF and does things to them), I wanted to find out which documents were unread for the mail file's owner. This involved two extra steps that I needed to research:
    1.        Figure out the name of the mailbox owner. NotesPeek came to my rescue (as it has so many times before), and I found what I needed. It's a Profile document called "calendarprofile" (just use NotesDatabase.GetProfileDOcument("calendarprofile", "") ), and the name is on an item named "Owner". So far, so good. It would be a good idea to make sure that the name you get is in "distinguished name" format (i.e., cn=xxx/o=yyy...)
    2.        Figure out if the curernt document is unread for the name we found in step 1. Unfortunately, there is no back-end class support for this functionality, you have to drop down to the C API. I knew how to do this in LotusScript, but not from .NET. Fortunately, some Google research found me a couple of articles that were very helpful (links at the end of this post). Knowing what C API calls to use, I was able to write a C# class to tell me whether a NOTEID is in the unread list or not.

    It turns out that the magic incantation in C# is easy to do, once you know it. You have to tell CLR ("common language runtime") where the DLL is that you want to access, and what the signature (call name and parameter types) of the entry point you need is. Just like Declare statements in LotusScript (with different syntax, of course).

    Here's an extract from my code showing the Notes entry points I needed:


          /*
           * STATUS LNPUBLIC NSFDbOpen(
              const char far *PathName,
              DBHANDLE far *rethDB);
          */
          [DllImport("nnotes.dll")]
          public static extern STATUS NSFDbOpen(String path, ref HANDLE phDB);

          /*
           * STATUS LNPUBLIC NSFDbClose(DBHANDLE  hDB);
          */
          [DllImport("nnotes.dll")]
          public static extern STATUS NSFDbClose(HANDLE hDb);

          /*
           * STATUS LNPUBLIC NSFDbGetUnreadNoteTable2(
              DBHANDLE  hDB,
              char far *UserName,
              WORD  UserNameLength,
              BOOL  fCreateIfNotAvailable,
              BOOL  fUpdateUnread,
              HANDLE far *rethUnreadList);
          */
          [DllImport("nnotes.dll")]
          public static extern STATUS NSFDbGetUnreadNoteTable2(
              HANDLE hDb, String user, ushort namelen, bool create, bool update, ref HANDLE hList);

          /*
           * STATUS LNPUBLIC NSFDbUpdateUnread(
                  DBHANDLE  hDataDB,
                  HANDLE  hUnreadList);
           */
          [DllImport("nnotes.dll")]
          public static extern STATUS NSFDbUpdateUnread(HANDLE hDb, HANDLE hUnreadList);

          /*
           * BOOL LNPUBLIC IDIsPresent(HANDLE  hTable, DWORD  id);
          */
          [DllImport("nnotes.dll")]
          public static extern bool IDIsPresent(HANDLE hTable, DWORD id);

          /*
           * STATUS LNPUBLIC OSMemFree(HANDLE  Handle);
          */
          [DllImport("nnotes.dll")]
          public static extern STATUS OSMemFree(HANDLE h);


    Note that every call is preceded by " [DllImport("nnotes.dll")]", that's a requirement (and the DLL must be on the system PATH). There's no "#include" directive in C# to bring in header files, so you have to map all the special Notes datatypes (like DBHANDLE, NOTEID, etc.) to native C# types. I did this with "using" statements, like this:

    using HANDLE = System.UInt32;
    using DWORD  = System.UInt32;
    using STATUS = System.UInt16;

    After doing this, I just called the routines as if they were native C# code. CLR took care of mapping string buffers to "unmanaged" memory and so on. It worked great! Since this post is already too long, I won't post the class I wrote to use these CAPI calls, but it's very simple. There's an Init() routine that takes a server, db and username, and opens the database. With the username, it gets the IDTable representing the user's unread list (and updates it). Then, every time I get a new Document, I convert it's NOTEID string to a number, and see if that ID is in the unread list. On shutdown I free the IDList and close the Database (actually, I could almost certainly close the database after retrieving the IDList....)

    Not bad! The articles I read said there was a way to invoke all kinds of entry points: using structs, passing callback functions, everything (someday I'm sure I'll need to do those things too, just didn't have to this time).

    Here are the 2 links I promised:
    http://msdn.microsoft.com/en-us/magazine/cc301501.aspx
    http://msdn.microsoft.com/en-us/library/ms123402.aspx (I swear this one was there on Saturday, but today when I went to verify it, I got "content not found". Oh well, keep trying...)

    Enjoy!


    (Need expert application development architecture/coding help? Contact me at: bbalaban, gmail.com)
    Follow me on Twitter @LooseleafLLC
    This article ┬ęCopyright 2009 by Looseleaf Software LLC, all rights reserved. You may link to this page, but may not copy without prior approval.