[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 ではビット演算がよく出てきますが、この講座ではビット演算の解説までは行わない予定です(細かいことを挙げるとキリがないので)。
コメント
😉
Good
勉強になる。
Posted at 2010/10/30 11:57 PM by 雷
雷さん、コメントありがとうございます! 😀
この連載で反響があったのは初めてなので、とってもうれしいです! 😉
Posted at 2010/10/31 1:09 PM by takenoff
実際、私が去年からこのBlogを注目になった、でも、私の日本語はちょっと上手ではないので、一度もコメントしなかった。
ここから本当にいろいろな経験と技法を勉強しました。
😆
Posted at 2010/11/20 7:12 PM by 雷
雷さん、中国の方なのですね!
日本語で読み書きできるなんて、本当に頭が下がります。 😯
コメントしていただく際は、中国語か英語でも大丈夫ですよ。
中国語は大学でちょっとかじったので、読むだけならなんとかできます。
作文は全くできませんが(涙
Posted at 2010/11/20 9:29 PM by takenoff
毎回参考にさせて頂いていますが、いくつか疑問点がございましたので質問します。
>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 です
とありますが、もし使う事があるとしたらどのような場合でしょうか。
(これが原因でエラーになる事がある?)
質問ばかりで申し訳ございません。
Posted at 2014/06/19 8:00 PM by 黒
黒さん、コメントありがとうございます! 😀
また、回答が遅くなってしまいまして申し訳ありませんでした(_ _;;;
> >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にしておけばいいと思います。
これが原因でエラーになることは、おそらくないと思います。
Posted at 2014/06/21 11:42 PM by takenoff
OSPathNetConstructを知りませんでしたので、信頼出来る他のサーバーに設置されているノーツDBの未読文書へのアクセス方法が解からず困っていました。環境は古い6.5です。
この事例を参考にOSPathNetConstructを介しましたら、あっさり他のサーバーにアクセスできて助かりました。
有用な情報、ありがとうございました。
Posted at 2014/07/18 3:48 PM by booskanium
booskanium さん、コメントありがとうございます! 😀
お役に立てたなら、執筆者として大変うれしいです。
今後も有用な記事が書けるよう、がんばります。
(って年単位でサボってますが(汗))
Posted at 2014/07/18 11:32 PM by takenoff
※コメントは承認制となっております。管理者が承認するまで表示されません。申し訳ありませんが、投稿が表示されるまでしばらくお待ちください。