投稿者「wt」のアーカイブ

4Dの親コンポーネントのテーブルとフィールドをポインタで参照する

親コンポーネントのテーブルとフィールドをポインタで参照する

テーブルに値を保存するような処理をコンポーネント化する場合は、ホストデータベースのテーブルをコンポーネント側で参照するのにポインタを使います。自分のテーブルではないためテーブル名を記述することができないからです。4Dのデザインリファレンスに「ホストデータベースとコンポーネントはポインターを使用して通信を行うことができます。」とあります。

http://doc.4d.com/4Dv16/4D/16.3/Interaction-between-components-and-host-databases.300-3670869.ja.html

ここではホストデータベースのテーブルを「親のテーブル」と呼びます。親のテーブルはポインターで参照します。次のように記述します。これはHBデータベースのIDと名前を配列に取得する例です。まずポインタ変数をプロセス変数として宣言、テーブル名とフィールド名のポインタを取得します。

  //HB_DefInit
  //20171228 wat
  //テーブルとフィールドのポインタをプロセス変数に取得
  //本文 テーブル

  //テーブル
C_POINTER(vHBptr)
vHBptr:=JCL_tbl_GetPtr_byName ("HONBUN")

  //フィールド
C_POINTER(vHB_IDptr)
C_POINTER(vHB_NAMEptr)
vHB_IDptr:=JCL_tbl_GetFldPtr (vHBptr;"HB_ID")
vHB_NAMEptr:=JCL_tbl_GetFldPtr (vHBptr;"HB_NAME")

次にそれぞれのテーブルから値を取得、プロセス変数の配列に格納しておきます。

  //HB_cache_make
  //20171228 wat
  //本文 テーブルをキャシュ配列に取得

ARRAY LONGINT(vAryHB_ID;0)
ARRAY TEXT(vAryHB_NAME;0)

READ ONLY(vHBptr->)
ALL RECORDS(vHBptr->)
SELECTION TO ARRAY(vHB_IDptr->;varyHB_ID)
SELECTION TO ARRAY(vHB_NAMEptr->;vAryHB_NAME)

このようにして配列にキャッシュしておき、あとでHB_cache_findを使って参照します。

$hb_id:=HB_cache_find ($name)

HB_cache_findは次のように記述しておきます。

  //HB_cache_find
  //20171228 wat
  // hbのキャッシュからファイル名を探して、あればIDを返す。なければゼロ

C_TEXT($1;$filename)
$filename:=$1
C_LONGINT($0;$hb_id)
$hb_id:=0
C_LONGINT($index)

$index:=Find in array(vAryHB_NAME;$filename)
If ($index>0)
 $hb_id:=vAryHB_ID{$index}
End if 

$0:=$hb_id

このようにして親テーブルの値を参照します。保存するときも同じ要領でいけます。たとえば次のように記述します。

CREATE RECORD(vMDptr->)
vMD_NAMAEptr->:=""
vMD_IDptr->:=$md_id
SAVE RECORD(vMDptr->)

コンポーネントから親テーブルを参照する場合は、上記のような記述をしておけば内部テーブルと同様にデータを保存することができます。

4Dのマルチスレッドを使って処理時間を短縮する

テーブルに1万件を超えるHTMLファイルがあって、それらの中から所定のタグを抽出して別のテーブルに保存する、ような課題がありました。インタープリタモードでXeonプロセッサでも2時間以上かかっていました。同時にマルチタスクで実行すれば、使っているXeonが4コアなので1/4の処理時間、つまり30分で終わるようになるはずです。
4D Serverのモニタ画面で見ていると、CPUは25%くらいしか使われていません。4Dプロセスはインタープリターモードでは1つのコアしか使わないのです。コンパイルしてプリエンプティブ(Preemptive)モードで実行すると、「プロセス管理はシステムへと委任され、マルチコアのマシンではシステムはプロセスをそれぞれのCPUへと個別に割り当てる」とされています。
http://doc.4d.com/4Dv16/4D/16.3/Preemptive-4D-processes.300-3651705.ja.html#2821655
果たしてCPUは100%使われるのでしょうか。やってみましょう。
プリエンプティブマルチスレッドで実行するためには3つの敷居があります。マルチプロセス、プリエンプティブ、コンパイルの3つです。マルチプロセスで動くように記述されたメソッドを、スレッドセーフ(プリエンプティブモード)で、コンパイルして実行する必要があります。3つの敷居を乗り越えるように元のソースを修正するには少々手間がかかります。コンパイルするだけで早くなるかもと、次の順に試していきました。

■ コンパイル

1.【デザイン】→【コンパイル開始】
2.【実行】→【コンパイル済み再起動】
これだけです。20%ほど速くなります。ただメニューを実行するだけですので簡単です。しかしメソッドやフォームを編集しているときは【インタープリタで再起動】しなくてはなりません。特に4D Serverで複数の開発者が編集中の場合は恩恵をうけることができません。そこで問題の処理をコンポーネント化することにしました。遅い処理をコンポーネントに記述してコンポーネントだけをコンパイルしておき、ホストプロジェクトは編集可能なインタープリターで運用する、という作戦です。
コンポーネント化に伴う親テーブルをポインタ参照

 

■ マルチプロセス(マルチスレッド)

手始めにプリエンプティブではなく、コオペラティブ(Cooperative)プロセスで、マルチスレッドを実行してみました。検討した4Dコマンドは、New ProcessとCall Workerです。New processは以前からあって使ったことがあります。Call Workerはv15から使えるようになったコマンドです。今回はCall Workerを試してみます。 Call Workerの使用例

プリエンプティブでなくてもマルチプロセス化するだけで20%ほど速くなります。おそらくデータベースをクエリしたり保存したりするときに、ハードディスクのファイルI/OなどでCPUに待ち時間が発生していると考えられます。

 

■ プリエンプティブマルチスレッド

スレッドセーフという敷居があります。プリエンプティブマルチスレッドで実行するためには対象部分のメソッドがスレッドセーフでコンパイルされている必要があります。ここでは明示的にメソッドプロパティで「プリエンプティブプロセスで実行可能」をチェックします。呼び出しているメソッドすべてにこの設定をします。この設定をしておくと、メソッド内部でスレッドアンセーフなコマンドを呼び出しているとコンパイルエラーになって便利です。4Dコマンドにもスレッドアンセーフなコマンドがあります。【コンパイル開始】するとスレッドセーフなメソッドができます。
http://doc.4d.com/4Dv16/4D/16.3/Preemptive-4D-processes.300-3651705.ja.html#2821655

■ 実行結果
MacBookPro(2016)、macOS Sierraで試しました。Core i7の2コアで、4スレッド使えるようです。アクティビティモニタでCPU使用率を確認すると、コオペラティブモードでは97%くらいでしたが、これが390%とか400%近い数値になります。これでプリエンプティブになっていることがわかります。速度は4倍より遅いと感じましたが、元のよりも十分に速いです。
WindowsはOSがWindow 7で32 bitのため、プリエンプティブにしても恩恵が無いにもかかわらず、コンパイルとマルチプロセス化でかなり速くなりました。

4D リストボックス 列の移動をできなくしたい

リストボックス 列の移動をできなくしたい

フォームにリストボックスを配置して列数を2以上にすると、実行モードではユーザが列を移動することができます。実行モードでリストボックスの列(ヘッダ部分)をドラッグすると、列の順番を変えることができる、という機能です。

プログラマとしては何もコードを書かずにこの機能を提供できるのはいいことですが、たまにこの機能を抑制したい時があります。つまりユーザに列の入れ替えをしてほしくないケースです。

例えば、次の例では、勘定科目の金額は借方金額の方が左の列、貸方金額が右の列に並んでいます。これを次のようにドラッグして貸方金額を左に移動させることができてしまいます。これは抑止したいところです。会計の世界では「借方金額は左」と決まっているからです。

図1 借方金額は左

図2 ドラッグして移動中

図3 貸方が左に!!

 

 

対策:

リストボックスのプロパティで「ドラッグしない列数」に「列数」と同じ数値を入力します。数値が異なる場合はどうなるかというと、左からN個目までが移動できない列になります。

http://doc.4d.com/4Dv16/4D/16.1/List-box-specific-properties.300-3373463.ja.html

図 リストボックスを選択して表示したプロパティリストの一部

プロパティリスト

現在の「ドラッグで移動しない列数」は次のコマンドで取得できます。

LISTBOX Get static columns

「ドラッグで移動しない列数」をセットするコマンドは次です。

LISTBOX SET STATIC COLUMNS

参考情報:

リストボックスの機能に「スクロースしない列数」というのがあります。

リスボックスの横スクロールが有効になっている時に、横スクロールすると左側にあった列は左に隠れてしまいます。このように隠れて欲しくない列、つまり左側にいつも表示していたい列を指定する機能です。フォームエディタではプロパティリストの「リストボックス」の「スクロールしない列数」に数値を入力することで指定します。

このほかリストボックスの機能に「スクロースしない列数」というのがあります。

列数が多かったり、列幅が大きい列がある場合、リスボックスは横スクロールが有効になります。ことのきにスクロールして欲しくない列、つまり左側にいつも表示していたい列を「スクロールしない列数」と呼びます。

フォームエディタではプロパティリストの「リストボックス」の「スクロールしない列数」に数値を入力することで指定します。

4D 文字数に応じてフォントサイズを自動的に小さくする

4D 文字数に応じてフォントサイズを自動的に小さくする

フィールドや変数は四角形の領域を指定して配置します。そして表示する文字列がこの四角形に収まるようにフォントサイズを指定します。しかし文字列が長い場合は文字が切れてしまいます。

リストボックスの場合は、ユーザが列幅をドラッグして広げたり、v16以降で使えるようになったエリプシスを指定することで文字切れの問題を緩和させることができます。

画面表示の場合はそれで問題はないのですが、印刷の場合は問題になります。あとで広げたりすることができないからです。

そこで、文字数によって、動的にフォントサイズを指定する方法を紹介します。フォームがロードされた時に表示したい文字列に合わせて、入り切らない場合はフォントサイズを小さくします。次のようなプログラムです。

このプログラムをフォームロード後「On Load」イベント、印刷時であれば「On Printign Detail」イベントで実行します。印刷時の例で説明します。

これで「詳細」部分が印刷される時。フィールドの幅を超える文字列のフォントサイズが自動的に小さくなります。

[注意]
「On Printing Detail」イベントはデフォルトでオンになっていて、オフにすることができません。明示的にオンにする必要はありません。デフォルトでオフになっている思い込んでいたために探してしまいました。