2008-02-26

C による Tcl/Tk 拡張 : カスタマイズした wish (3)

SourceForge.net Logo
回は、Tcl における expr コマンドで利用できる関数を追加した拡張 wish, bwish を作ります。追加する関数は、サンプルとして立方根を計算する cbrt という C の関数を利用します。もし、お使いの Linux で最新の gcc(正確に言えば、glibc)の開発環境でない場合は念のため man cbrt でマニュアルが表示されるかどうか確認してください。利用できない場合は、適当に他の利用可能な関数に置き換えてください。MinGW クロスコンパイラの方は C99 に準拠していますので、cbrt を利用できます。

プログラム


プログラムのソースは、kwish をビルドした時と同じような構成をとります。すなわち bwish_main.cbwish_sub.c および bwish.h です。

今回のプログラムでは、前回 kwish をビルドするときに利用した API、Tk_MainLoop を利用せず、代わりに Tk_Main を使いました。どのやり方が正しい、ということではありませんが、wish あるいは tclsh を拡張して、イベントループを扱うプログラムを作成する際には、Tk_Main あるいは Tcl_Main を利用する方法がポピュラーだと思います。

/* bwish_main.c */
#include "bwish.h"

/* type of math value definition */
Tcl_ValueType ArgDouble[] = { TCL_DOUBLE };

/*
* Tk_Main acts as the main program for most Tk-based applications.
* Starting with Tk 4.0 it is not called main anymore because it is part of
* the Tk library and having a function main in a library (particularly
* a shared library) causes problems on many systems. Having main in the Tk
* library would also make it hard to use Tk in C++ programs, since C++
* programs must have special C++ main functions.
*
* Normally each application contains a small main function that does nothing
* but invoke Tk_Main. Tk_Main then does all the work of creating and running
* a wish-like application.
*/

int
main (int argc, char *argv[])
{
Tk_Main (argc, argv, AppInit);
return EXIT_SUCCESS;
}

/*
* This procedure provides a “hook” for the application to perform its own
* initialization, such as defining application-specific commands.
* The procedure must have an interface that matches the type Tcl_AppInitProc:
*
* typedef int Tcl_AppInitProc(Tcl_Interp *interp);
*/

int
AppInit (Tcl_Interp * interp)
{
if (Tcl_Init (interp) == TCL_ERROR)
return TCL_ERROR;

if (Tk_Init (interp) == TCL_ERROR)
return TCL_ERROR;

/* Define application-specific commands and math functions here */
Tcl_CreateMathFunc (interp, "cbrt", 1, ArgDouble, func_cbrt,
(ClientData) NULL);

return TCL_OK;
}

/* bwish_main.c */

コメントに記載してある内容は Tcl/Tk のマニュアルからそのまま写しただけです。要は main 関数では Tk_Main を実行してイベントループ状態にするだけで、Tcl/Tk のカスタマイズに関わる処理は AppInit 関数に記述するようにします。

今回のサンプルで Tcl に追加する立方根の算術関数は、cbrt という名前で、その処理は、func_cbrt 関数が担います。この算術関数 cbrt では倍精度実数の引数がひとつ必要なので、Tcl_Value 型で TCL_DOUBLE を一つ持つ配列をあらかじめ定義しておきます。もし倍精度実数の引数が2つ必要な場合には、例えば次のようにします。

Tcl_ValueType ArgDouble2[] = { TCL_DOUBLE, TCL_DOUBLE };

なお、TCL_DOUBLE の他に TCL_INT, TCL_WIDE_INT あるいは TCL_EITHER を指定できます。

立方根の計算処理部である func_cbrt 関数は bwish_sub.c に記述しました。立方根の計算に関わる引数と計算結果は、インタープリタ interp に対しポインタでやりとりをします。func_cbrt 関数は int 型で、計算の成否を TCL_OK (= 0) あるいは TCL_ERROR (= 1) で返すだけです。

/* bwish_sub.c */
#include "bwish.h"

/*****************************************************************************
* int func_cbrt
* 拡張数学関数 3√x(立方根)の処理部
*
* Tcl 側のコマンド
* expr cbrt($n)
*****************************************************************************/

int
func_cbrt (ClientData clientData, Tcl_Interp * interp,
Tcl_Value * args, Tcl_Value * resultPtr)
{
double x;

x = args->doubleValue;

/* 計算結果 */
resultPtr->type = TCL_DOUBLE;
resultPtr->doubleValue = cbrt (x);

return TCL_OK;
}

/* bwish_sub.c */

インクルードファイル bwish.h には、このプログラムで使用するインクルードファイルやプロトタイプ関数宣言をまとめました。

/* bwish.h */
#if !defined(BWISH_H)
#define BWISH_H

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <tcl.h>
#include <tk.h>

/* prototype functions */
extern int AppInit (Tcl_Interp *);
extern int func_cbrt (ClientData, Tcl_Interp *, Tcl_Value *, Tcl_Value *);

#endif /* BWISH_H */
/* bwish.h */

Windows 用のビルドでは、アイコンをリソースとして用意しました。リソーススクリプト bwish.rc は、Tcl/Tk のソースファイルにある win/tclsh.rc などをベースにしています。なおブログの表示の関係で、80文字を越える行は80文字で改行しています。

// bwish.rc
// RCS: @(#) $Id: bwish.rc,v 1.2 2008/02/25 10:48:08 bitwalk Exp bitwalk $
//
// Version Resource Script
//


#include <winver.h>
#include <tcl.h>

//
// build-up the name suffix that defines the type of build this is.
//

#if TCL_THREADS
#define SUFFIX_THREADS "t"
#else
#define SUFFIX_THREADS ""
#endif

#if STATIC_BUILD
#define SUFFIX_STATIC "s"
#else
#define SUFFIX_STATIC ""
#endif

#if DEBUG && !UNCHECKED
#define SUFFIX_DEBUG "g"
#else
#define SUFFIX_DEBUG ""
#endif

#define SUFFIX SUFFIX_THREADS SUFFIX_STATIC SUFFIX_DEBUG

LANGUAGE 0x9, 0x1 /* LANG_ENGLISH, SUBLANG_DEFAULT */

VS_VERSION_INFO VERSIONINFO
FILEVERSION TCL_MAJOR_VERSION,TCL_MINOR_VERSION,TCL_RELEASE_LEVEL,TCL_RELEASE
_SERIAL
PRODUCTVERSION TCL_MAJOR_VERSION,TCL_MINOR_VERSION,TCL_RELEASE_LEVEL,TCL_RELEA
SE_SERIAL
FILEFLAGSMASK 0x3fL
#ifdef DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "FileDescription", "Wish Application\0"
VALUE "OriginalFilename", "bwish" STRINGIFY(TCL_MAJOR_VERSION) STRI
NGIFY(TCL_MINOR_VERSION) SUFFIX ".exe\0"
VALUE "CompanyName", "bitWalk Co., Ltd.\0"
VALUE "FileVersion", TCL_PATCH_LEVEL
VALUE "LegalCopyright", "Copyright \251 2008 by bitWalk Co., Ltd.,
et al\0"
VALUE "ProductName", "bWish " TCL_VERSION " for Windows\0"
VALUE "ProductVersion", TCL_PATCH_LEVEL
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

//
// Icon
//


bwish ICON DISCARDABLE "bwish.ico"

// bwish.rc

適当なアイコンファイル bwish.ico が必要になります。下の関連資料に示した素材サイトなどからダウンロードして適当な ico ファイルを bwish.ico と名前を変えて使うか、アイコン作成ツールで用意してください。

関連資料で紹介した素材サイトは、利用条件が明記されていたので載せました。他の多くのサイトでもアイコン素材が公開されていますので、興味があれば検索してみてください。

最後に、Linux 用と Windows 用クロスコンパイルのメイクファイルを示します。

# Makefile

CC = gcc
RM = rm -f

TARGET = bwish

OBJEXT = o
TARGETEXT =

DEFS =
INCLUDE = -I/usr/include
CFLAGS = -Wall -O2 -std=c99 $(DEFS) $(INCLUDE)
LDFLAGS = -L/usr/lib
TK_LIBS = -ltk -ltcl
LIBS = $(TK_LIBS) -lX11 -lm

OBJS = \
bwish_main.$(OBJEXT) \
bwish_sub.$(OBJEXT)

all: $(TARGET)$(TARGETEXT)

$(TARGET)$(TARGETEXT): $(OBJS)
$(CC) -o $@ $(OBJS) $(LDFLAGS) $(LIBS)

.SUFFIXES: .$(OBJEXT)
.c.$(OBJEXT):
$(CC) -c $(CFLAGS) $< -o $@

$(OBJS): $(TARGET).h

clean:
$(RM) $(TARGET)$(TARGETEXT) $(OBJS) *~

# Makefile

MinGW クロスコンパイル環境でビルドするためのメイクファイル Makefile.win では、windres でリソースをコンパイルしますが、出力の拡張子は .res.o にしています。
なお、このサンプルでは、拡張した expr コマンドの算術関数の動作を確認するために、敢えて -mconsole スイッチをつけてリンクするようにしています。

# Makefile.win

PREFIX = i386-mingw32-
CC = $(PREFIX)gcc
RC = $(PREFIX)windres
RM = rm -f

TARGET = bwish

OBJEXT = o
TARGETEXT = .exe
RES = res.o
TARGET_RES = bwish.$(RES)

DEFS =
INCLUDE = -I/usr/local/i386-mingw32/include
CFLAGS = -Wall -O2 -std=c99 $(DEFS) $(INCLUDE)
LDFLAGS = -L/usr/local/i386-mingw32/lib
TK_LIBS = -ltk -ltcl
WIN32_LIBS = -lws2_32 -lgdi32 -lcomdlg32 -limm32 \
-lcomctl32 -lshell32 -luuid -lole32 -loleaut32
LDFLAGS_CONSOLE = -mconsole
LDFLAGS_WINDOW = -mwindows
LIBS = $(TK_LIBS) $(WIN32_LIBS) $(LDFLAGS_CONSOLE)

OBJS = \
bwish_main.$(OBJEXT) \
bwish_sub.$(OBJEXT) \
$(TARGET_RES)

all: $(TARGET)$(TARGETEXT)

$(TARGET)$(TARGETEXT): $(OBJS)
$(CC) -o $@ $(OBJS) $(LDFLAGS) $(LIBS)

.SUFFIXES: .$(OBJEXT)
.SUFFIXES: .$(RES)
.SUFFIXES: .rc

.c.$(OBJEXT):
$(CC) -c $(CFLAGS) $< -o $@

.rc.$(RES):
$(RC) -o $@ $<

$(OBJS): $(TARGET).h

clean:
$(RM) $(TARGET)$(TARGETEXT) $(OBJS) *~

# Makefile.win


ビルドと実行


Linux ではビルドして、bwish を実行すると、wish を実行したときと同様に、トップレベルウィジェットが表示され、コンソール上は % のプロンプトが表示され入力待ちになります。

$ make
gcc -c -Wall -O2 -std=c99 -I/usr/include bwish_main.c -o bwish_main.o
gcc -c -Wall -O2 -std=c99 -I/usr/include bwish_sub.c -o bwish_sub.o
gcc -o bwish bwish_main.o bwish_sub.o -L/usr/lib -ltk -ltcl -lX11 -lm
$ ./bwish
% expr cbrt(27)
3.0
%

Windows 用のクロスコンパイルでは、ビルド後に Tcl/Tk の bin ディレクトリへ bwish.exe をコピーして実行しても、コンソール上にプロンプトが表示されません。仕方がないので、ビルドしたバイナリを Windows へ移して動作確認をしました。コンソールは、コマンドプロンプトのウィンドウが表示されます。

$ make -fMakefile.win clean
rm -f bwish.exe bwish_main.o bwish_sub.o bwish.res.o *~
$ make -fMakefile.win
i386-mingw32-gcc -c -Wall -O2 -std=c99
-I/usr/local/i386-mingw32/include bwish_main.c -o bwish_main.o
i386-mingw32-gcc -c -Wall -O2 -std=c99
-I/usr/local/i386-mingw32/include bwish_sub.c -o bwish_sub.o
i386-mingw32-windres -o bwish.res.o bwish.rc
i386-mingw32-gcc -o bwish.exe bwish_main.o bwish_sub.o bwish.res.o
-L/usr/local/i386-mingw32/lib -ltk -ltcl -lws2_32 -lgdi32 -lcomdlg32 -limm32
-lcomctl32 -lshell32 -luuid -lole32 -loleaut32 -mconsole
$ cp bwish.exe ../mingw-tcltk-8.5.1-2/bin/
$ cd ../mingw-tcltk-8.5.1-2/bin/
$ ./bwish.exe
err:winedevice:ServiceMain driver L"Cdr4_2K" failed to load
err:winedevice:ServiceMain driver L"Cdralw2k" failed to load
fixme:font:WineEngCreateFontInstance Untranslated charset 255
fixme:comm:set_queue_size insize 4096 outsize 4096 unimplemented stub
fixme:imm:ImmReleaseContext (0x10026, 0x158f08): stub



このところ、立て続けにカスタマイズした wish のビルドの方法について説明をしてきましたが、いかがだったでしょうか。Tcl/Tk を拡張してアプリケーションを構築する際、wish や tclsh を直接拡張する方法の他に、拡張パッケージを作成し、標準の wish や tclsh からロードして拡張機能を利用する方法があります。拡張パッケージの作り方については、過去にホームページで掲載していた資料がありますので、それをベースに書き直して、あらためて現在のホームページの方に掲載しようと考えています。

関連情報


Tcl/Tk C Library
[1] Tcl Library
[2] Tk Library

ico ファイル素材&ツール
[1] [素材]Windows XP ファイル用アイコン
[2] @icon変換 - 画像とアイコンの相互変換ツール

C 言語
[1] プログラミング言語 C の新機能
 

5 件のコメント:

匿名 さんのコメント...

こんにちは。windows版はアイコンじゃアと喜んでクロスコンパイルしたのですが、エラーです。アイコンも用意してソースと同じフォルダに入れてあります。念のためTcl/Tk のソースファイルにある win/tclsh.rcをコピーしてから内容を書き換えてテストしてみましたが同じエラーです。bwish.rc の bwish ICON DISCARDABLE "bwish.ico" の行をコメントアウトしてコンパイルすると問題なくコンパイルできました(動作も問題ありません)。 何が問題なのでしょうか? 
参考:
$ echo $PATH
/usr/kerberos/bin:/usr/lib/ccache:/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin:/home/kn/bin

$ make -f Makefile.win
i386-mingw32-gcc -c -Wall -O2 -std=c99 -I/usr/local/i386-mingw32/include bwish_main.c -o bwish_main.o
i386-mingw32-gcc -c -Wall -O2 -std=c99 -I/usr/local/i386-mingw32/include bwish_sub.c -o bwish_sub.o
i386-mingw32-windres -o bwish.res.o bwish.rc
i386-mingw32-windres: can't open icon file `bwish.ico': No such file or directory
make: *** [bwish.res.o] エラー 1

bitWalk さんのコメント...

koji さん、こんにちは。

> i386-mingw32-windres: can't open
> icon file `bwish.ico': No such file
> or directory
> make: *** [bwish.res.o] エラー 1

このエラーはそのまま読めば、単純に bwish.ico が無い(開けない)とエラーを出している以上のことはなにも判りませんね。

bwish.ico が確かにディレクトリ内に存在するとすれば、考えられることは、何らかの理由で読み込み権限が bwish.ico にないことぐらいでしょうか。でも、その場合、エラーメッセージが微妙に違うんですよね。

$ i386-mingw32-windres -o bwish.res.o bwish.rc
i386-mingw32-windres: can't open icon file `bwish.ico': Permission denied

一応念のため、コンパイルするディレクトリ内を示しておきます。

$ ls
Makefile RCS bwish.ico bwish_main.c
Makefile.win bwish.h bwish.rc bwish_sub.c

この状態で次のように入力すればリソースのコンパイルは出来ると思うのですが…。

$ i386-mingw32-windres -o bwish.res.o bwish.rc

単純なミスをしていないか、再度確認してみてください。この程度のことしか助言ができず、申し訳ありません。

ちなみに PATH の設定は、koji さんとほぼ同じだと思います。

echo $PATH
/usr/kerberos/bin:/usr/lib/ccache:
/usr/local/bin:/usr/bin:/bin:
/usr/X11R6/bin:/home/bitwalk/bin

最後の /home/bitwalk/bin には、
IEs4Linux が作成した ie6 へのシンボリックリンクがあるだけです。ご参考まで。

匿名 さんのコメント...

こんにちは。 アイコンがつきましたー。 感激です。

とても複雑な現象でした。思いのよらないような。想像を絶するような。他の人には真似のできないような。 、、、とても単純なミスでした。 

lsではわかりませんでしたがll (long list)で判明。なんとアイコンのファイル名の先頭にスペースが!(きれいに並んでいないので。)

お手数をお掛けして、申し訳ありませんでした。
(受けをねらったのではありませんが、笑ってください、、、はい。)

bitWalk さんのコメント...

koji さん、こんにちは(こんばんは)。
とにかく、原因が判って良かったですね。

ところで、Tcl/Tk の拡張に関する説明ページの準備は、週末まで手をつけられそうにありません。
と言うのは、現在 MinGW Cross Compiler プロジェクトの方でライブラリ群をどんどん増やしているからです。

実はクロスプラットフォーム対応な C++ GUI Toolkit みたいなのでクロスコンパイル出来るようにするため、現在、基本的なライブラリの不具合を直したり、基本ライブラリを追加したりして、クロスコンパイルに明け暮れています。

近い将来、Eclipse を使ってクロスコンパイル環境を利用したアプリケーション開発が出来ればいいなと思っています。そうそう、Tcl/Tk の拡張パッケージの作成も Eclipse で出来ますよ。

匿名 さんのコメント...

お忙しいなか、いつも感謝しております。 早速 Eclipse をさわってみます。