@Left/@Right のEmulate

2008年10月31日 03:03

実際に必要になってから書いても大したコードでも無いのですが、LotusScriptって文字列処理がイマイチなので、@Leftや@Rightの処理がLotusScriptで使いたくなることがあります。
 Evaluateを使えばいいじゃん?て声もありそうですが僕は以下のようなコードを使います。

これは実は僕が考えたコードでなくて、R5時代にSetup.nsfに含まれていたコードを自分のローカルに取っておいたものです。なのでブログのネタにしやすいな、て思ったのです。確かNotes/Domino Magazineでもこの話は取り上げられていました。
テンプレートのコードはなぜかRightStrに初歩的なバグがあったのですが、そこは直してあるので使えると思います。


Function LeftStr(sString As String, sKey As String) As String
%REM
この関数は@left 関数のように動作します。
キーが見つからなかったら "" を返します。
%END REM

Dim nPos As Integer
nPos = Instr(sString,sKey)
If (nPos > 0) Then
LeftStr = Left$(sString,npos-1)
Else
LeftStr = ""
End If
End Function

Function RightStr(sString As String, sKey As String) As String
%REM
この関数は @Right のように動作します。
キーにある文字が見つからなかったら""を返します。
%END REM

Dim nPos As Integer
nPos = Instr(sString, sKey)
If (nPos = 0) Then
RightStr = ""
Else
RightStr = Right$(sString, Len(sString)-nPos-Len(sKey)+1)
End If
End Function


プロファイリングを使用したエージェントのボトルネック調査

2008年11月10日 00:43

 エージェントのパフォーマンス分析をするときにどんな方法を使っているでしょうか?
単純にサーバーのログを調べたり、NotesTimerクラスを使ったり色々なアプローチをしている方がおられるのではないかと思います。

今日は、プロファイリングを使用したエージェントのパフォーマンス分析の方法について紹介します。
エージェントのプロファイリングはLotus Domino 7.x 以降でサポートされた機能なのですが、バックグラウンドエージェントでもローカルのエージェントでも使用でき、使い方も簡単なので非常に便利です。
 結論から言うとプロファイリングは、エージェントのプロパティでプロファイルを有効にして、実行後に"プロファイル結果の表示"を選択するとエージェントの分析結果が表示される」ので非常に簡単に使用できるのですが、ディスカッションデータベースを使用して具体的に触ってみましょう。
ローカルで使えるのでデザイナーを持っている人なら比較的簡単に試せるのではないかと思います。

※ 私は以下のエージェントをNotes 8.0.2 の日本語版を使用して作成・実行しています。
他の環境でUIや操作手順が違う部分もあると思いますが、ご容赦下さい。

■ 環境の準備
1) ディスカッションデータベースの準備
 テンプレートは無しでもいいのですが、一般的なアプリケーションを想定してディスカッションデータベースを作ってみましょう。

2) 文書の作成
 何でもいいのでディスカッションデータベースに文書を作ってみましょう。
性質上100文書以上はあった方がいいのではないかと思います。
僕はここで紹介したようなエージェントを使用して作成したので、参考にしてみて下さい。

3) エージェントの作成
 何でもいいのですが、以下のような簡単な共有エージェントを実行してみます。Initializeルーチンに以下のようなスクリプトを作成します。

使用したエージェント

Sub Initialize
Dim s As New NotesSession
Dim db As NotesDatabase
Dim col As NotesDocumentCollection
Dim doc As NotesDocument

Set db = s.CurrentDatabase
' エージェントのプロパティで対象を「ビューの全ての文書」に指定して下さい
Set col = db.UnprocessedDocuments
Set doc = col.GetFirstDocument

While Not (doc Is Nothing)
Print doc.subject(0)
Set doc = col.GetNextDocument(doc)
Wend

End Sub


見ての通り、全文書にアクセスしてSubjectフィールドをステータスバーまたはサーバーコンソールにprintするだけのものです。

エージェントの名前だけは何でもいいのですが、エージェントのプロパティでは以下のように対象を「ビューの全ての文書」になるようにして下さい。そうしないと、エージェント中のUnprocessedDocumentsが正しく動作しません。

エージェントのプロパティ(基本タブ)
エージェントのプロパティ

セキュリティタブの設定が重要です。ここで「プロファイルの結果の表示」を選択するとプロファイリングが有効になります。
エージェントのプロパティ(セキュリティタブ)
プロファイルの設定

ここまで言ったらエージェントを保存して終了し、Notesクライアントでディスカッションデータベースを開いて下さい。

■ エージェントの実行とプロファイルの表示

1. エージェントの実行
  全ての文書ビューを開いたら、アクションメニューから[アクション]-[全ての文書にアクセスする(Use GetNextDocument)]を実行して下さい。

2. プロファイル結果の表示
 デザイナーからエージェントを選択して右クリックメニューから「プロファイル結果の表示」と言うメニューを選択しましょう。

右クリックメニュー
 プロファイル結果の表示

以下のようなプロファイル結果がノーツ文書形式で開かれます。

プロファイル結果
プロファイル結果

明日はこの辺で少し具体例を挙げてエージェントプロファイリングを使用したパフォーマンス分析について考えてみます。




なぜGetNextdocumentなのか?

2008年11月11日 02:21

 そういえば、なぜLotusScriptでビューをループするときはWhile ループ+GetNextDocumentで書くのでしょう。
別にGetNthDocument メソッドを使ってFor ループで書いてもいいじゃないか、て僕はよく思っていました。

これをしないのはForループととWhileループの違いではなくて、単純にGetNthDocumentよりGetNextDocumentの方がパフォーマンスがよいからです。とはいえこのことを検証したことは無かったので、エージェントプロファイリングを使って分析して見てみましょう。該当メソッドだけを抜粋しているので完全な形式で無い点はご容赦下さい
 データベースやサンプルコードはは昨日使用したものと同じです。GetNextDocumentのWhileループをview.count を使ってForループに変えるのは自明な変更なので省略させてください。
同じデータベースに対して、ループでアクセスしてメソッドの所要時間を比較してみましょう。

 

全ての文書にアクセスする(Use GetNextDocument)プロフィール

クラス          メソッド オペレーション コール 時間
DocumentCollection  GetNextDocument  1907 40

ここではGetnextDocumentは40msしか消費していません。

全ての文書にアクセスする(Use GetNthDocument)プロフィール
クラス         メソッド オペレーション コール 時間
DocumentCollection  GetNthDocument  1906 1665

結構違いますよね。。。なのでWhileループじゃないといけない理由は分かりませんが、とりあえずGetNextDocumentを使う理由はパフォーマンスが理由にある、と言う事は分かってもらえたのではないかと思います。

ディスカッションデータベースに文書を大量に作る

2008年11月12日 01:35

 開設して二週間くらい経ちますが、ちょっとずつアクセスも増えてきてうれしいです。
しばらくアプリケーション開発系の小ネタを書いてから、DWAとかの話もしていきたいと思っているのですが、出来るだけ評判のよい記事を参考にしながら投稿していきたいと思うので、率直な感想とかコメントを社内外問わず頂けるととても助かります。コメント恥ずかしい方は拍手とかでも参考になるので是非是非フィードバックよろしくお願いしいます。

 今日は以前投稿したエージェントプロファイルの紹介をしたときに少し触れたのですが、テスト用にとにかく文書数の多いデータベースを作りたいときに使うコードです。僕でさえ、10分くらいで作り終わったサンプルなので、必要になってから書いても大した手間じゃないと思うのですが、あるに越した事は無いと思うので。。 文書にアクセスするようなコードのパフォーマンスの差をみたいときとかに、こういうスクリプトで10万文書くらい作ってから、そのDBにScriptを回してみたりします。

 とりあえず、文書を作るだけだとあんまりなので、他の場面で応用できるようにSubjectに乱数を二桁ほど入れるコードと、20文書毎に途中結果を出力するような処理も入れてみました。

あと、この手のコードを作るときには、実行時間は長くなりますが、ComputeWithForm() メソッドは絶対呼んでおいたほうがいいと思います。

・ フィールドの型の調整をやってくれる。
  (文字列が入っているけど本当は数値型じゃないとダメ、とか。)
・ 計算可能なフィールドの計算をしてくれる

これをしないと、本当にコードで書いたフィールドだけしか出力されなくなってしまったり、フォームとつじつまの合わないフィールド値を持つ文書が出来てしまったりします。


Sub Initialize
Dim ses As New NotesSession
Dim db As NotesDatabase
Dim doc As NotesDocument

Set db = ses.CurrentDatabase
n = Cdbl(Inputbox("作成する文書数を入力して下さい"))
bodystr$ = "this is body"

For i = 1 To n
Set doc = db.CreateDocument
Randomize
doc.subject="test " & Cstr(Cint(Rnd() * 100))
doc.Form = "MainTopic"
doc.body=bodystr$
Call doc.ComputeWithForm(True,True)
Call doc.save(False,False)

If ( i Mod 20 = 0 ) Then
Print i & " 文書作成しました"
End If
Next
End Sub



もうちょっと便利なものをアップしたいな、と思うのですが、手持ちが少ないので。。。

LS2J : LotusScript でJavaのクラスを呼び出してみる

2008年11月13日 23:29

 意外とDIIOPの話が受けがよかったので今日も少しJavaの話をしてみます。

 LotusScript だけを書いている人はあまり意識しないのかもしれませんが、Javaなら標準で出来るこんな事がLotusScript で出来ればいいのに・・・て思うときはないでしょうか。
 前回はそういう人のためにDIIOPを紹介したつもりですが、今自分が使っているアプリケーションのエージェントをJavaで書く、となるとなかなか勇気がいるのではないでしょうか。今回はそういう方のために簡易なJavaへの接続方法としてLS2J を利用した方法を紹介します。

「また新しい技術か・・・」と思った方もいるかもしれませんが、実質9つしかクラスが無いものですのでサンプルを見るだけでだいたい使い方が分かるのではないかと思います。

LotusScript ではLS2Jと言うLSXモジュールを使うことによってJavaのクラスを呼び出すことが出来ます。僕はLotusScript を使っていると文字列処理が弱いな、と思うことが多いのでLotusScript正規表現を使用した文字列処理が出来るように、java.util.regex.Pattern クラスやjava.util.regex.Matcher クラスを呼んで正規表現を処理させてみましょう。

とりあえず今日は正規表現を行ったマッチングを行うサンプルですが、明日は置換を行う例も紹介して、コードの解説もしてみます。(ほとんど自明ですが。。。)
 正規表現を使う意味がピンと来ない方もいるかもしれないので、LotusScript で処理しづらい例として「入力文字列が全角文字だけを含む」と言うチェックが行えるような例を取り上げてみます。利用しやすいようにLS2J を使用したマッチング処理の部分は関数化しておきました。

# あわてて自分のDBにコピペするとUSELSX宣言のところをコピーし忘れるので気をつけて下さい。。

以下はボタンスクリプトなので、デザイナーがある方は新規メールなどでメニューから[作成]-[ホットスポット]-[ボタン]等を選択する事によって利用する事が出来ます。


' LS2J で使うモジュール
Uselsx "*javacon"

Sub Click(Source As Button)
Dim b As Boolean
teststr$ = "あああ"
'「ASCII 文字を含まない」=「全角文字(半角カナを含む)のみの文字」であるかをチェック
regex$ = "[^\p{ASCII}]*"
replacedstr$ = "Test"
b = MatchString(teststr$ ,regex$)
Msgbox "Input: " & teststr$ & Chr(13) & "Pattern string: " & regex$ & Chr(13) & "Result: " & Cstr(b)
End Sub

Function MatchString( InputStr As String, RegEx As String) As Boolean
' InputStr: テストする入力文字列
' RegEx: マッチングする正規表現
' 戻り値: マッチしたらTrue を返します。
Dim j As JavaSession
Dim c As JavaClass
Dim pattern As JavaObject
Dim matcher As JavaObject
Dim e As JavaError
Set j = New JavaSession
Set c = j.GetClass("java.util.regex.Pattern")
Set pattern = c.compile(RegEx)
Set matcher = pattern.matcher(InputStr)
MatchString = matcher.matches()
End Function




 比較的呼びやすいJavaクラスを例として取り上げたので、すごく簡単に思った方もいるかもしれないですが、実際はコンストラクタとか呼ぶと案外表記が面倒だったりします。本格的に使う場合には、デザイナーヘルプにあるJavaClassクラスのcreateObject メソッドや「データ型のマッピング」の項はよく読んでおいて下さい。

 また、今までのLotusScriptと違って、JVMを使用しないといけないので実行時間やメモリ使用量には影響が出るのでパフォーマンスなどが懸念されるエージェントでは特に気をつけて使う必要があります野でその点は十分注意してご利用下さい。

参考:
Javaで使用可能な、正規表現は以下をご参照下さい。
java.util.regex Class Pattern (SDK Document)

LS2J: 正規表現を使った文字列置換

2008年11月17日 03:01

 前日の例は、「これなら@ContainsとかをLotusScriptで呼び出せば出来る!」とか思う人も多いと思うので、正規表現の力をアピールするならもうちょっと複雑なマッチングの方がよかったのかもしれません。

なので今日は正規表現らしい文字列処理の例として、文字列置換の例を出しておきます。ログのスレッドIDを消すようなパターン。
あとは、1行に複数の値がマッチする場合も例として挙げておくと、一度に全部置換してくれるので、正規表現の力が分かってもらえる気がしたのですが。。実際LotusScriptでこのような処理はどうやって対処しているのでしょうか。自分が開発する事になってこの手の処理をしようと思ったら専用のライブラリを作ってしまうのかなあ・・・と思ってしまうのですが、実際使われている方の話などが聞けたらいいな、と思います。

シェルとかで文字列処理をしていると、スクリプト言語の文字列処理が物足りなくなってしまうので、正規表現に恋しくなって、案外本気でこの処理を組み込みたい・・・、て思う人もいるのかもしれません。



Uselsx "*javacon"

Sub Click(Source As Button)
Dim b As Boolean
teststr$ = "[09D4:000D-0D50] 2008/09/18 15:45:26 HTTP Server: Error - Memory allocation error"
' Thread ID部分([09D4:000D-0D50])にマッチする正規表現
regex$ = "\[[\p{Alnum}:-]*\]"
' regex$ = "\[[a-zA-Z0-9:-]*\]"
replacedstr$ = ""
result$ = ReplaceString(testStr$,regex$, replacedstr$)
Msgbox "Input: " & teststr$ & Chr(13) & "Pattern: " & regex$ & " -> " & replacedstr$ & Chr(13) & result$
End Sub

Function ReplaceString(InputStr As String , RegEx As String, ReplacedStr As String) As String
' RepleaceString: 正規表現を利用した文字列置換関数
' InputStr: 置換を行う元の文字列
' RegEx: 置換を行う文字列パターンをあらわす正規表現
' ReplacedStr: 置換する文字列
' 戻り値: 置換を行った文字列
' 注: 必ず Uselsx "*javacon" の宣言を行う事
Dim j As JavaSession
Dim c As JavaClass
Dim pattern As JavaObject
Dim matcher As JavaObject
Dim e As JavaError
Set j = New JavaSession
Set c = j.GetClass("java.util.regex.Pattern")
Set pattern = c.compile(RegEx)
Set matcher = pattern.matcher(InputStr)
ReplaceString = matcher.replaceAll(ReplacedStr)
End Function

Listを使用して2つの集合を効率よく比較する

2008年12月01日 01:28

ある集合 list1 の要素がある別の集合blakklist1 の要素を含むかどうかをチェックするにはどうしているでしょうか。

list1 = {"aa", "bb", "cc", "dd", "ee"}
blacklist1 = {"bb", "cc"}

このような場合以下のようなコードを書くことが多いのではないでしょうか。

Sub Click(Source As Button)
Dim list1(4) As String
Dim blacklist1(1) As String

list1(0) = "aa"
list1(1) = "bb"
list1(2) = "cc"
list1(3) = "dd"
list1(4) = "ee"

blacklist1(0) = "bb"
blacklist1(1) = "cc"

Forall l In list1
Forall b In blacklist1
If (b = l) Then
Print "Hit! " & l
End If
' blacklist1 が重複の無い集合の場合、ここで抜ける事が出来ます
Exit Forall
End Forall
End Forall
End Sub

僕自身、このような処理は上のように書いていました。
別にこの方法で特に問題があるとは思っていなかったのですが、先日以下のTechnoteにあるサンプルを見てなるほど・・・と思ってしまいました。

Sample agent for updating existing folders in a database when they do not exist in the inherited template (#1086404)

ここではフォルダーのアップデートをする際にシステムフォルダだけアップデートをスキップするような処理を加えている訳ですが、上の例に当てはめると、以下のような二つの集合の比較になっている事が分かると思います。

list1: 全ビュー またはフォルダ
blacklist1: ignore_list (テンプレートに含まれているフォルダ名)

同じ処理をしているわけですが、Listを上手に使うことによって、Forallが入れ子構造になることを回避しています。上の例で言うと、blacklist1を配列ではなく、リストとして定義する事によって効率のよい比較を行っているのです。

まず、リストに関してはあまり使ったことが無い方も多いのではないかと思うので、リストとはどういうものかを手短に説明しておきます。Perlを知っている方なら、Perlの連想配列のような概念です、と言えば説明終了なのですが、要するに以下のように配列のインデックスを数字ではなく、文字列にしたものです。

Dim blacklist List As String
blacklist1("bb") = 1

今回は、実はこのリストの値である部分は使用しないので、右辺では1のようなダミーの値をセットしています。
 ここでIsElement関数を使って、その文字列が、blacklistの引数として有効であるか無いかを比較する事によって効率のよい比較を行っているのです。

変更後のコードを見てみて下さい。


Sub Click(Source As Button)
Dim list1(4) As String
Dim blacklist1 List As String

list1(0) = "aa"
list1(1) = "bb"
list1(2) = "cc"
list1(3) = "dd"
list1(4) = "ee"

' 右辺の1に特に意味はありません。
blacklist1("bb") = 1
blacklist1("cc") = 1

Forall l In list1
If Iselement(blacklist1(l)) Then
Print "Hit! " & l
End If
End Forall
End Sub


細かい話はおいておくとして、すっきりしたのが分かるのではないでしょうか。
このような比較は特に、blacklist1のサイズが大きくなればなるほど効率よくなるのではないかと思います。 僕自身、実は LotusScript のリストは、デザイナーヘルプにあるように、

emp_list("山田 太郎") = 123456

のような文字列をキーにしたデータ格納をしたいとき以外には、使用しないのでは?と思っていて、実際には使用したことはなかったのですが、このように二つの集合比較のような処理でも利用出来るとするともっと使えるチャンスはあったのではないかと思って反省しました。

 是非このような処理を使う機会が僕以上にある方は、僕に代わって利用してみて下さい。。。

LotusScript でAssert を使う

2009年02月13日 00:10

 今日はOpenNTF.orgのCodebinのページから。

LS-ERROR - Minimal Error Handling for Lotus Script (OpenNTF.org)
LS-Error (Documentation)


LotusScriptのエラーハンドリングは色々な方法があるし、開発チームの運用によって色々なニーズがあるのでこういうエラーハンドリングルーチンのようなものを作っている方は多いと思うのですが、なかなか別の開発のときに流用するのは難しいのが現状ではないかと思います。実際このライブラリも便利な機能を持っているのですが、エラーメッセージが英語になったりするので抵抗をもたれる方も多いと思います。なので、ライブラリを眺めてみて面白かったエッセンスの部分だけを紹介したほうが有益かな、と思ったので今日はそんなお話です。

C言語などを勉強された方はAssert文を使用したエラートラップの方法をご存知なのではないでしょうか。簡単に言うと、「成立してはいけない条件式」を指定することによって予期しない処理を防ぐためのものです。If文でも同じことは出来るのですが第三者が見たときにも一発で目的が分かるし、必要があればDebugビルドだけで使用したり出来るので便利だったりします。

今回のライブラリでは、このAssertの関数が含まれていたので試してみましょう。
尚、このLSSファイルはボタンスクリプトでは正しく動かないのでエージェントを新規に作成してみてください。

チュートリアル
1. 新規にデータベースを作ってみてください。テンプレートなしでもかまいません。
2. 新規にエージェントを作ります。
   名前: Error Test (任意の名前を指定してください)
   イベント:アクションメニューから主導で
   対象: なし
3. このページからLSSファイルをダウンロードしておきます。
4.  エージェントペインで右クリックを選択し、「呼び出し」を選択します
5. ダウンロードしたLSSファイルを指定してください。一部書き換えが起こることが確認されますが、OKを選択してください。
6. 以下のようなコードを書いて、エージェントを保存します

Sub Initialize
Dim divisor As Integer
' divisorに初期値の代入忘れ。この場合0で初期化されます。
assert divisor <> 0 , "0で割ることは出来ません"
Msgbox "これは表示されません"
End Sub


7. デザイナーを閉じてサンプルデータベースを開きます。
8. メニューから[アクション]-[Error Test]を選択します。
9. 以下のようなダイアログが表示されます。

   Assertion failed: 0で割ることは出来ません。
at Error Test.INITIALIZE:0


これは代入忘れの変数などがあって0で割ったりすることが無いようなチェックコードを想定している例です。LotusScriptだとデバッグビルドなどは無いので単に可視性の意味のほうが大きいですね。


どんな処理をしているのでしょうか。

Public Sub assert(Byval condition As Integer, message As String)
If condition Then Exit Sub
Stop
Error 1000, formatError(Nothing, {Assertion failed: } & message, Getthreadinfo(10))
End Sub


formaterrorなどはさらに別で定義している関数ですが、エラーメッセージを整形しているだけなのでこの際立ち入ることはしません。
当たり前のように見えますが、以下のような事は参考になると思います。経験のある開発者が読んだらそんなこと言われなくても分かっている!と怒られそうですが。。。

・ Stop関数の利用
 ランタイムエラーが出たときにデバッガを使っていると自動的に止まるので便利ですね。
これもエラーハンドリング関数では付けておくと便利だと思います。当然 ですが、ConditionがTrueのときは止まりません。

・ GetThreadInfo の利用
10はLSI_THREAD_CALLPROC の事なのですが呼び出し元ルーチン名が表示されるように指定しています。
ランタイムエラーハンドリングの関数を作る場合は必ず呼び出し元を作らないと意味が無いですよね。

他にもCatch-Throwのような処理をEmulateするような関数があったりするようなので、使ってみると面白いのではないかと思います。

エラー処理そのものは業務ロジックを含んでいないので、どうしてもこの実装は開発者の良心とか美学だけにかかってしまっているところがあるのですが、エラー処理がしっかりしているアプリケーションは様々な問題が起こっても改修や対応が迅速に行えたり、メンテナンス性は大きく変わるのではないかと思います。是非経験のある開発者が身近におられる方は色々ノウハウを教わってみるといいのではないかと思います。(私も教わりたいくらいです。)

空のNotesDocumentCollection オブジェクトを作成する

2009年05月19日 00:08

今日は以下のDesigner Wiki の記事からです。

Create an empty NotesDocumentCollection

8.x 以降では 「Set coll = db.CreateDocumentCollection」のようにCreateDocumentCollection メソッドを使えば終了なので、何の面白みも無いのですがWikiでは古いバージョンでどのように作成されるかが議論されていますが、アイディア勝負なので非常に面白いですね。

たとえば以下のような例。

Dim coll As NotesDocumentCollection
Dim tomorrow As New NotesDateTime("")
tomorrow.SetNow
tomorrow.AdjustDay 1
Set coll = db.Search("@False", tomorrow, 1)


通常空のオブジェクトを作るのにdb.search()を使うのは負荷が高いのですが、未来の日付でフィルターする事によって処理を軽くしています。他にもリストを使う人、GetProfileDocCollectionで存在しないプロフィール名を使う方法など色々なアイディアが紹介されています。

これもまたWikiの醍醐味かな、と思います。

開発を行っている方にこういうアイディアを募ってみると色々出てきて面白いですね。

LS2J: Notes のJava Home ディレクトリを取得する

2009年08月31日 01:32

Notes クライアントのJavaのディレクトリを取得するのにプログラムディレクトリを取得してそこから計算してもいいのですが、もっとスマートな方法ないかな、と思っていたのですがLS2J を使う方法を考えてみました。

Javaのシステムプロパティからjava.homeの値を使えば現在のコンテキストからJavaのディレクトリを取得できるのでNotesのJavaディレクトリを取得する事が出来ます。

また、java.home をjava.specification.version のようにして、システムプロパティを変えれば現在のJava のRuntimeのバージョンを取得する事も出来るので別の使い道もあるんじゃないかと思います。

気に入ったら是非ご利用下さい。


Uselsx "*javacon"
Sub Click(Source As Button)
' C:\Lotus\Notes\jvm を返します
Msgbox GetNotesJavaDir
End Sub

Function GetNotesJavaDir As String
GetNotesJavaDir= ""
Dim jsession As New JavaSession
Dim jclass As JavaClass
Dim jobject As JavaObject
Dim notepath As String

Set jclass = jsession.getclass("java/lang/System")
Set jobject = jclass.createObject()

GetNotesJavaDir = Cstr(jobject.getProperty("java.home"))
End Function



最新記事