Takenoff Labs

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

[Notes/Domino] 設計解析講座: ハンドル

今回からちょっとプログラムっぽくなってきました。まだ基本的なところではありますが、API を使ったプログラムがどういう感じになるのか、ざくっと見てみてください。

ハンドルとは

API 関数では、ほとんどの関数がハンドルの受け渡しによって処理を行います。Notes C API では、たとえば、IDテーブルハンドル、DBハンドル、ノートハンドル、メモリハンドル、といったものがあります。

ハンドルというものがなぜあるかというと、C言語が基本的に関数の集合体であるからです(たぶん)。たとえば、「DBを開く関数」がDBを開いた場合、「DBを開く関数」はそこで処理が終わってしまいますから、次に処理する関数に「開いたDBのオブジェクトの場所」を伝えなければなりません。それを伝えるのがハンドルの役目です。

ハンドルは必ず閉じること!!

ハンドルで重要なことは、「取得したハンドルは必ず閉じる」ということです。ハンドルを閉じ忘れると、メモリリークが発生したり、最悪の場合ノーツクライアントがハングアップします。API 関数でありがちなバグのひとつが、ハンドルの閉じ忘れですので、充分に注意してください。

ハンドルの閉じ忘れをなるべく減らすには、以下のいずれかを行うと便利です。

  • ハンドルをグローバル変数に置いて、Terminate で閉じる処理を行う
  • 処理をクラス化して、ハンドルをメンバ変数にし、デストラクタ(Sub Delete)で閉じる処理を行う

Terminate や クラスのデストラクタは、システムエラーが発生しようが End で終わらせようが、必ず実行されます(オブジェクトの消滅時に実行されるため。ノーツクライアントがハングアップしたときは実行されませんが、まぁそれは仕方がないということで)。以下、Terminate でグローバル変数のDBハンドル(g_hDB)を閉じるサンプルです。

Sub Terminate
    If g_hDB <> 0 Then
        Call NSFDbClose(g_hDB)
    End If
End Sub

超絶便利な NotesDocument.handle

API を使ったプログラミングは、「DBを開いてDBハンドルを得る → DBハンドルを元にノートを開き、ノートハンドルを得る → ノートハンドルを元にノートの情報を得る ……」といったように、順繰りにハンドルを得ながら処理する必要があるため、冗長になりやすいです。

ノートの情報だけを取得する場合は、NotesDocument.handle という隠しプロパティを使うと、とても便利です。NotesDocument オブジェクトを LotusScript で取得してしまえば、NotesDocument.handle を使うことによって、DBハンドルとノートハンドルの取得処理を省略できます。おまけに、NotesDocument.handle で取得したハンドルは、プログラム中で閉じる必要がありません。

とっても便利な NotesDocument.handle ですが、注意点もあります。あくまでこれは隠しプロパティですので、サポートされない、ということです。サポートされないということは、バージョンアップ時にこのプロパティが無くなってしまう可能性があるということですから、業務のシステムで使う場合は、なるべく使用を避けるべきでしょう。……ま、かくいう管理人は、業務で若干使ったことがありますが(汗

サンプルプログラム

上でごちゃごちゃ述べましたが、実際にプログラムを見たほうが理解は早いと思います。ハンドルを使ったサンプルプログラムを載せてみます。このサンプルは、データベースプロパティの「[データベースを開く] ダイアログ」にチェックが入っているかどうかを取得するものです。(ここでは個人アドレス帳を調査しています。他のDBを調べたい場合は定数の値を変えてください。)

Public Const MAXPATH = 256
Public Const WORDLEN = 65534
Public Const OS_TRANSLATE_NATIVE_TO_LMBCS = 0
Public Const OS_TRANSLATE_LMBCS_TO_NATIVE = 1

Public Const REPLFLG_DISABLE              = &H0004
Public Const REPLFLG_UNREADIFFNEW         = &H0008
Public Const REPLFLG_IGNORE_DELETES       = &H0010
Public Const REPLFLG_HIDDEN_DESIGN        = &H0020
Public Const REPLFLG_DO_NOT_CATALOG       = &H0040
Public Const REPLFLG_CUTOFF_DELETE        = &H0080
Public Const REPLFLG_NEVER_REPLICATE      = &H0100
Public Const REPLFLG_ABSTRACT             = &H0200
Public Const REPLFLG_DO_NOT_BROWSE        = &H0400
Public Const REPLFLG_NO_CHRONOS           = &H0800
Public Const REPLFLG_IGNORE_DEST_DELETES  = &H1000
Public Const REPLFLG_MULTIDB_INDEX        = &H2000
Public Const REPLFLG_PRIORITY_LOW         = &HC000
Public Const REPLFLG_PRIORITY_MED         = &H0000
Public Const REPLFLG_PRIORITY_HI          = &H4000
Public Const REPLFLG_PRIORITY_SHIFT       = 14
Public Const REPLFLG_PRIORITY_MASK        = &H0003
Public Const REPLFLG_PRIORITY_INVMASK     = &H3fff

Public Type TIMEDATE
    Innards(1) As Long
End Type

Public Type DBREPLICAINFO
    ID As TIMEDATE
    Flags As Integer
    CutoffInterval As Integer
    Cutoff As TIMEDATE
End Type

Public Const NOTESDLL = "nnotes.dll"

Declare Function NSFDbOpen Lib NOTESDLL (Byval PathName As String, rethDB As Long) As Integer

Declare Function NSFDbReplicaInfoGet Lib NOTESDLL (Byval hDB As Integer, _
retReplicationInfo As DBREPLICAINFO) As Integer

Declare Function NSFDbClose Lib NOTESDLL (Byval hDB 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 Function OSPathNetConstruct Lib NOTESDLL (Byval PortName As Long, Byval ServerName As String, _
Byval FileName As String, Byval retPathName As String) 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

Sub Initialize
    Const DB_SVR  = ""
    Const DB_PATH = "names.nsf"
    
    Dim repinfo As DBREPLICAINFO
    
    Dim strPath As String
    Dim hDb     As Long
    Dim intRet  As Integer
    
    strPath = Space$(MAXPATH)
    Call OSPathNetConstruct(Byval &h0, DB_SVR, DB_PATH, strPath)
    intRet = NSFDbOpen(APITranslate(OS_TRANSLATE_NATIVE_TO_LMBCS, strPath), hDb)
    If intRet <> 0 Then
        Msgbox APIGetError(intRet), 0, "エラー"
        Exit Sub
    End If
    
    intRet = NSFDbReplicaInfoGet(hDb, repinfo)
    If intRet <> 0 Then
        Msgbox APIGetError(intRet), 0, "エラー"
        Call NSFDbClose(hDb)
        Exit Sub
    End If
    
    If (repinfo.Flags And REPLFLG_DO_NOT_BROWSE) = REPLFLG_DO_NOT_BROWSE Then
        Msgbox "False"
    Else
        Msgbox "True"
    End If
    
    Call NSFDbClose(hDb)
End Sub

Public 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)
    
    If Instr(strOut, Chr(0)) = 0 Then
        APITranslate = Trim(strOut)
    Else
        APITranslate = Left$(strOut, Instr(strOut, Chr(0)) -1)
    End If
End Function

Public 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

※ 今回からスクリプトに色をつけてみました。使用したのは nsfl10n さんのエントリで知った、Format Your LotusScript というツールです。今まで使うのが面倒で避けていましたが、さすがにこれだけ長いプログラムを色なしだとかなり見づらかったので。今後、ちょっとずつ過去のエントリもカラーリングしていこうかなと思っています。

解説

赤字のところを見てもらえばわかるとおり、NSFDbOpen でデータベースハンドル(hDb)を取得し、NSFDbReplicaInfoGet にハンドルを渡して複製情報を得て、NSFDbClose でハンドルを閉じています。ハンドルは、おおむねこのような使われ方をします。(まあそれにしても、複製情報のところにデータベースプロパティのフラグがある、っていうのはどうなんでしょう(^^; )

ハンドル以外のところも少し解説しましょう。

OSPathNetConstruct は、サーバー名とファイルパスを NSFDbOpen に渡すための文字列に直す関数です。API 的には「サーバー名!!ファイルパス」という書式が使用されるので、この文字列を作っています。自分で作ってもいいですが、まあお作法としてこの関数を使うようにしましょう。ちなみに、第1引数は PortName ですが、こんなの使うことはほぼ無いので、「0」(=NULL)で OK です。また、パスにはダブルバイトが使用されることもありますから、OSPathNetConstruct から得た文字列を LMBCS に変換する必要がある点にも注意してください。

「If (repinfo.Flags And REPLFLG_DO_NOT_BROWSE) = REPLFLG_DO_NOT_BROWSE Then」の箇所は、「REPLFLG_DO_NOT_BROWSE」 = &H0400 = &B0000010000000000 というビットが立っているかどうかを調べています。「If (repinfo.Flags And REPLFLG_DO_NOT_BROWSE) <> 0 Then」でもかまいません。この意味がわからない方は、ビット演算について再度学習してください。API ではビット演算がよく出てきますが、この講座ではビット演算の解説までは行わない予定です(細かいことを挙げるとキリがないので)。

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

トラックバック

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

コメント

😉
Good
勉強になる。

雷さん、コメントありがとうございます! 😀
この連載で反響があったのは初めてなので、とってもうれしいです! 😉

実際、私が去年からこのBlogを注目になった、でも、私の日本語はちょっと上手ではないので、一度もコメントしなかった。

ここから本当にいろいろな経験と技法を勉強しました。
😆

雷さん、中国の方なのですね!
日本語で読み書きできるなんて、本当に頭が下がります。 😯

コメントしていただく際は、中国語か英語でも大丈夫ですよ。
中国語は大学でちょっとかじったので、読むだけならなんとかできます。
作文は全くできませんが(涙

毎回参考にさせて頂いていますが、いくつか疑問点がございましたので質問します。

>OSPathNetConstruct から得た文字列を LMBCS に変換する必要がある
とありますが、

>intRet = NSFDbOpen(APITranslate(OS_TRANSLATE_LMBCS_TO_NATIVE, strPath), hDb)
ではOS_TRANSLATE_LMBCS_TO_NATIVEとなっております、どちらが正しいのでしょうか。

また、APITranslateではNullを削除していますが、NSFDbOpenのPathNameを調べたら
「The string must be null-terminated.」
と記載されておりました。
LotusScriptから値を渡す場合はNullで終わっていなくても問題ないという事でしょうか。

最後に、
>第1引数は PortName ですが、こんなの使うことはほぼ無いので、「0」(=NULL)で OK です
とありますが、もし使う事があるとしたらどのような場合でしょうか。
(これが原因でエラーになる事がある?)

質問ばかりで申し訳ございません。

黒さん、コメントありがとうございます! 😀
また、回答が遅くなってしまいまして申し訳ありませんでした(_ _;;;

> >intRet = NSFDbOpen(APITranslate(OS_TRANSLATE_LMBCS_TO_NATIVE, strPath), hDb)
> ではOS_TRANSLATE_LMBCS_TO_NATIVEとなっております、どちらが正しいのでしょうか。

あああああああ、すみません、完全に間違いです(_ _;;;
OS_TRANSLATE_NATIVE_TO_LMBCS が正しいです。

大変失礼いたしました。
記事のほうも修正させていただきました。

> LotusScriptから値を渡す場合はNullで終わっていなくても問題ないという事でしょうか。

Declare で Byval 指定されていると、NULLで終わる文字列へのポインタ
という扱いになるのかな、と思います。参考ページ
LotusScript の String 型は、(少なくとも見た目は)最後にNull文字が
付かないようですが、C関数に渡すときは、Nullで終わるように振る舞う
ようですので、あえてNull文字を追加する必要はなさそうです。

> >第1引数は PortName ですが、こんなの使うことはほぼ無いので、「0」(=NULL)で OK です
> とありますが、もし使う事があるとしたらどのような場合でしょうか。
> (これが原因でエラーになる事がある?)

OSPathNetConstruct は、「PortName!!!ServerName!!Filepath」という
文字列を作るだけの関数です。
流儀として使っているだけで、本当は LotusScript だけで作っても
かまいません。
日本語が含まれないパスであれば、全部すっ飛ばして
NSFDbOpen(“HogeServer!!names.nsf”, hDb) と書いても大丈夫です。

PortName は、おそらくポート名を明示的に指定したい場合に指定する
のかなと思います。
この記事のコードでは、Declare で Long 型にして潰していますが、
String 型にすれば使えます。
たとえば、”TCP”を渡せば、「TCP!!!HogeServer!!names.nsf」のような
文字列が返ります。

が、わざわざポート名を指定したいことは、ほとんどないと思いますので、
0にしておけばいいと思います。
これが原因でエラーになることは、おそらくないと思います。

OSPathNetConstructを知りませんでしたので、信頼出来る他のサーバーに設置されているノーツDBの未読文書へのアクセス方法が解からず困っていました。環境は古い6.5です。
この事例を参考にOSPathNetConstructを介しましたら、あっさり他のサーバーにアクセスできて助かりました。
有用な情報、ありがとうございました。

booskanium さん、コメントありがとうございます! 😀

お役に立てたなら、執筆者として大変うれしいです。
今後も有用な記事が書けるよう、がんばります。
(って年単位でサボってますが(汗))

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





(以下のタグが使えます)
<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 7889 to the field below:

^
×