What | ffidl |
Where | http://elf.org/ffidl/ |
Where | http://rutherglen.ics.mq.edu.au/~steffen/tcltk/ffidl/doc (dead) |
version | 0.6 |
Updated | ??/2006 |
Contact | mailto:[email protected] (Roger E. Critchlow Jr.) |
ffidl, "Foreign Function Interface with Dynamic Loading", by Roger E Critchlow, is an extension which allows pure Tcl extensions to invoke functions in shared libraries without having to create any glue code. Ffidl supports calls in both directions between C/C++ and Tcl, and operates on a variety of platforms.
The Tcl command specifies a function name, a library, a list of argument types, and a return type, and ffidl takes care of the details of setting up the arguments and invoking the C function. Using ffidl, a pure Tcl wrapper to a shared library can be created.
Avaliable for Linux, Windows, and Mac OS X.
modifictions resulting in version 0.6 were contributed by DAS.
For MacOS users, an unofficial update version 0.6.1 can be found at
Note that the binary package listed at http://elf.org/ffidl/ does not work on Tiger/Leopard (? only for PowerPC ?)
A binary package for Win/Linux/Mac, unoficially versioned 0.6.1.1, is available at the irrational-numbers project
Note that a little BUG has been fixed ("Ffidlrt.tcl does not work if installed in a path name with whitespaces").
DAS - I have updated Ffidl to support Darwin/Mac OS X, as well as modernized it in other ways:
the testsuite is just the existing tests wrapped into .test files.
[http://rutherglen.ics.mq.edu.au/~steffen/tcltk/ffidl/ffidl.tar.gz%|%source tarball%|%]
Ffidl either uses unmodified ffcall 1.10 or the HEAD of libffi from the gcc CVS with a small patch to the buildsystem to make it build standalone (i.e. without relying on the gcc sourcetree structure)
Note that libffi is under BSD license but ffcall is GPLd.
APN: In response to a question on c.l.t DAS replied: no, ffidl uses either libffi or ffcall, but never both. If you use my 0.6 update to ffidl, the choice of libffi vs ffcall is a compile time option (with libffi being the default for GPL avoidance reasons). A ffidl binary built with the default configure options (or with -enable-libffi) will contain only BSD licensed code, whereas a ffidl built with --enable-ffcall will indeed become GPLd as a whole by virtue of static linking with ffcall.
The diff of ffidl.c against the 0.5 version
Mac OS X ffidl binaries (with libffi) are available as tarball installer package or tarball .
A Windows ffidl binary (with libffi) built with MinGW on WIndowsXP in VirtualPC is now also available .
I have tested & exercised this quite extensively on Mac OS X 10.3 (with both libffi and ffcall), and have verified that it builds and passes the testsuite on Windows XP with MinGW (in Virtual PC on my Mac...).
I have also built ffidl and run the testsuite on all the machines in the sourceforge compilefarm :
hosts passing the test suite with both libffi and ffcall:
host core dumping when running the test suite (with both libffi and ffcall):
hosts passing the test suite with ffcall, but where building the libffi library fails:
the last two may be fixable by reverting to an earlier version of libffi
from Rolf Schroedter on c.l.t
--- file foo.h: ---
int foo_init( int adr, int log ); int foo_done( void ); int foo_info( FOO_INFO *infoPtr ); /* FOO_INFO is a structure */ int foo_open( const char *port );
--- file foo.tcl: ---
load ffidl05.dll set DLL foo.dll ffidl::callout foo_init {int int} int [ffidl::symbol $DLL foo_init] ffidl::callout foo_done {} int [ffidl::symbol $DLL foo_done] ffidl::callout foo_info {pointer-var} int [ffidl::symbol $DLL foo_info] ffidl::callout foo_open {pointer-utf8} int [ffidl::symbol $DLL foo_open]
Explain Rolf Schroedter's screensaver example
#Rolf Schroedter #German Aerospace Center #Institute of Space Sensor Technology and Planetary Exploration load ffidl05.dll ffidl::callout dll_FindWindow {pointer-utf8 pointer-utf8} int [ffidl::symbol user32.dll FindWindowA] ffidl::callout dll_FindWindowTitle {int pointer-utf8} int [ffidl::symbol user32.dll FindWindowA] ffidl::callout dll_FindWindowClass {pointer-utf8 int} int [ffidl::symbol user32.dll FindWindowA] ffidl::callout dll_SetWindowPos {int int int int int int int} int [ffidl::symbol user32.dll SetWindowPos] ffidl::callout dll_SystemParametersInfo {int int pointer int} int [ffidl::symbol user32.dll SystemParametersInfoA] proc FindWindow { class title } { if { [string length $class] == 0 } { dll_FindWindowTitle 0 $title } elseif { [string length $title] == 0 } { dll_FindWindowClass $class 0 } else { dll_FindWindow $class $title } } proc SetWindowPos { hwnd after x y cx cy {flags 0} } { array set VAL {TOP 0 BOTTOM 1 TOPMOST -1 NOTOPMOST -2} set iAfter $VAL([string toupper $after]) dll_SetWindowPos $hwnd $iAfter $x $y $cx $cy $flags } proc SetupScreenSaver { bool } { dll_SystemParametersInfo 97 $bool 0 0 ;# SPI_SCREENSAVERRUNNING=97 } proc exit? {} { set answer [tk_messageBox -message "Really quit?" -type yesno -icon question] switch -- $answer { yes { SetupScreenSaver 0 exit } no {} } } proc ScreenSaver {win} { set size(X) [winfo screenwidth .] set size(Y) [winfo screenheight .] toplevel $win wm title $win "TclScreenSaver" ;# to find the window wm overrideredirect $win true $win configure -relief flat -bd 0 $win configure -cursor hand2 ;# Ohne cursor ??? update idletasks ;# virtually display $win, allows window to be found set hwnd [FindWindow "" "TclScreenSaver"] set res1 [SetWindowPos $hwnd TOPMOST 0 0 $size(X) $size(Y)] ;# ever makes full screen set res2 [SetupScreenSaver 1] canvas $win.c -background yellow -width $size(X) -height $size(Y) -relief flat -bd 0 pack $win.c -expand yes -fill both focus -force $win bind $win <Key> exit? bind $win <Motion> {} } wm withdraw . ScreenSaver .scr
from Rob Hegt on c.l.t, Subject: solution for regaining focus from OpTcl hosted ActiveX control:
load lib/ffidl05.dll ffidl::callout dll_SetFocus {int} int [ffidl::symbol user32.dll SetFocus] proc GrabFocus {args} {dll_SetFocus [winfo id .]}
Then just bind GrabFocus to some event. In the post he uses <button> .
bind . <Button> +GrabFocus
by daapp
C declarations:
typedef short I16; typedef unsigned short U16; I16 _7443_initial(I16 *existCards); I16 _7443_close(void); I16 _7443_version_info(I16 CardNo, U16 *HardwareInfo, U16 *SoftwareInfo, U16 *DriverInfo); I16 _7443_d_output(I16 CardNo, I16 Ch_No, I16 value);
Tcl code:
namespace eval 7443 { variable dll_name PPCI7443.dll ffidl::callout _initial {pointer-var} sint16 \ [ffidl::symbol $dll_name _7443_initial] ffidl::callout close {} void [ffidl::symbol $dll_name _7443_close] ffidl::callout _version_info {sint16 pointer-var pointer-var pointer-var} \ sint16 [ffidl::symbol $dll_name _7443_version_info] ffidl::callout d_output {sint16 sint16 sint16} sint16 \ [ffidl::symbol $dll_name _7443_d_output] } # varName should containt quantity of available cards proc 7443::initial {varName} { upvar $varName existsCards set cards [binary format s 0] set result [_initial cards] binary scan $cards s existsCards return $result } # return: {errorCode hardwareInfo softwareInfo driverInfo} proc 7443::version_info {cardNumber} { set hardwareInfo [binary format s 0] set softwareInfo [binary format s 0] set driverInfo [binary format s 0] set result [_version_info $cardNumber hardwareInfo softwareInfo driverInfo] binary scan $hardwareInfo s hi binary scan $softwareInfo s si binary scan $driverInfo s di return [list $result $hi $si $di] }
DAS - The script below is a brief demo of Ffidl's usefulness for accessing Carbon APIs on Mac OS X, in particular it shows a carbon event handler implemented in tcl. It also shows how to access Tk APIs via the new [::ffidl::stubsymbol].
The demo installs the the system wide hotkey Cmd-Shift-A, pressing it makes the blue labelframe flash red. Note how the hotkey works even with Wish not in front...
#!/bin/sh # # Let's ffidl with Carbon HotKeys! # # Copyright (c) 2005, Daniel A. Steffen <[email protected]> # BSD License: c.f. <http://www.opensource.org/licenses/bsd-license> # #\ exec wish $0 "$@" package require Tk package require Ffidl namespace eval carbon { ::ffidl::typedef EventHotKeyID {unsigned long} uint32 ::ffidl::typedef EventTypeSpec uint32 uint32 ::ffidl::typedef EventTargetRef pointer ::ffidl::typedef OSStatus sint32 ::ffidl::callout RegisterEventHotKey {uint32 uint32 EventHotKeyID EventTargetRef \ uint32 pointer-var} OSStatus \ [::ffidl::symbol Carbon.framework/Carbon RegisterEventHotKey] ::ffidl::callout GetApplicationEventTarget {} EventTargetRef \ [::ffidl::symbol Carbon.framework/Carbon GetApplicationEventTarget] ::ffidl::callout InstallEventHandler {EventTargetRef pointer-proc uint32 pointer-byte \ pointer pointer-var} OSStatus \ [::ffidl::symbol Carbon.framework/Carbon InstallEventHandler] ::ffidl::callout XKeysymToKeycode {pointer {unsigned long}} {unsigned long} \ [::ffidl::stubsymbol tk intXLibStubs 35]; #XKeysymToKeycode ::ffidl::callout TkStringToKeysym {pointer-utf8} {unsigned long} \ [::ffidl::stubsymbol tk intStubs 86]; #TkStringToKeysym } proc hotkeyHandler {handlerCallRef event userData} { .l configure -bg red after 200 .l configure -bg blue return 0 } proc installHotKey {key} { labelframe .l -width 100 -height 100 -bg blue pack .l ::ffidl::callback hotkeyHandler {pointer pointer pointer} OSStatus set EventHandlerRef [binary format I 0] set res [carbon::InstallEventHandler [carbon::GetApplicationEventTarget] hotkeyHandler 1 \ [binary format a4I keyb 5] 0 EventHandlerRef] if {$res} {puts stderr "InstallEventHandler failed: $res"; exit -1} set keycode [expr {[carbon::XKeysymToKeycode 0 [carbon::TkStringToKeysym $key]]>>16}] set modifiers [expr {1 << 8 | 1 << 9}]; #Cmd-Shift #set modifiers [expr {1 << 8}]; #Cmd set EventHotKeyRef [binary format I 0] set res [carbon::RegisterEventHotKey $keycode $modifiers [binary format a4I wish 1] \ [carbon::GetApplicationEventTarget] 0 EventHotKeyRef] if {$res} {puts stderr "RegisterEventHotKey failed: $res"; exit -1} } installHotKey A
DAS - How to set the application menu name at runtime on Mac OS X using undocumented Apple SPI:
package require Tk package require Ffidl 0.6 ::ffidl::callout CPSSetProcessName {pointer-byte pointer-utf8} sint32 \ [::ffidl::symbol /System/Library/Frameworks/ApplicationServices.framework/Frameworks/CoreGraphics.framework/CoreGraphics CPSSetProcessName] CPSSetProcessName [binary format I2 {0 2}] "MyCoolApp"
and how to show or hide the current application: (also see tclCarbonProcesses)
::ffidl::callout ShowHideProcess {pointer-byte int} sint32 [::ffidl::symbol Carbon.framework/Carbon ShowHideProcess] ShowHideProcess [binary format I2 {0 2}] 1; #Show ShowHideProcess [binary format I2 {0 2}] 0; #Hide
DAS - Another Mac OS X example in response to a question on c.l.t. from Steven Myers [L1 ]
How to set the current application's dock tile from a png file (c.f. API docs [L2 ]):
#!/bin/sh # # Set the Dock Tile from a png file with Ffidl # # Copyright (c) 2005, Daniel A. Steffen <[email protected]> # BSD License: c.f. <http://www.opensource.org/licenses/bsd-license> # #\ exec wish $0 "$@" package require Tk package require Ffidl namespace eval carbon { proc api {name argl ret lib} {::ffidl::callout $name $argl $ret \ [::ffidl::symbol $lib.framework/$lib $name]} proc type {name type} {::ffidl::typedef $name $type} proc const {name args} {variable {}; eval set [list ($name)] $args} type OSStatus sint32 type bool int type CFURLRef pointer type CGDataProviderRef pointer type CGImageRef pointer type CGColorRenderingIntent int const kCGRenderingIntentDefault 0 api CFURLCreateFromFileSystemRepresentation {pointer pointer-utf8 \ int bool} CFURLRef CoreFoundation api CFRelease {pointer} void CoreFoundation api CGDataProviderCreateWithURL {CFURLRef} CGDataProviderRef \ ApplicationServices api CGImageCreateWithPNGDataProvider {CGDataProviderRef pointer \ bool CGColorRenderingIntent} CGImageRef ApplicationServices api SetApplicationDockTileImage {CGImageRef} OSStatus Carbon proc setDockTileToPNG {pngFile} { if {[file exists $pngFile]} { set url [CFURLCreateFromFileSystemRepresentation 0 $pngFile \ [string bytelength $pngFile] 0] if {$url} { set dp [CGDataProviderCreateWithURL $url] if {$dp} { set img [CGImageCreateWithPNGDataProvider $dp 0 1 \ [const kCGRenderingIntentDefault]] if {$img} { SetApplicationDockTileImage $img CFRelease $img } CFRelease $dp } CFRelease $url } } } } carbon::setDockTileToPNG test.png
DAS - Yet another Mac OS X example on how to find the user's preferred locale (as set in system preferences 'International') via the CFLocale API:
Note that kroc has since found a way to get this info without resorting to Ffidl: [exec defaults read NSGlobalDomain AppleLocale]
#!/bin/sh # # Ffidling CFLocale # # Copyright (c) 2005, Daniel A. Steffen <[email protected]> # BSD License: c.f. <http://www.opensource.org/licenses/bsd-license> # #\ exec tclsh $0 "$@" package require Ffidl 0.6 namespace eval corefoundation { proc api {name argl ret} {::ffidl::callout $name $argl $ret \ [::ffidl::symbol CoreFoundation.framework/CoreFoundation $name]} api CFLocaleCopyCurrent {} pointer api CFLocaleGetIdentifier pointer pointer api CFStringGetLength pointer sint32 ::ffidl::typedef CFRange sint32 sint32 api CFStringGetCharacters {pointer CFRange pointer-var} void api CFRelease pointer void proc getLocaleIdentifier {} { set cfloc [CFLocaleCopyCurrent] set cfstr [CFLocaleGetIdentifier $cfloc] set len [CFStringGetLength $cfstr] set buf [binary format x[expr {2*$len}]] set range [binary format [::ffidl::info format CFRange] 0 $len] CFStringGetCharacters $cfstr $range buf CFRelease $cfloc encoding convertfrom unicode $buf } } puts [corefoundation::getLocaleIdentifier]
A wrapper for the Microsoft API-Call, NetMessageBufferSend :
package require Ffidl 0.5 ffidl::callout dll_netSend {pointer-utf16 pointer-utf16 pointer-utf16 pointer-utf16 long} long \ [ffidl::symbol netapi32.dll NetMessageBufferSend] proc netSend {dest mesg {srv {}}} { set from $::tcl_platform(user) # or: # set from [info host] # (only these two alternatives seems to work...) return [dll_netSend $srv $dest $from $mesg [expr [string length $mesg]*2]] }
This is to send small messages to computers or users or work groups, which will immediately pop-up on the screen (using NT/2000/XP, if the messenger service is started, or with DOS/Win3x/9x, if winpopup/netpop.exe is running) - a task often needed by administrators! Note: The data-type-definitions are somewhat tricky....
With the srv Argument it is theoretically possible to specify the system which will perform the sending task - (example: \\machine1), but this involves some complex security aspects...
contributed by FM
expand strings like `%ProgramFiles%:
ffidl::callout dll_ExpandEnvironmentStringsForUser \ {int pointer-utf16 pointer-utf16 long} int \ [ffidl::symbol Userenv.dll ExpandEnvironmentStringsForUserW] proc {ExpandEnvironmentStringsForUser} {WPath} { set TclPath [string repeat \u0000 300] if [dll_ExpandEnvironmentStringsForUser 0 $WPath $TclPath 300] { set ix [string first \u0000 $TclPath] if {$ix > 0} { return [string range $TclPath 0 [expr {$ix - 1}]] } else { return {} } } else { return {} } }
let's try it:
ExpandEnvironmentStringsForUser {%ProgramFiles%\windows media player\wmplayer.exe}
result:
C:\Program Files\windows media player\wmplayer.exe
DLR Including ffidl in the core would be a huge boost to Tcl, and specially, Tcllib, as many modules could be written in pure Tcl. Part of the success of Mono/.NET is its P/Invoke feature which allows it to effortlessly wrap native libraries. The Mono implementation uses (or at least used to do) ffidl at its core.
Lectus: This is a must have functionality in Tcl. Any chance of moving it to core?
[L3 ].
The examples are quite impressive. RT