NSFItemInfoNext関数を64bit Windows Dominoサーバで使用するとクラッシュする

追記 2019-11-24

  • 記事のリンクを追加しました。
  • ソースコードにcpp指定をしました。

本編

私が、とあるプロジェクトでDominoサーバのアドインを作ったとき、奇妙な現象に悩まされた。

その現象というのは、NSFItemInfoNextというNotes C APIの関数を使うと、32bit WindowsのNotesクライアントとDominoサーバ、64bitのMacOS、32bit Linuxではまったく問題がないのに、64bit WindowsのDominoサーバでのみクラッシュするという事象である。

そもそも「NSFItemInfoNext」とは何なのか。

NSFItemInfoNext関数は、通常NSFItemInfo関数とペアで使用する。「NSFItem○○」という関数はアイテム(フィールドと同義)に関する操作を行う種類のもので、NSFItemInfoはアイテムの情報を取得するのに使用する。アイテムは、名前、型、値、属性の4つで構成されていて、NSFItemInfoは属性以外の情報を取得できる。アイテムは文書中名前(フィールド名)で区別されるため、通常1文書に1つだが、1アイテム中に保持できる値の大きさが限られているため、同名アイテムが複数保管されることがある。その時、2つめ以降の同名アイテムを取得するのに使われるのが、NSFItemInfoNext関数というわけである。

例えば、次のようにコーディングになる。

STATUS error = NSFItemInfo(hNote, "Body", strlen("Body"), &bItem, &wType, &bValue, &dwLength);
if (ERR(error) != ERR_ITEM_NOT_FOUND) {
  if (error) return;
  bPrevItem = bItem;
  error = NSFItemInfoNext(hNote, bPrevItem, "Body", strlen("Body"), &bItem, &wType, &bValue, &dwLength);
}

バイト数が決まっている数値型(NUMBER)、日時型(TIMEDATE)やシンプルなテキストデータくらいであれば、同名アイテムを2つ以上に分けて保存することはめったにないが、64キロバイトを超えるデータは文書に保存できないため、同名アイテム保存を使用する。Notesクライアントは、約40キロバイトを目安に同名アイテムを2つ以上に分けて保存する。

このように、NSFItemInfoNext関数は何か特殊な関数というわけではなく、APIとしては極めてベーシックな存在の関数なのだが、それだけにDominoサーバx64Winでクラッシュし、その原因がNSFItemInfoNextだとわかった時は、首をひねるばかりだった。

Notes C APIを使用したコードをコンパイルする場合、Windows 32ビットでは、少なくとも次の3つを指定する。

-DW -DW32 -DNT

Windows 64ビットでは、これらに加えて、以下の3つも必要になる。

-DW -DW32 -DNT -DW64 -DND64 -D_AMD64_

これら以外に識別子の過不足がないか調べてみたが、特に問題ない。

ネット上で調べているうちに、興味深い記述を見つけた。NSFItemInfoNext関数は以下のような引数を取る。

STATUS LNPUBLIC NSFItemInfoNext (
  NOTEHANDLE hNote,
  BLOCKID NextItem,
  const char far *Name,
  WORD NameLength,
  BLOCKID far *retbhItem,
  WORD far *retDataType,
  BLOCKID far *retbhValue,
  DWORD far *retValueLength
);

その記事では、この関数をLotusScript内から呼び出した時に、BLOCKID型の引数を倍精度小数点数(double型)の幅8バイトで指定する必要があるということだった。

C APIの世界では、BLOCKIDはプールハンドル(4バイト)とブロックハンドル(2バイト)の2つの値を持つ6バイト構造体だ。これを8バイトにするには、2バイトのブロックハンドルを4バイトにすることになる。

Notes C APIでブロックハンドルを定義しているのは、global.hヘッダーファイルだ。

// global.hの1076行目
typedef WORD BLOCK;

これを次のように書き換える。

#ifdef W64
typedef DWORD BLOCK;
#else
typedef  WORD BLOCK;
#endif

こうすることで、NSFItemInfoNext関数はクラッシュしなくなり、 BLOCKIDを使用するほかの関数も問題なく動作している(ように見える)。この手法が他の関数に本当に影響はないのか予断を許さないが、64bit WindowsのDominoサーバでクラッシュに困ったら、NSFItemInfoNext関数に起因していないか調べ、上記方法を試してみるのもよいかもしれない。

この修正による影響事案

この記事で提案した修正について、私自身以下の事案に遭遇しました。よろしければこちらもご参照下さい。

chiburusystems.hatenablog.com