Ffidl

Ffidlは、Tclから直接ダイナミックリンクライブラリ内のCルーチンを呼び出すためのパッケージです。
ダイナミックリンクライブラリであれば、システムのものや自作のものまでなんでも呼び出せます。

Win32環境であれば、DLL callerというパッケージで同様のことが実現できますが、
最近、DLL callerのページがアクセスできず、どうやら閉鎖されてしまったようです?。
これからは、マルチプラットフォーム対応のFfidlを使っていくことになります。

ダイナミックリンクライブラリ内のCルーチンを直接呼び出すメリットはいくつかあると思われます。

後者は、Tcltestパッケージを併用することで回帰テストを行うことができます。

::ffidl::callout

ダイナミックリンクライブラリのCルーチンを呼び出すには::ffidl::calloutを使います。
::ffidl::calloutは、前以てCルーチンとのインターフェースとなるTclプロシジャを作成します。
::ffidl::calloutコマンドの形式は以下の通りです。

package require Ffidl

::ffidl::callout name {?arg_type1 ...?} return_type address ?protocol?

nameは、Tclプロシジャの名前を指定します。
arg_typeは、Cルーチンの引数の型を指定します。
return_typeは、Cルーチンの返却値の型を指定します。
addressは、ダイナミックリンクライブラリのロードアドレスを指定します。
protocolは、cdeclかstdcallを指定します。(cdeclがデフォルト)

arg_typeとreturn_typeに指定できる型は以下の通りです。(+は指定可, -は指定不可)

callout callback elt type definition
arg ret arg ret
-+-+-voidvoid
+++++charsigned char
+++++unsigned charunsigned char
+++++shortsigned short int
+++++unsigned shortunsigned short int
+++++intint
+++++unsignedunsigned int
+++++longsigned long int
+++++unsigned longunsigned long int
+++++floatfloat
+++++doubledouble
+++++long doublelong double
+++++sint8signed 8 bit int
+++++uint8unsigned 8 bit int
+++++sint16signed 16 bit int
+++++uint16unsigned 16 bit int
+++++sint32signed 32 bit int
+++++uint32unsigned 32 bit int
+++++sint64signed 64 bit int
+++++uint64unsigned 64 bit int
+++++pointerpointer as an integer value
++++-pointer-objpointer from Tcl_Obj
+++--pointer-utf8pointer from String
+++--pointer-utf16pointer from Unicode
+----pointer-bytepointer from ByteArray
+----pointer-varpointer from ByteArray stored in
variable. If the ByteArray is shared,
then an unshared copy is made and
stored back into the
variable.
+----pointer-proc pointer to callback function constructed to call a
Tcl proc.
eltは、その型が構造体のメンバに成り得るか否かを示します。

以下の例は、Win32のuser32.dll内のGetWindowTextAルーチンを呼び出しています。
GetWindowTextAルーチンは、int型の値を返すルーチンで、
引数に、ウィンドウハンドル、文字列を返却するためのバッファのポインタ、バッファサイズを取ります。

package require Ffidl

::ffidl::callout GetWindowText {int pointer-var int} int [::ffidl::symbol user32.dll GetWindowTextA]
set buf [binary format x256]
GetWindowText 0x105EE buf 256
binary scan $buf A* buf
puts stdout $buf

buf変数には、ウィンドウハンドルで指定したウィンドウのタイトル文字列が返却されます。
binaryコマンドについてはこちらです。

::ffidl::callback

::ffidl::callbackは、::ffidl::calloutの引数に渡すためのコールバックルーチンを生成します。
実際に、生成されたコールバックルーチンは、Tclプロシジャに置き換えられます。
::ffidl::callbackコマンドの形式は、以下の通りです。

package require Ffidl

::ffidl::callback name {?arg_type1 ...?} return_type ?protocol?

nameは、Tclプロシジャの名前を指定します。
arg_typeは、Cルーチンの引数の型を指定します。
return_typeは、Cルーチンの返却値の型を指定します。
protocolは、cdeclかstdcallを指定します。(cdeclがデフォルト)

以下の例は、Win32のEnumWindowsルーチンに渡すコールバックルーチンを定義しています。
EnumWindowsプロシジャは、コールバックルーチンEnumWCBを呼び出して、
現在開いている全てのウィンドウのウィンドウハンドル値を表示します。

package require Ffidl

::ffidl::callout EnumWindows {pointer-proc int} int [::ffidl::symbol user32.dll EnumWindows]
::ffidl::callback EnumWCB {int int} int
proc EnumWCB {hwnd lparam} {
	puts stdout $hwnd
	return 1
}
EnumWindows EnumWCB 0

::ffidl::typedef

Ffidlは、構造体を使う関数にも対応しています。
前述の基本タイプの他に、ユーザ定義タイプ(構造体)を定義できます。

例えば、以下のようなC言語の構造体は、

typedef struct _OSVERSIONINFO{
    DWORD dwOSVersionInfoSize; /* この構造体のサイズ */
    DWORD dwMajorVersion;      /* メジャーバージョン */
    DWORD dwMinorVersion;      /* マイナバージョン */
    DWORD dwBuildNumber;       /* ビルド番号 */
    DWORD dwPlatformId;        /* プラットフォーム番号 */
    TCHAR szCSDVersion[ 128 ]; /* 追加情報 */
} OSVERSIONINFO; 

このように::ffidl::typedefコマンドでユーザ定義タイプを定義できます。

::ffidl::typedef verinfo unsigned unsigned unsigned unsigned unsigned
※配列の部分は長くなるので定義を割愛する。

以下の例は、ユーザ定義タイプverinfoを使って、Win32のGetVersionExAでOSのバージョンを取得しています。

package require Ffidl

::ffidl::typedef verinfo unsigned unsigned unsigned unsigned unsigned
::ffidl::callout GetVersionEx {pointer-var} int [::ffidl::symbol kernel32.dll GetVersionExA]
set buf [binary format x[::ffidl::info sizeof verinfo]x128@0i 148]
GetVersionEx buf
binary scan $buf [::ffidl::info format verinfo] size major minor build plat
puts stdout "version = $major.$minor.$build"

buf変数には、構造体イメージでOSのバージョン情報が返却されます。

このようにして、Ffidlを使うとTclから直接ダイナミックリンクライブラリ内のCルーチンを呼び出すことが出きます。
非常に手っ取り早い方法ですが、引数の指定ミス等によりアプリケーションをクラッシュさせるリスクを伴います。
Ffidlを使用する際は十分注意して使いましょう。

Win32版Ffidl

Win32版のFfidlパッケージを以下からダウンロードできるようにしました。


Ffidl