[Notes/Domino] 設計解析講座: アイテムの値の取得~$FILE の解析
2ヶ月もサボってしまいスミマセン。今回は、アイテムの値を取得する方法について説明します。
基本型の場合
文字や数値など、基本的な型のアイテムから値を取得するには、NSFItemGet~ で始まる関数を使うと簡単です。たとえば、文字型の場合なら NSFItemGetText(文字リストは別の関数になります)、数値型なら NSFItemGetNumber (or NSFItemGetLong )といった具合です。
C でプログラムを書く場合は、上記の関数が必要となりますが、今回は LotusScript を使いますので、これらの関数は基本的に使う必要はありません。「doc.アイテム名(0)」といった通常の取得方法で大丈夫です。なので、説明は省略します。
その他の種類の場合
基本型以外にどのようなアイテムの種類があるかについては、前回のエントリを参照してください。これらのアイテムの値を解析するには、メモリの値を直接参照する必要があります。
メモリの値を参照する、というと難しく感じられるかもしれませんが、アドレスの概念さえわかっていれば、それほど難しいものではありません。以下、比較的構造が単純な $FILE をサンプルとして、説明してみます。
$FILE の構造
$FILE は、添付ファイル1つにつき、1アイテム作成されます。添付ファイルの実体は $FILE とは別のところにあり、$FILE にはその実体のオブジェクトID、圧縮形式、ファイル名、作成日時・更新日時などが格納されています。
$FILE の中身は、基本的に FILEOBJECT 構造体だけです。FILEOBJECT 構造体(+ファイル名)の後に、16バイトのデータが続くことがありますが、これは何なのかよくわかりません……(x_x)。まあ無視しちゃってよいと思いますが。
FILEOBJECT 構造体は以下のように定義されています。
typedef struct { OBJECT_DESCRIPTOR Header; /* object header */ WORD FileNameLength; /* length of file name */ WORD HostType; /* text delimiters (HOST_xxx) */ WORD CompressionType; /* compression used (COMPRESS_xxx) */ WORD FileAttributes; /* orig. file attribs (ATTRIB_xxx) */ WORD Flags; /* misc. flags (FILEFLAG_xxx) */ DWORD FileSize; /* orig. file size */ TIMEDATE FileCreated; /* orig. file date/time of creation */ TIMEDATE FileModified; /* orig. file date/time of mod. */ /* Now comes the file name... It is the original RELATIVE file path with no device specifiers */ } FILEOBJECT;
構造体の中には、構造体の後に可変長の文字列などのデータが付随することがあります。FILEOBJECT もそのような構造体の1つで、FileNameLength の長さ分だけ、ファイル名の文字列が後に続きます。APIドキュメントには、上記のように「/* Now comes the file name...」といった説明が書いてありますので、このような説明を見逃さないようにしましょう。
FILEOBJECT には、OBJECT_DESCRIPTOR 構造体のメンバが最初にあります。この構造体の定義は以下のようになっています。
typedef struct { WORD ObjectType; /* Type of object (OBJECT_xxx) */ DWORD RRV; /*Object ID of the object in THIS FILE */ } OBJECT_DESCRIPTOR;
これで、メンバの型は WORD、DWORD、TIMEDATE と、今までの連載で説明したものになりました(わからない人は、過去の連載を読み返してみてくださいね)。
では、「test.txt」という添付ファイルの $FILE をバイナリダンプしたデータを見てみましょう。以下は、最初の2バイト(=アイテムの種類)を除いた $FILE のバイナリダンプです(バイトオーダー=リトルエンディアン)。
00 00 4E 06 00 00 08 00 00 00 ..N....... 00 00 00 00 03 00 E1 C1 03 00 .......... AB B2 48 00 10 79 25 49 76 5C ..H..y%Iv\ 49 00 10 79 25 49 74 65 73 74 I..y%Itest 2E 74 78 74 40 1F 6E D3 FD CF .txt@.n... 84 3F D7 DB DB 14 FF 9F 85 55 .?.......U
青色が FILEOBJECT 構造体、赤色が FILEOBJECT 構造体に続くファイル名の文字列、灰色がよくわからない(^^;)16バイトのデータです。
最初のメンバ OBJECT_DESCRIPTOR.ObjectType は WORD 型ですから、最初の2バイト「0000」(つまり「0」)が ObjectType の値だとわかります。OBJECT_DESCRIPTOR.RRV は DWORD 型なので、次の4バイト「0000064E」が RRV (オブジェクトID)の値ということになります。
その次の FileNameLength は WORD 型なので、次の2バイトを取ると「0008」になります。つまり、ファイル名の長さが8バイトということで、FILEOBJECT の後の8バイト分のデータを見ると、たしかにファイル名(赤色のところ)になっていることがわかるかと思います。
$FILE の解析
プログラム的にデータを解析するには、ちょうど上述したようにデータを決められたバイト数分取得していく処理になります。以下、ノートの $FILE アイテムのデータを取得するサンプルです。(解析するのは、オブジェクトID、ファイル名、ファイルサイズ、作成日時・更新日時 だけです。他はあまり有用そうではないのと、説明をシンプルにするために、省略しています。)
Const WORDLEN = 65534 Const OS_TRANSLATE_NATIVE_TO_LMBCS = 0 Const OS_TRANSLATE_LMBCS_TO_NATIVE = 1 Const NOTESDLL = "nnotes.dll" Type BLOCKID hPool As Long Block As Integer End Type Type TIMEDATE Innards(1) As Long End Type Declare Function NSFItemInfo Lib NOTESDLL (Byval hNote As Long, Byval ItemName As Any, _ Byval NameLen As Integer, ItemBlockid As BLOCKID, ValueDatatype As Integer, _ ValueBlockid As BLOCKID, ValueLen As Long) As Integer Declare Function NSFItemInfoNext Lib NOTESDLL (Byval hNote As Long, Byval hPool As Long, _ Byval Block As Integer, Byval ItemName As Any, Byval NameLen As Integer, ItemBlockid As BLOCKID, _ ValueDatatype As Integer, ValueBlockid As BLOCKID, ValueLen As Long) As Integer Declare Function ConvertTIMEDATEToText Lib NOTESDLL (Byval IntlFormat As Integer, _ Byval TimeFormat As Integer, InputTime As TIMEDATE, Byval retTextBuffer As String, _ Byval TextBufferLength As Integer, retTextLength As Integer) As Integer Declare Function OSLockObject Lib NOTESDLL (Byval hHandle As Long) As Long Declare Function OSUnlockObject Lib NOTESDLL (Byval hHandle As Long) As Integer Declare Function OSTranslate Lib NOTESDLL (Byval TranslateMode As Integer, _ Byval InData As String, Byval InLength As Long, Byval OutData As String, _ Byval OutLength As Long) As Integer Declare Function OSLoadString Lib NOTESDLL (Byval hModule As Long, Byval StringCode As Long, _ Byval retBuffer As String, Byval BufferLength As Integer) As Integer Declare Sub RtlMoveMemoryString Lib "kernel32" Alias "RtlMoveMemory" (Byval hpvDest As String, _ Byval hpvSource As Long, Byval cbCopy As Long) Declare Sub RtlMoveMemory Lib "kernel32" Alias "RtlMoveMemory" (hpvDest As Any, Byval hpvSource As Long, _ Byval cbCopy As Long) Sub Initialize Dim ss As New NotesSession Dim db As NotesDatabase Dim doc As NotesDocument Dim ItemBlockid As BLOCKID Dim ValueBlockid As BLOCKID Dim lngLen As Long Dim lngAddr As Long Dim intDataType As Integer Dim intRet As Integer Dim intObjType As Integer Dim lngRRV As Long Dim intFnLen As Integer Dim intHostType As Integer Dim intCmpType As Integer Dim intAttr As Integer Dim intFlags As Integer Dim lngFileSize As Long Dim tdCreated As TIMEDATE Dim tdModified As TIMEDATE Dim strFileName As String Set db = ss.CurrentDatabase Set doc = db.GetDocumentByID("{添付ファイルがある文書のノートID}") If doc Is Nothing Then Msgbox "文書が見つかりません", 0, "エラー" Exit Sub End If intRet = NSFItemInfo(doc.handle, "$FILE", Lenbp("$FILE"), ItemBlockid, intDataType, ValueBlockid, lngLen) If intRet <> 0 Then Msgbox APIGetError(intRet), 0, "エラー" Exit Sub End If Do 'メモリをロック lngAddr = OSLockObject(ValueBlockid.hPool) + Val("&B" & Right(Bin$(ValueBlockid.Block), 16) & "&") lngAddr = lngAddr + 2 '最初の2バイトはアイテムタイプなので読み飛ばす '構造体データを読み込む Call APIMoveMemory(intObjType, lngAddr, 2) 'WORD OBJECT_DESCRIPTOR.ObjectType Call APIMoveMemory(lngRRV, lngAddr, 4) 'DWORD OBJECT_DESCRIPTOR.RRV Call APIMoveMemory(intFnLen, lngAddr, 2) 'WORD FileNameLength Call APIMoveMemory(intHostType, lngAddr, 2) 'WORD HostType Call APIMoveMemory(intCmpType, lngAddr, 2) 'WORD CompressionType Call APIMoveMemory(intAttr, lngAddr, 2) 'WORD FileAttributes Call APIMoveMemory(intFlags, lngAddr, 2) 'WORD Flags Call APIMoveMemory(lngFileSize, lngAddr, 4) 'DWORD FileSize Call APIMoveMemory(tdCreated.Innards(0), lngAddr, 4) 'TIMEDATE.Innards(0) FileCreated Call APIMoveMemory(tdCreated.Innards(1), lngAddr, 4) 'TIMEDATE.Innards(1) FileCreated Call APIMoveMemory(tdModified.Innards(0), lngAddr, 4) 'TIMEDATE.Innards(0) FileModified Call APIMoveMemory(tdModified.Innards(1), lngAddr, 4) 'TIMEDATE.Innards(1) FileModified 'ファイル名を読み込む Call APIMoveMemoryString(strFileName, lngAddr, intFnLen) strFileName = APITranslate(OS_TRANSLATE_LMBCS_TO_NATIVE, strFileName) '表示 Msgbox _ "オブジェクトのID: " & Right("00000000" & Hex$(lngRRV), 8) & Chr(10) & _ "サイズ: " & Format$(lngFileSize, "#,###") & Chr(10) & _ "ファイル名: " & strFileName & Chr(10) & _ "作成日: " & APITIMEDATE2String(tdCreated) & Chr(10) & _ "更新日: " & APITIMEDATE2String(tdModified) 'メモリロックを解除 Call OSUnlockObject(ValueBlockid.hPool) Loop While NSFItemInfoNext(doc.handle, ItemBlockid.hPool, ItemBlockid.Block, "$FILE", Lenbp("$FILE"), _ ItemBlockid, intDataType, ValueBlockid, lngLen) = 0 End Sub Function APITranslate(Byval intMode As Integer, Byval strBuf As String) As String Dim strOut As String strOut = Space$(WORDLEN) Call OSTranslate(intMode , strBuf, Lenbp(strBuf) - 1, strOut, WORDLEN - 1) APITranslate = Strleft(strOut & Chr(0), Chr(0)) End Function Function APIGetError(Byval intErrCode As Integer) As String Dim strIn As String Dim strOut As String Dim intCode As Integer strIn = Space$(255) strOut = Space$(255) intCode = intErrCode And &h3FFF Call OSLoadString(0, intCode, strIn, Lenbp(strIn) - 1) strOut = APITranslate(OS_TRANSLATE_LMBCS_TO_NATIVE, strIn) APIGetError = Trim(strOut) End Function Function APITIMEDATE2String(td As TIMEDATE) As String Dim s As String * 81 Dim i As Integer Call ConvertTIMEDATEToText(0, 0, td, s, 80, i) APITIMEDATE2String = Strleft(s & Chr(0), Chr(0)) End Function Sub APIMoveMemory(varVal As Variant, lngAddr As Long, Byval lngLen As Long) Call RtlMoveMemory(varVal, lngAddr, lngLen) lngAddr = lngAddr + lngLen End Sub Sub APIMoveMemoryString(strVal As String, lngAddr As Long, Byval lngLen As Long) strVal = Space$(lngLen + 1) Call RtlMoveMemoryString(strVal, lngAddr, lngLen) lngAddr = lngAddr + lngLen End Sub
NSFItemInfo、NSFItemInfoNext のループは、過去に説明した「アイテムの取得、アイテムヘッダ」を参照してください。今回重要なのは、以下の部分です。
lngAddr = OSLockObject(ValueBlockid.hPool) + Val("&B" & Right(Bin$(ValueBlockid.Block), 16) & "&") lngAddr = lngAddr + 2 '最初の2バイトはアイテムタイプなので読み飛ばす '構造体データを読み込む Call APIMoveMemory(intObjType, lngAddr, 2) 'WORD OBJECT_DESCRIPTOR.ObjectType Call APIMoveMemory(lngRRV, lngAddr, 4) 'DWORD OBJECT_DESCRIPTOR.RRV Call APIMoveMemory(intFnLen, lngAddr, 2) 'WORD FileNameLength Call APIMoveMemory(intHostType, lngAddr, 2) 'WORD HostType Call APIMoveMemory(intCmpType, lngAddr, 2) 'WORD CompressionType Call APIMoveMemory(intAttr, lngAddr, 2) 'WORD FileAttributes Call APIMoveMemory(intFlags, lngAddr, 2) 'WORD Flags Call APIMoveMemory(lngFileSize, lngAddr, 4) 'DWORD FileSize Call APIMoveMemory(tdCreated.Innards(0), lngAddr, 4) 'TIMEDATE.Innards(0) FileCreated Call APIMoveMemory(tdCreated.Innards(1), lngAddr, 4) 'TIMEDATE.Innards(1) FileCreated Call APIMoveMemory(tdModified.Innards(0), lngAddr, 4) 'TIMEDATE.Innards(0) FileModified Call APIMoveMemory(tdModified.Innards(1), lngAddr, 4) 'TIMEDATE.Innards(1) FileModified 'ファイル名を読み込む Call APIMoveMemoryString(strFileName, lngAddr, intFnLen) strFileName = APITranslate(OS_TRANSLATE_LMBCS_TO_NATIVE, strFileName) '(略) 'メモリロックを解除 Call OSUnlockObject(ValueBlockid.hPool)
まず、ValueBlockid.hPool のハンドルを OSLockObject 関数によって上書きされないようロックし、ValueBlockid.Block の値を足します。これが、アイテムの「値」部分のデータがあるメモリの、先頭部分のアドレスになります。
「Val("&B" & Right(Bin$(ValueBlockid.Block), 16) & "&")」という処理が入っていますが、これは ValueBlockid.Block がマイナス値になるのを防ぐための、念のための処理です。もしかするといらないかもしれません(^^;
ちなみに、OSLockObject 関数によって返されるメモリアドレスがマイナス値であっても、それは問題ありません(たとえば、「FFFFFFFF」は変数のサイズによって「4,294,967,295」とも「-1」とも解釈されますが、「FFFFFFFF」には違いないので)。
OSLockObject 関数によってロックされたメモリは、OSUnlockObject によってロックを解除する必要があります。これは必ず忘れないようにしてください。
次に、返されたメモリアドレスのデータを、Windows API の RtlMoveMemory 関数(APIMoveMemory、APIMoveMemoryString 関数でラッピングしています)で読み取っていきます。これは、コードを見ていただければ、おわかりいただけるかと思いますが、lngAddr のアドレスから○バイト取得し、その分 lngAddr をインクリメントする、ということを繰り返しているだけです。
この部分、本来は ODSReadMemory で読み取るのが望ましいのですが……なぜかうまくいかなかったので、このようにしています。なんか ODSReadMemory って構造体によってうまくいく場合といかない場合があるみたいで、よくわかんないです……(x_x)。というわけで、Windows 以外の環境のみなさま、ゴメンナサイ。
今回、intHostType など、解析には使用していないものについても変数を用意しましたが、必要なければ、「lngAddr = lngAddr + 2」のようにアドレスをインクリメントするだけにして読み飛ばしてください。
次回は、いよいよ(というかやっと)本連載のメインイベント(?)、リッチテキストの解析に入ります。乞うご期待!
コメント
コメントはありません
※コメントは承認制となっております。管理者が承認するまで表示されません。申し訳ありませんが、投稿が表示されるまでしばらくお待ちください。