Takenoff Labs

Lotus Notes/Domino に関する Tips や、クラシックの名曲などを紹介します

[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」のようにアドレスをインクリメントするだけにして読み飛ばしてください。


次回は、いよいよ(というかやっと)本連載のメインイベント(?)、リッチテキストの解析に入ります。乞うご期待!

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
読み込み中...

トラックバック

トラックバックはありません

コメント

コメントはありません

※コメントは承認制となっております。管理者が承認するまで表示されません。申し訳ありませんが、投稿が表示されるまでしばらくお待ちください。





(以下のタグが使えます)
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

For spam filtering purposes, please copy the number 6262 to the field below:

^
×