[Notes/Domino] 設計解析講座: データ型と構造体
今回も引き続き基本事項ですが、マイナス値については上級者でも間違いやすいところですので、一読してみてください。
データ型
API のデータ型のうち、よく使われる基本的なものには、以下のものがあります。対応する LotusScript の型をよく覚えておいてください。
API | LotusScript | サイズ |
---|---|---|
BYTE | Byte | 1バイト |
WORD | Integer | 2バイト |
DWORD | Long | 4バイト |
QWORD | Currency | 8バイト |
これ以外については、リファレンスで確認してみてください。
たとえば、STATUS を調べてみると、
typedef WORD STATUS;
となっていますので、WORD のことであることがわかります。
マイナス値に注意!!
さらにリファレンスで WORD を確認してみると、Description に「Unsigned 16-bit integer」と記載されています。訳すと「符号なし16ビット整数」となります。8ビット=1バイトですから、「16ビット」は「2バイト」、2バイト整数は LotusScript では Integer ですから、Integer が対応する型であることがわかりますね。
「符号なし」はマイナス値にならない、ということです。「符号あり」の場合、つまりマイナス値になることがある型の場合は、「Signed」と表記されているはずです。(例: SWORD)
先ほど WORD=Integer としましたが、Integer はマイナス値になることがありますから、「符号あり」の型です。とすると、WORD の値を Integer に代入するとまずいことが起こりそうです。
例を挙げてみましょう。
たとえば、WORD 型の変数に &HFFFF (=65535)の値が入っている場合、これを Integer 型の変数に代入したら、「-1」と解釈されます。
変数に入っている値は、あくまでも 0xFFFF です。しかし、同じ 0xFFFF でも、WORD では 65535 ですし、LotusScript の Integer では -1 と解釈するのです。
なぜこのようなことが起きるかというと、符号あり変数は、最上位ビットが1の場合、マイナス値として解釈するためです。いったんマイナスとして解釈されたものは、いくら大きな型に変換しても、マイナスとして解釈されます(ま、そりゃそうか)。
いったんマイナスになってしまったものを正常な値に戻すには、マイナスの場合のみ 2の16乗(BYTEの場合は8乗、DWORDの場合は32乗)を足すか、以下のようにして、上位の型の変数に代入します。
[Byte→Integerの場合] Val("&H" & Right(Hex(変数), 2 ) & "%") [Integer→Longの場合] Val("&H" & Right(Hex(変数), 4 ) & "&") [Long→Currencyの場合] Val("&H" & Right(Hex(変数), 8 ) & "@")
また、こうなってしまう前に、最初から大きいほうの型にしておく、という手段も有効です。関数から値が戻ってくるものであれば、Declare で hoge As Integer とするところを、hoge As Long としてもかまいません。ただし、構造体ではサイズが違うものはNGです。
上記のことをわかっているつもりでも、いざコーディングするとうっかり忘れてしまうことが多いです。また、大きな値でしか不具合が起きないので、テストで発見されない隠れたバグになりがちです。ネット上で公開されているコードも、この「マイナス値バグ」が含まれているものをよく見かけます。かく言う管理人も、何度このバグを出したことか(涙
API ではマイナス値が登場することは少ないです。API 関数から返ってきた値は、常に「マイナスになることがあるか?」を確認するようにしてください。
構造体
Cで言うところの「構造体」は、LotusScript では「ユーザー定義型」と言うそうです。でも「ユーザー定義型」という用語が好きじゃないので、この講座では「構造体」で統一します。
構造体の定義の仕方は、前回紹介したとおりです。もう一度載せておきます。
typedef struct { DHANDLE pool; /* pool handle */ BLOCK block; /* block handle */ } BLOCKID;
↓
Type BLOCKID hPool As Long Block As Integer End Type
R5 のときは、Byte型が無かったため、BYTE を定義するのに四苦八苦しました(Integerにしておいて1バイトずつに分割するとか(x_x))。またR5 では、ちょっと複雑な構造体を渡すと、むちゃくちゃな値が返ってくる、というとんでもない問題があり、これがトラウマとなって管理人はメンバごとにメモリから RtlMoveMemory で値を取得するという方法をよくやります。
たとえば CDACTION 構造体の場合、
typedef struct { LSIG Header; /* Signature and Length */ WORD Type; /* Type of action (formula, script, etc.) */ WORD IconIndex; /* Index into array of icons */ DWORD Flags; /* Action flags */ WORD TitleLen; /* Length (in bytes) of action's title */ WORD FormulaLen;/* Length (in bytes) of "hide when" formula */ DWORD ShareId; /* Share ID of the Shared Action */ /* Variable portion of the record: TitleLen bytes of action's title Action data: (Header.Length - TitleLen - FormulaLen) bytes of formula, script, etc. FormulaLen bytes of "hide when" formula */ } CDACTION;
以下のようにすることがあります。(ものすごいはしょってますが。)
Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" _ (hpvDest As Any, Byval hpvSource As Long, Byval cbCopy As Long) Sub MoveMem(varVar As Variant, lngAddr As Long, Byval lngLen As Long) Call MoveMemory(varVar, lngAddr, lngLen) lngAddr = lngAddr + lngLen End Sub Sub ReadCDAction(Byval lngAddr As Long) '(前略) Call MoveMem(intSig, lngAddr, 2) Call MoveMem(lngLen, lngAddr, 4) Call MoveMem(intType, lngAddr, 2) Call MoveMem(intIconIndex, lngAddr, 2) Call MoveMem(lngFlags, lngAddr, 4) Call MoveMem(lngTitleLen, lngAddr, 2) Call MoveMem(lngFormulaLen, lngAddr, 2) Call MoveMem(lngShareId, lngAddr, 4) '(後略) End Sub
これはあきらかにオーバーヘッドがあり速度的に不利ですが、以下のようなメリットもあります。
- Declarations が構造体で埋め尽くされずに済む。 (設計解析をするとやたらと構造体がでてくるので、律儀に Type で宣言すると、Declarations がものすごいサイズになることがあるので。)
- 前述のマイナス値問題を解決しやすい。(一気に大きな型の変数に入れられる。)
ものすごくパフォーマンスを求められていなければ、最近のマシンであればそれほど遅いわけでもないので、以降の連載で上記のようなコードを多用します。パフォーマンス重視の方は、構造体を使用する方法に読み替えてください。
コメント
コメントはありません
※コメントは承認制となっております。管理者が承認するまで表示されません。申し訳ありませんが、投稿が表示されるまでしばらくお待ちください。