Tcl/TkでDDE通信をする(2)
 DDE通信のページもついにシリーズ化されますます佳境の第2回。 第1回は駆け足というか急ぎ足でつっきってしまったため今度はのんびり進みます。 さてPowerBuilderなどという言語処理系を使っている時点で既にTcl/Tk実験室の中でも異端の道を猫まっしぐらなシリーズになること間違いなし。 そのうちモンプチのストックがなくなると息切れ必至で、 近いうちに出張所に移管という悪夢のシナリオも既にバッチリ用意。さて…

 VisualBasicやPowerBuilderのようなWindowsカルチャーをベースに成長してきた言語処理系は、 基本的にファイル操作や文字列処理というのがまったくできなかったり、 Perlなら数行で済む処理を50行ぐらいかけて書く必要があったりします。 それはこれらの言語製品がターゲットとする分野がそのような処理をあまり必要としなかったというだけの話で、 大抵の開発者はその世界の枠内で今日も元気にデベロッピングなのです。 逆にどうしてもそんな処理がいるという「異常な」場合には 「ならCでDLLを書け」とか「OCXを開発しろ」とか無理難題ワールドに突入してしまい、 ここで私を含め時間に追われライフを送る多くの開発者は涙をのむことになります。

 DDEというのはADOの現在ではもはやオブソリット気味に廃れた、 というか死にかけた、というかマイクロソフトがVBX、OCX、ActiveX、DAO、ADO と手を変え品を変え出してくるやつらどもが新しくなるたびにたくさんの技術者が1から習得のしなおし、 さすがに5回目になるともういや!こんな生活、 という中でもまだ風水に耐えているテクノロ爺です。 そして、 Windows上で動き、ファイル操作や文字列処理が簡単にでき、 DDEに応答できる、そういう都合のよい言語が都合よく存在します。

 また逆の場合もあります。Tcl/Tkではどうあがいても表計算ソフトみたいなグリッド状のシートを出して支店の売上を集計してグラフに出して印刷までしてしまうなどとゆーことはできませんし、 いわんやPowerBuilderのようにSQL文でRDBMSと通信してとってきたレコードをいかようにも並べて表示、 それも単なる表示ではなく文字通り表にして示すことができるようなもの(データウィンドウコントロール) を出すこと、をや。(係り結び) バッチファイルや表計算のマクロも含め従来のスクリプト言語では このような他のアプリケーションを呼び出す、 ということはかなりできましたが、 DDEを使うとスクリプト言語でアプリケーションの中の動作まで (むろん限度はありますが)制御できるようになります。


PowerBuilderでTcl/Tkを利用する
 よくある例が、というか私が実際にアプリケーションで使った例ですが、 PowerBuilderでファイルの最終更新日やサイズを取得する例です。 もともとPowerBuilderでできるファイル操作は 「ファイルの読み書き」「ファイルを消す」「ファイルが存在するかどうかを知る」 これだけです(*1)。 そこで Tcl/Tk のfileコマンドでその他の情報を返すスクリプトをつくってみます。 つまり、Tcl/TkスクリプトがDDEサーバ(ソース)となります。
(*1) PowerBuilder 5.0.1時点。現在はPowerBuilder 7がリリースされています。
package require dde

namespace eval StatDaemon {
    set result {}

    proc init {} {
	dde servername STATDAEMON
    }

    proc hello {} {
	tk_messageBox -message "Hello!"
    }

    proc hello2 {who} {
	tk_messageBox -message "Hello, $who!"
    }

    proc mtime {filename} {
	variable result
        set result [clock format [file mtime $filename] -format {%Y/%m/%d}]
    }

    proc size {filename} {
	variable result
	set result [file size $filename]
    }

    proc type {filename} {
	variable result
        set result [file type $filename]
    }
}

StatDaemon::init
label .laba -text "STAT DAEMON READY"
pack .laba
bind .  exit
# end.
まず、package require ddeでDDEを使えるようにします。 次にDDEサーバになるTclスクリプトは、必ず dde servernameコマンドで一意識別できる名前をつけておきます。 この名前はDDEのマニュアル中でしばしばトピック と呼ばれるもので、ここでは「STATDAEMON」と名づけています。 ちなみに、DDEのマニュアルで プログラム名または アプリケーション名と呼ばれるものは、 Tclスクリプトの場合は自動的に必ずTclEval という名前にされます。外部からは、このプログラム名とトピック名でどの Tclスクリプトに接続するかを指定できます。
 注意点は、DDE通信されるのを待つ、ということは「イベント待ち」 状態になっていないといけないということです。 つまり普通はこの例のように適当なTk画面を出してGUIイベント待ちの状態にして 「常駐」させておくことになります。

 今度はこれを呼び出すPowerBuilderのプログラム(一部)です。 DDE通信をする部分は関数とかウィンドウ関数とかの独立したコードにしておくとよいでしょう。 (大きなお世話? 確かに、そうですが) ここでは

String f_dde_get_mtime(String as_filename)
という格好の関数としています。
//
// f_dde_get_mtime
// Tcl/TkのDDEサーバにファイルの日付を聞きます。
// as_filename : ファイル名
//
integer handle
string result

// まだサーバが立ち上がっていなければ、起動します。
handle = OpenChannel("TclEval", "STATDAEMON")
if handle < 0 then
   run("C:\Program Files\Tcl\Bin\WISH82.EXE " +&
       "C:\DEV\POWERBUILDER\STATDAEMON.TCL")
   handle = OpenChannel("TclEval", "STATDAEMON")
   if handle < 0 then
      MessageBox("環境エラー", "DDEサーバと通信できません。")
      return ""
   end if
end if

if ExecRemote("StatDaemon::mtime " + as_filename, handle) < 0 then
   MessageBox("DDEエラー", "DDE通信(コマンド実行)でエラーが発生しました。")
   result = ""
else
   if GetRemote("StatDaemon::result", result, handle) < 0 then
      MessageBox("DDEエラー", "DDE通信(結果の取得)でエラーが発生しました。")
      result = ""
   end if
end if

CloseChannel(handle)
return result
// end.
PowerBuilderでDDE通信をするには、 OpenChannel関数を使います。 2つの引数はそれぞれアプリケーション名、トピック名で、 アプリケーション名は先述の通りDDEサーバがTclスクリプトのときは 「TclEval」に固定です。ちなみにExcelは「Excel」、Wordは「WinWord」です。
handle = OpenChannel("TclEval", "STATDAEMON")
ここでは、まず一度OpenChannelしてみて、失敗したらこのDDEサーバになるTclスクリプト自身が起動してないのが原因ではないかと見てこれを起動しようとします。 しかるのちそれでも接続できなければエラー、ということで。
ExecRemote("StatDaemon::mtime " + as_filename, handle)
GetRemote("StatDaemon::result", result, handle)
DDE通信が若干面倒なのが、この「実行させる」というのと「実行結果を受け取る」 というのを別々に行う必要がある点です。 実行させるのは、ExecRemote関数です。 接続相手がTclスクリプトの場合、その最初の引数はTclのコマンド(文)です。 グローバルレベルで実行されるので、必ず名前空間の修飾は上のように完全分類化 (Fully Qualified)で行う必要があります。

一方結果を受け取るにはGetRemote関数を使います。 接続相手がTclスクリプトの場合、 その最初の引数は受け取るべき値が入っているTcl変数の名前、 2つめの引数がそれを格納するPowerBuilder側の変数名(String型)です。

ExecRemote関数もGetRemote関数も、エラーを起こすと負の数を返してきますが、 重大な注意として、このエラーというのは「DDE通信ができたかどうか」 という意味で、「Tclコマンドが正しく実行されたか」という意味ではありません。 Tclコマンドがエラーになろうがなるまいが、そのコマンドが DDEサーバであるTclスクリプトに無事渡った時点でDDE通信としては正常終了ということになります。 で、Tclコマンドがエラーになった場合どうなるかというと、どうもなりません。 はにゃ? つまり、エラーのダイアログも出ませんし、 ExecRemote関数は正常終了なので単に無視されただけという状態になってしまいます。 しかしそれでは何のエラーかわからないので開発する人としては何が悪いのか、何も悪くないのか、 という情報が全く得られません。いや、唯一情報を得る方法があって、 それはGetRemote関数でTcl変数errorInfoをとってくることです。 これでどの文で変数が存在しないとか計算式が文法エラーとか、 そういうエラーが得られます。 複雑なDDE通信をデバッグするのは結構ホネです。覚悟はいいですか?

やねうらの top
(first uploaded 1999/11/03 last updated (not ever), Urano398)