unified diffのシンタックスハイライトをHTMLで出力するアプリ

アプリの品質チェックが厳しい会社にバグフィックスを提供する際に、コード差分と解説を求められることがありました。
果たして本当に目を通してくれているのかはわかりませんが、せっかく作るなら見てもらえた方がいいのでカラーの文書にしたいと思うわけです。
TortoiseSVNについてくるTortoiseUDiff.exeというツールは、リビジョン間のunified diff結果をハイライト表示してくれますが、これを見たまんまWordやExcelに張り付ける方法がありません。
最初に採用した方法は、http://github.com/artemv/diff_to_html.rb を使ってHTMLを出力する方法。
ブラウザで表示してWordに貼付ければよいです。
まあ、これでも別に不自由はしてませんが、
もっと簡単な方法を見つけました。
ここからダウンロードできるhighlightというアプリを使えば、diffに限らず140種類のフォーマットをハイライトできます。
マルチバイト文字は、プレビュー画面で文字化けしますが、出力結果では化けないので問題ないです。
注意点としては、ファイルのパスにマルチバイト文字があると読めないようです。
だから「デスクトップ」とかにおいてるファイルや、もちろんファイル名が日本語だったりすると移動ないしリネームしないとダメです。

LINQ to EntitiesのWhereとFirstに別々に条件を与えると並び順がおかしくなる

こんなコードを書くほうが悪いわけだけど、
var contract = entities.Contract
    .Where(c => c.User.id == user.id)
    .OrderByDescending(c => c.applied_from).ThenByDescending(c => c.created_at).ThenByDescending(c => c.id)
    .FirstOrDefault(c => c.applied_from < dtTo);  // (1)
var contract = entities.Contract
    .Where(c => c.User.id == user.id && c.applied_from < dtTo)
    .OrderByDescending(c => c.applied_from).ThenByDescending(c => c.created_at).ThenByDescending(c => c.id)
    .FirstOrDefault();   // (2)
これらはいずれも以下のSQLの結果と同じになることを期待して書いたものです。
SELECT * FROM contracts
WHERE user_id = 10 AND applied_from < '2009-12-01 00:00:00'
ORDER BY applied_from desc, created_at desc, id desc
LIMIT 1
しかし、両者の返すレコードは異なります。
OrderByDescendingで並べ替えた先頭から一件とりたいのですが、(1)の方はまれに意図したレコードと違うものが返って来ることがあります。なぜ例外がこんなに少ないのか分かりませんが、とにかく保証はできないようです。しかし、以下の(3)は(2)と同じ結果になります。
var contract = entities.Contract
    .Where(c => c.User.id == user.id)
    .OrderByDescending(c => c.applied_from).ThenByDescending(c => c.created_at).ThenByDescending(c => c.id)
    .ToArray()
    .FirstOrDefault(c => c.applied_from < dtTo);  // (3)
こういう場合、最終的にどんなSQLが実行されているのか知りたくなるわけですが、その方法がありません。
ToTraceString()で見られる未完成のSQLでは、最終的なクエリは見えません。
このへんが、Entity Frameworkが駄目なところです。いや、System.Data.SQLiteの問題か?
今回は関係ないですが、ObjectQueryを使い回しする場合には、FirstやToArrayの直前までを変数に格納して、Firstの条件部分だけを変えるという使い方をしていることがあったんですが、これではまずいですね。。。

本日の災難RubyとPostgreSQL

2年前にリリースしたRubyのシステムが突然止まった。しかも部分的に。

UDPのパケットを待ち受けていろいろやるプログラムでLinuxのデーモンとして動作するサーバー。

デバッグログを見てみると、こんなエラーが。
Rubyの組み込みライブラリであるlogger.rbとそれが利用してるmonitor.rbで例外が起こっている。
安易に考えれば、writeメソッド内をbegin-rescue-endでくくってしまえばなんとかなりそうだ、と思ったけど、ちゃんと調べてからにする事にした。
まあ、たぶん1年に一回起こるか起こらないかの事でしょう、いずれ対策を考えます、などと言って現場を後にした。
帰ってきて「monitor.rb ThreadError thread killed」などとググってみると、まさにそのバグが見つかった。
上のは4ヶ月前で、予想通り例外処理による一時しのぎ。そしてつい2日前に根本的解決がコミットされていたようだ。
まさか、こんなバグが昔からあったとは。rescueしたあと原因をログにはいてたりするところでおきたら致命的ではないか。
もっともこのプログラムがスレッドを頻繁に使用しているから起こったのであって、Railsではスレッドを多用しないし、
そもそもproduction環境で大量にログを吐くってこともないだろうからめったに遭遇しないのかもしれない。
とりあえず、小さな修正なので、Rubyをアップグレードしなくてもパッチを当てるだけでよさそう、これで解決!と思った矢先。
「また止まったんだけど。」
ここ2年で動き続けたシステムが1日に2回止まった。
これは偶然じゃなく、システムが何らかの限界を訴えているに違いない。
現場に急行してfreeしてみると、なんと2GBの物理メモリの空き容量が50MBしかない。
しかし、topで見てもそんなにメモリ食ってるプロセスは見当たらない。
何が悪いのか分からなかったが、再起動すると1.3GBくらい空きが出た。
いろいろ見ても、PostgreSQLくらいしか疑うところがない。
履歴テーブルが140GB近くになってるのだけど、このテーブルから100件ほどSELECTすると、
一気に1GBくらいメモリが増大することが分かった。
後はGCでぎりぎり頑張ってるという状態が続く。
postgresql.confを見てみると、なんとautovacuum=offになってた。CentOS5.2のPostgreSQLは8.1で、デフォルトではAUTO VACUUMされない。
24時間365日動かさないといけないのに、このテーブルにはBLOBの画像データも含んでいて簡単にバックアップもできないし、VACUUMも何時間、あるいは何日かかるか予想できない。
その間、システムのレスポンスは確実に落ちる。ドキュメントによればオプションなしのVACUUM中は読み書きが許可されるとあるが、しばらくCPUを占有されると、本来の動作が割り込んできた1回目のレスポンスが極端に落ちる。
さてどうしよう。
  1. VACUUMって部分的にできるの?できるなら毎晩ちょっとずつ実行するようにする。
  2. 代替機を用意し、その間にVACUUMを済ます。代替機にたまった履歴は破棄。
  3. システム完全停止。VACUUM FULL実行。終わるまで待ってもらう。
いずれにしても大変だ。
こういう蓄積するデータについてはデータベースではなくファイルで管理するしかないのだろうか。
--追記
メモリのfree領域がどんどん減っていくのは正常な挙動らしい。Linuxではバッファやキャッシュを利用してディスクIOの負担を減らしており、この領域はプロセスに占有されているとはみなさないらしい。
そのため、以下のようになっていても53.6MBしか空きがない!とあせる必要はないということのようだ。
[root@localhost ~]# free
	total         used        free          shared     buffers     cached
Mem:	2075444    2021844      53600           0       21848     1702788
-/+ buffers/cache:	297208    1778236
Swap:	2031608          112    2031496
いまいちRubyのエラー頻度が増えた理由との関連性があるのかよく分からないけど、VACUUMしてないことは今回のトラブルにおいてそれほど重要ではなかったということだと思う。しかし、UPDATEやDELETEしてできた領域が再利用できないのは問題なので、今後はなんとかしないといけないと思う。なにかの機会に8.4に上げるかのう。
--追記
VACUUM FULLというのは、PostgreSQL7.1以前のVACUUMで、今はこれをしないで済むように定期的なVACUUMをやるようにしなさいとのこと。
でもVACUUMがいくらテーブルロックをしなくても、確実に負荷は増すので、パーティショニングといってテーブルを分割する仕組みを利用するのがよさそう。パーティションテーブルの名前を年月からつけておき、1年以上前の月のテーブルは丸ごとDROPすれば不要領域も削除される。真のメンテナンスフリーが実現可能?
このあたりの仕組みは、最近のPostgreSQLはかなり機能アップしてきているみたいです。逆に言うとLinuxに標準で含まれるパッケージを利用してる場合はかなり使い勝手の悪さを強いられているということでしょう。
24時間365日メンテナンスフリーなんて、小さな規模では考えなくてもできるものだと思い込んでましたが、それはかなり大変なことで、データベースに詳しくない人でもそれなりにできる道が開けたというのはつい今しがた、というところなのかもしれません。
Railsならデータベースを意識する必要なし、本番環境で入れ替えたりもできちゃう、なんてのは、やはり宣伝効果を狙った機能ということになるのでしょうね。

IsolationLevel.ReadCommittedが安全なケース

データベースにはロックのポリシーとして、分離レベルというのがある。
このレベルを高めれば、データの一貫性を維持できるが、その分ロックされる時間が増え、結果としてパフォーマンスに影響したり、一貫性を損なう処理をした場合の対応について考慮しなくてはいけなくなる。
 
最も分離レベルが高いのをSerializableという。これを設定すると、読み取り中にデータの変更ができない。書き込み中は読み取れないという、あらゆる矛盾を排除するようなロックがかけられる。そのかわりロックがかかっている間、処理がブロックされるのでパフォーマンスが落ちる。
 
ほとんどのデータベースはデフォルトでReadCommittedという分離レベルを使う。これはSerializableよりもロックが甘く、トランザクション中でもコミット済みデータの読み取りを許可する。ただし、途中で削除処理のトランザクションが完了すると、読み取り中のデータと矛盾してしまう。だから反復読み取りには矛盾が発生する可能性がある。
 
 
でも、アプリケーションの用途によっては、というかたいていの場合、ReadCommittedは安全なのだ。
 
例えば、月末で確定したデータを月初に帳票処理する業務アプリの場合、読み取るデータは全て確定済みなのだからそのデータに関しては変更されることがない。だから現実的には矛盾は発生しないし、そういう処理はアプリケーションで禁止してしまうことができる。
 
とはいえ、そう簡単に割り切れないケースもあるだろう。アプリケーション側でロックを実装する必要があるかもしれない。
 
 
並行処理が必要ないなら何にしたってかまわない。
 
自動的にデータを収集するシステムなどで、いつデータが入ってくるのか分からない場合はロックの少ないReadComittedを使うことでブロックしないアプリケーションが作れる。
 
OracleやSQLServerのような高度なデータベースはReadCommitted Snapshotなどというようなものを使えるらしい。
 
 

C#のプロパティに初期化の方法があればいいのにと思った

C#のプロパティは宣言と一緒に初期値指定することはできない。フィールドと組み合わせるか、コンストラクタで初期化するとかしないといけない。

class Person
{
    string _Name = "名無し"; // フィールドでやるか
    public string Name
    {
        get
        {
            return _Name;
        }
        set
        {
            _Name = value;
        }
    }
    public Person()
    {
        Name = "名無し"; // コンストラクタでやるか
    }
}

これまでは値をセットしたときにイベントを起こすとか、設定ファイルと連動させるときとかに使ったことがあったくらい。

もしかしたら次期バージョンではできるのかなと思って、「C# プロパティ 初期 .net framework 4.0」とかで調べてみるけど、無さそうですね。

そのかわりちょっと興味をひく記事を見つけました。

[C#]自動プロパティの必要性
[C#]自動プロパティの必要性(その2)

出水さんという方のコメント「アセンブリ公開するのがわかっていれば、もともとフィールドでは置かないですしね」

なるほど、先日からCOMを使っているので意味分かります。インターフェースにフィールドは置けないんですよね。メンバ変数みたいなのをCOMに公開したければパブリックプロパティを使う必要があります。

インターフェースに初期化データを置くというのも変な気がするので、その辺で初期化は導入されないのかもしれないですね。詳しいことは知りませんが。

DataGridViewをActiveXコントロールにラップしてTcl/Tkのウィンドウに埋め込む(2)

 

前回に引き続き、イベントの定義をやってみました。

DataGridViewのボタンセルが押されたら、そのセルの行・列番号を引数にして、Tclのプロシージャをコールバックするということがしたい。

ソリューション一式: SimpleDgv.zip

まず、メソッドと別にインターフェースを用意して、InterfaceTypeをComInterfaceType.InterfaceIsIDispatchにします。

[Guid("1AE7D7D7-02EF-4d70-B7F5-71CE046FAEA9"), ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ISimpleDgvDispatch
{
    void ButtonClick(int col, int row);
}

そして、クラスの属性にComSourceInterfaces(“ISimpleDgvDispatch”)を追加し、クラスにButtonClickイベントを定義します。

[Guid("29E1BC35-88D3-47f0-997D-B889CA25E135"), ComVisible(true), ClassInterface(ClassInterfaceType.None), ComSourceInterfaces(typeof(ISimpleDgvDispatch))]
public partial class SimpleDgv : UserControl, ISimpleDgvInteface
{
    public delegate void ButtonClickDelegate(int col, int row);
    public event ButtonClickDelegate ButtonClick;

    protected void OnButtonClick(int col, int row)
    {
        if (ButtonClick != null)
        {
            ButtonClick(col, row);
        }
    }
}

ビルドするとCOMとして公開されました。

Tcl側のコード

proc ButtonClick {col row} {
    tk_messageBox -type ok -message "Callback Col=$col, Row=$row"
}
optcl::bind $dgvObj ButtonClick ButtonClick

 

さあ、どうでしょう?

エラー全文

イベントを実行したC#側で例外が発生しました。例外テキストからはSystem.RuntimeType.InvokeDispMethodに渡されるcultureが不明なものであったということだろうと解釈できますが、だからといってどうすればよいというのでしょう?

DISP_E_UNKNOWNLCIDとはなんぞや?ググっても文字通りの説明しか出てきません。

AssemblyInfo.csでCultureInfoを指定したりもしてみたが、何も変わらない。

これで丸1日試行錯誤したものの、全く原因も解決法も分からず途方にくれました。

どうしよう。引っ込みがつかない。


2009/9/8 -- 追記: 成功

.Net Framework フォーラムで質問したらjzkeyさんが回答をくれました。

http://social.msdn.microsoft.com/Forums/ja-JP/netfxgeneralja/thread/46e0c5a2-0a89-472e-b4d6-0cd7399e1300

動くようになったoptclのバイナリとソースを置いておきます。

http://yyamasak.drivehq.com/devel/src/tcl/lib/optcl3010t-bin.zip
http://yyamasak.drivehq.com/devel/src/tcl/lib/optcl3010t-src.zip

optclがビルドできるようになったので、とりあえずBase64でのやり取りで妥協したマルチバイト引数の件も調べれば何とかなるかもしれないという希望が出てきました。

DataGridViewをActiveXコントロールにラップしてTcl/Tkのウィンドウに埋め込む(1)

先日Tcl/TkのGUIにデータグリッドを、ということでTkTableをいじっていましたが、やっぱりいろいろ大変だということで、.NET FrameworkのDataGridViewをTkのウインドウに埋め込む実験をしてみました。これならほとんどデフォルトのバインディングでも文句はないでしょう。
 
さて、C#で作ったユーザーコントロールをActiveXコントロールにする方法は比較的簡単です。プロジェクトのプロパティでそれっぽいところに2箇所ほどチェックを入れてビルドすればCOM参照可能になります。
 
ビルドしてできたDLLはP/Invoke的な方法で利用することはできないです。マネージド環境の外から使うため、COMに登録する必要があります。
 
手動でインストールする場合は、WindowsSDKに含まれるregasm.exeを使って、
regasm SimpleDgv.dll /tlb:SimpleDgv.tlb /codebase
 
アンインストールは
regasm SimpleDgv.dll /unregister
 
COMへの登録に成功したら、次はいかにTclから利用するかです。ActiveTclには標準でtcomというCOMを利用するためのパッケージが付いてきますが、ActiveXコントロールをTkに埋め込むという目的には使えません。代わりにOptclというパッケージが存在します。これはActiveTclに含まれていないため、Tcler's Wikiにあったリンクからコンパイル済みのバイナリをとってきました。
 
System.Windows.Forms.dllがそのままCOMにできればいいなと一瞬思ったんですが、そうしたところでOptclで使うのが難しい引数や型もあるので、あまり意味がない気がします。必要そうなものに限ってラッパーメソッドを作って公開することにします。
 
以下の図は、上がC#のフォームにユーザーコントロールを貼り付けたもの(これはCOMではありません)。下の方が、optclを使ってActiveXコントロールとしてTkのウインドウに貼り付けたものです。
 
 
日本語が文字化けしていますね。どうしてこうなるのか、よく分かっていません。
文字列はソースファイルに埋め込んであります。
 
TclのソースはShiftJISで、C#はUTF-8 BOM有りとなっています。
聞くところによると、Tclは内部エンコーディングとしてUTF-8を、C#はunicodeを採用しているそうですが、その辺の問題でしょうか?
optclが出す(C#のエラーメッセージと思われる)も文字化けしてて復元できないんですよね。
 
いろいろといじってみてはいますが、解決していません。
 
encoding convertto unicode
encoding convertto cp932
 
とかは意味なかったです。このコマンドもあまり理解してないんですけどね。
 
C#でバイト列から文字コードを判定する方法はあちこちで紹介されていたので、Tclから渡すときに文字列をbinary formatしてbyte[]で渡そうと思ったらoptclの制限で配列引数は未実装とのこと。
 
うーん困った。
 

2009/9/2 -- 追記: とりあえずC#の側で解決しました。

byte[] b = Encoding.Default.GetBytes(s);
string u = Encoding.UTF8.GetString(b);

要するに、TclはUTF-8をデコードして送ってるのに、C#はシステムのデフォルトエンコーディング(Shift_JIS)としてエンコードしてるようなのです。

だからその逆をたどってやればよいわけで、まず文字化けした文字列をShift_JISとしてバイト列に戻します。これをUTF-8エンコードしてやることで、本来の文字列に戻してやることができるという理屈です。ただし、これはもともとUTF-8で送ってくるクライアントにしか対応できません。2行目が決めうちだからです。


2009/9/2 -- 追記: やっぱだめ

うまくできたと思ったのはひらがなだけで、漢字やカタカナは一部が文字化けしてしまいました(例: 選択 -> 選・)。

ここによれば、一度間違って変換されたものは可逆性を失うようです。
http://dobon.net/vb/dotnet/string/getencoding.html

最初から不可逆なんだからC#で直すことはできないってことになる。

あとはTclからそもそもShift_JISで送れるようにするか、base64でASCIIとして送って、デコードするかしかなさそう。


2009/9/2 -- 追記: 成功

やっぱりbase64でやることにした。いちいち変換しないといけなくてめんどいけど、しょうがあるまい。

Tcl側のポイントは、マルチバイト文字を含む場合は一旦バイナリに変換しないといけないこと。encoding converttoを使う。
C#側はShift_JISのASCII部分だけ使うので、情報の損失がなくなった。返すときは任意のエンコーディングでかまわないようだが、Tcl側で対称性を持たせるためにシステムデフォルト(Shift_JIS)で返すようにした。

Tcl側のコード(エンコーディング省略でシステムデフォルト)

proc dec {s} {
 ::base64::encode [encoding convertto $s]; # Tcl -> C#
}

proc enc {s} {
 encoding convertfrom [base64::decode $s]; # C# -> Tcl
}

C#側のコード

namespace Extension
{
    public static class StringMethod
    {
        public static string Dec(this string s, Encoding encoding) // Tcl -> C#
        {
            var b = Convert.FromBase64String(s);
            b = Encoding.Convert(Encoding.Default, encoding, b);
            return encoding.GetString(b);
        }

        public static string Enc(this string s) // C# -> Tcl
        {
            var b = Encoding.Default.GetBytes(s);
            return Convert.ToBase64String(b);
        }
    }
}


Visual Studio 2008 C#ソリューションとTclのソースを固めて置いときます。ご自由に拾ってください。
 
 
 

今後の予定:
  1. イベントの定義(ボタンクリックとか)
  2. クリップボード操作(CSV/TSVプレーンテキスト)
  3. その他もろもろ

TkTableのバインディングを修正した

TkTableはdllで配布されていますが、ヘルプに説明のあるデフォルトのバインディングはlib/Tktable2.9/tkTable.tclに書かれているようです。
このファイルは特にデモアプリなどで使われているわけではなく、単にカスタマイズする人のために内部の実装を見えるようにしたものだと思われます。

TkTableはオプションで-selectmode extendedを指定することでドラッグによる範囲選択ができるようになるのですが、セル領域とタイトルセル領域にまたがってドラッグした場合、選択領域がばらばらになってしまうという問題がありました。

最新の2.10に添付されたtkTable.tclも全く変わってなかったので、修正してみました。ただ、過去の選択部分だけをクリアするような処理を、全域クリアに変えてしまったので、パフォーマンス的には問題ありの可能性もあります。ためしに3000行×50列とかでやってみたところでは全く問題ありませんでした。
 

--- C:/Tcl/lib/Tktable2.9/tkTable.tcl	Tue Aug 25 20:01:59 2009
+++ C:/devel/tcl/tktable/tkTable.tcl	Fri Aug 28 18:59:51 2009
@@ -433,26 +433,54 @@
 	    if {[catch {$w index anchor}]} { return }
 	    scan $Priv(tablePrev) %d,%d r c
 	    scan $el %d,%d elr elc
-	    if {[$w tag includes title $el]} {
-		if {$r < [$w cget -titlerows]+[$w cget -roworigin]} {
-		    ## We're in a column header
-		    if {$c < [$w cget -titlecols]+[$w cget -colorigin]} {
-			## We're in the topleft title area
-			$w selection clear anchor end
-		    } else {
-			$w selection clear anchor [$w index end row],$c
-		    }
-		    $w selection set anchor [$w index end row],$elc
-		} else {
-		    ## We're in a row header
-		    $w selection clear anchor $r,[$w index end col]
-		    $w selection set anchor $elr,[$w index end col]
+
+	    set titlerowend [expr {[$w cget -titlerows]+[$w cget -roworigin]}]
+		set titlecolend [expr {[$w cget -titlecols]+[$w cget -colorigin]}]
+	    if {$r != $elr} {
+			if {$r < $titlerowend} {
+				if {$elr >= $titlerowend && $r >= $titlerowend} {
+					set elr $r; # turn aside while selecting title row area
+				}
+			} else {
+				if {$elr < $titlerowend && $r < $titlerowend && $elc >= $titlecolend} {
+					set elr $r; # turn aside while selecting non-title row area
+				}
+			}
 		}
+		if {$c != $elc} {
+		    if {$c < $titlecolend} {
+				if {$elc >= $titlecolend && $c >= $titlecolend} {
+					set elc $c; # turn aside while selecting title col area
+				}
+			} else {
+				if {$elc < $titlecolend && $c < $titlecolend && $elr >= $titlerowend} {
+					set elc $c; # turn aside while selecting non-title col area
+				}
+			}
+		}
+		$w selection clear all
+	    if {[$w tag includes title $r,$c]} {
+			if {$r < $titlerowend} {
+			    if {$c < $titlecolend} {
+					## We're in the topleft title area
+					if {$elc >= $titlecolend} {
+					    $w selection set anchor [$w index end row],$elc
+					} else {
+					    $w selection set anchor $elr,[$w index end col]
+					}
+			    } else {
+				    ## We're in a column header
+				    $w selection set anchor [$w index end row],$elc
+			    }
+			} else {
+			    ## We're in a row header
+			    $w selection set anchor $elr,[$w index end col]
+			}
+		    set Priv(tablePrev) $elr,$elc
 	    } else {
-		$w selection clear anchor $Priv(tablePrev)
-		$w selection set anchor $el
+			$w selection set anchor $el
+		    set Priv(tablePrev) $r,$c
 	    }
-	    set Priv(tablePrev) $el
 	}
     }
 }

Tcl/TkからC#へ

大学でC++に挫折して、プログラマはあきらめていた私はサークルのホームページでPerl/CGIを使ったのをきっかけに再びプログラミングに手を染めることになりました。

それでも仕事で使えるレベルではないだろうと、会社に入った時点ではプログラマになることは断っていたのですが、会社のCRM立ち上げに携わった後に部署がなくなり、開発部にまわされました。

最初にやったのがカメラのPTZ制御プログラムにPELCO-Dプロトコルを追加するというC++の小さな案件でした。それはそれっきりで、次にやったのがTcl/Tkのタッチパネル式のGUIアプリケーションをベースにした開発案件でした。それ以来、3年ほどWindowsアプリケーションをTcl/Tkで作ることになりました。

Tclの自由度は高く、XOTclなどでオブジェクト指向言語のようにも使えます。C#のクラスはプロパティやメソッド以外にイベントというメンバを持つことができるのですが、これだってTclからすればさして特別な機能ではありません(コンテキストのキャプチャは確かに難しいですが)。Ruby on RailsのActiveRecordのようにスキーマを自動的にマッピングする機能はType-safeな方法では実現できないので、C#には同様のライブラリは存在しません(全てを文字列として扱えばよいだけなので、不可能というわけではないです。誰もやらないというだけで)が、Tcl/Tkでコンセプトを真似た簡易版を作って使っています。

しかし、Tcl/Tkの言語本体はそこそこのスピードで進化していくものの、ライブラリ群は放置されてるものが多いです。また、2002年くらいを境に日本語の書籍の発行は途絶えています。オブジェクト指向拡張はあるものの、Rubyみたいに自由度の高いXOTclにはActiveTclのIDEやデバッガが対応していません。

Tclは世間のうわさに反して優れた言語だと思うし、そこそこライブラリも充実してはいるのですが、やはり完璧なアプリを作るに当たって、いろいろと問題があるのは否めません。

BLT Toolkitはすばらしい拡張ライブラリですが、Tcl/Tkの最新版に対応していません。stubに対応すべくGeorgeさんががんばって更新しているようですが、依然完成のめどが立たない状況が続いてます。

ほかにも結構重要なライブラリがちょっとしたバグを残したまま放置された状態です(TclUDP, Tktableとか) 。

teapot化されたライブラリをEXEにラップするにはTclAppをバージョンアップしないといけないし。

新規開発に関しては既にTcl/Tkをあきらめて、C#を採用しています。Tclの自由度に比べれば堅苦しさは否めませんが、.NET Frameworkなら高品質なGUIが開発できるので、安心してお客さんに見せられます。

Tcl/Tkには印刷ダイアログを表示する機能がありません。DataGridView並みに高度なGUIがありません。TkTableやTablelistがあるじゃないか、というかもしれませんが、これらはDataGridViewに比べて非常にプリミティブです。

大学の研究室や社内で使うならまだしも、簡単にGUIを作れるというのは、現代においてもはや売り文句にならない気がします。

C#などのGUIを生かしつつTclの既存アプリを再利用することはできないのでしょうか?これについてはいろいろと調べてきましたが、なかなか有効な手段は見つかりません。WindowsであればCOMの仕組みを使うことで何とかいけるみたいな話(optcl)はありますが、果たして実際にやった人はいるのでしょうか?

エクスポート関数はともかく、引数の型が構造体だったりして、構造体のメンバがさらに構造体だったりしたら、どうするの?とか。

C++の方が相性はいいだろう。MFC DataGridというのが使えたらいいけど、クラスのインポートってできるの?とか。

TclBridgeというのは?高いなあ。ちゃんとメンテされてるの?

Eagleは?Tclの再実装?うーんそういうことじゃないんだよなあ。

じゃあ何ができたらいいのでしょう?

ネイティブのTclに.NETのクラスライブラリをインポートして、publicなクラスや構造体を自由に操作したい。これだ!

私にCの知識と遊んでいても給料払ってくれる社長がいたら多少貢献できると思うのだけど。

・・・いや、無理。私では力不足です。

こうしてネットのところどころでこういうボヤキを書いているのは誰かが作ってくれるかもとちょっと期待してるからなんですが、まあ、そんな都合よくはいかないよな。。。日本語だから外人読めないし。

夢の対決 投手ロボVS.打者ロボ

これはたしかにすごいけど、投手ロボの立場が微妙だなあ。

仮に150キロの球を投げられるようになっても大リーグの投手にはかなわないけど、
そのボールを100%打てる打者の方がレアだから、打者ロボの方が先に人間を超えてしまう。

これでは、技術的には高度なはずの投手ロボの注目度が低くなってしまう。

だから、さらに発展させて三本指でキャッチするロボットを作って欲しい。
三本指で150km/hのキャッチボールとなると、まさにロボットって感じがする。
それに両方とも対等だからどちらかが注目度が低くなって悲しむこともないし。

http://headlines.yahoo.co.jp/hl?a=20090724-00000097-san-soci