Version 40 of Garuda

Updated 2021-10-20 10:00:55 by MHo

Garuda (Eagle Package for Tcl) is part of the Eagle project. It is a stubs-enabled native package for Tcl that allows Eagle to be loaded and used by Tcl 8.4 or higher on Windows.

While Garuda can be used as a generic conduit between Tcl and the .NET Framework, it was originally designed to be used with Eagle.

When Garuda is used with Eagle, ideally the binaries should be built from the same source checkout. As of Beta 33, this is a hard requirement.

As of 2016-10-08 (i.e. pre-Beta 39), Garuda supports an alternate package name "dotnet" (e.g. [package require dotnet]), which can be used to load the package without starting the CLR (or the managed bridge to Eagle). Later, the [garuda clrstart] and [garuda startup] sub-commands may be used to perform the CLR startup and managed bridge setup.

As of 2015-12-29 (i.e. pre-Beta 35), Garuda supports using the "private Tcl stubs " mechanism originally created by Jan Nijtmans for use by Fossil. This optional, compile-time feature eliminates the need to have a compiled Tcl stubs library available for the target platform.

As of 2014-07-26 (i.e. pre-Beta 31), Garuda supports loading into TclKit or similar statically linked Tcl environment. In order for this to work, the following snippet must be evaluated prior to evaluating "package require Garuda":

namespace eval ::Garuda {
  variable methodFlags 0x40; # METHOD_PROTOCOL_V1R2
}

Now available via the ActiveState Teapot repository, here . To install the Garuda package from there, the command teacup install Garuda can be used. MHo 2014-07-28:

The ActiveState Teapot may not (and as of this writing does not) have the most recently released version of Garuda available. The best place to obtain it from is the official Eagle web site.

C:\Windows\system32>teacup install Garuda
Resolving Garuda ... Not found in the archives. Note: Names are case-sensitive.

        Aborting installation, was not able to locate the requested
        entity.

C:\Windows\system32>teacup install garuda
Resolving garuda ... Not found in the archives. Note: Names are case-sensitive.

        Aborting installation, was not able to locate the requested
        entity.

JJM (2014-07-28): Here, it shows up as listed:

package     Garuda                             1.0             win32-ix86

JJM (2014-07-28): The problem might be that you are trying for a 64-bit version, which ActiveState does not build?


JJM (2014-11-13): ActiveState now builds the 64-bit version of Garuda.

The 64-bit ActiveState build is here .

The 32-bit ActiveState build is here .


From the README:

The sources may be built from inside the Microsoft Visual Studio 2005 (or higher) integrated development environment. Alternatively, they may be built using the corresponding command line build environment. Installing this package requires Tcl 8.4 or higher. To install Garuda, copy the distribution files to a directory and make sure that directory is listed in the Tcl "auto_path" variable.


Loading and using this package requires Tcl 8.4 or higher and the latest release of Eagle. To load Garuda, execute the following Tcl command:

package require Garuda

By default, this will load and startup the CLR, setup the bridge to Eagle, and provide the package to the Tcl interpreter.

After the package has been loaded, the [garuda] command can be used to query and control various aspects of the bridge between Eagle and Tcl.

After the bridge between Eagle and Tcl has been setup, the [eagle] command can be used to evaluate Eagle scripts.


Here is an example of how to load Garuda in verbose mode:

#
# This will load Garuda and start the CLR in verbose mode, showing
# plenty of diagnostic output useful in troubleshooting.
#
namespace eval Garuda {set verbose true; set logCommand tclLog}

package require Garuda

Here is an example of how to use a CLR class:

#
# This will load Garuda and use it to print "Hello World" to the
# console.
#
package require Garuda

eagle [list object invoke System.Console WriteLine "Hello World"]

Here is a more complex example, using Tk and CLR classes together:

package require Tk
package require Garuda

wm withdraw .

if {![info exists i]} then { set i 0 }; incr i

set toplevel [toplevel .example$i]
bind $toplevel <F2> {console show}

set script {
  #
  # NOTE: This script can use any of the commands provided by
  #       Eagle (e.g. [object invoke] to invoke .NET Framework
  #       objects).
  #
  proc handleClickEvent { sender e } {
    if {[tcl ready]} then {
      msgBox [appendArgs "Tcl version is: " \
          [tcl eval [tcl primary] info patchlevel] \n \
          "Eagle version is: " [info engine patchlevel]] About
    } else {
      msgBox "Tcl is not ready." About
    }
  }

  object load -import System.Windows.Forms
  interp alias {} msgBox {} object invoke MessageBox Show

  set form [object create -alias Form]
  $form Text WinForm; $form Show

  set button [object create -alias Button]

  $button Left [expr {([$form ClientSize.Width] - [$button Width]) / 2}]
  $button Top [expr {([$form ClientSize.Height] - [$button Height]) / 2}]

  $button Text "Click Here"
  $button add_Click handleClickEvent

  object invoke $form.Controls Add $button
}

set button [button $toplevel.run -text "Click Here" \
    -command [list eagle $script]]

pack $button -padx 20 -pady 20 -ipadx 10 -ipady 10

MHo 2021-10-20 This example does not work for me. I got this error:

{type "Form" not found} {expected type value but got "Form"}
{type "Form" not found} {expected type value but got "Form"}
    while executing
"eagle {
  #
  # NOTE: This script can use any of the commands provided by
  #       Eagle (e.g. [object invoke] to invoke .NET Framework
  #       obj..."
    invoked from within
".example1.run invoke"
    ("uplevel" body line 1)
    invoked from within
"uplevel #0 [list $w invoke]"
    (procedure "tk::ButtonUp" line 24)
    invoked from within
"tk::ButtonUp .example1.run"
    (command bound to event)

Here are some examples of using the Garuda meta-command itself for introspection (i.e. garuda):

package require Garuda

garuda clrappdomainid; # returns "1"
garuda clrversion;     # returns "v2.0.50727" or "v4.0.30319"
garuda clrrunning;     # returns "0" or "1"

#
# NOTE: In the following command output the zeros should be
#       replaced with the actual Fossil checkout hash and
#       date/time.  Currently, the returned output will be
#       something like the following (but without any
#       line-breaks):
#
#       Garuda 1.0 0000000000000000000000000000000000000000
#       {0000-00-00 00:00:00 UTC}
#
garuda packageid

#
# WARNING: The [garuda dumpstate] sub-command is intended for
#          troubleshooting purposes only and the output may
#          vary wildly from release to release.  Currently,
#          the returned output will be something like the
#          following (but without any line-breaks):
#
#          packageMutex 0x01464140 hPackageModule 0x558D0000
#          packageFileName {C:\path\to\Garuda.dll} lTclStubs 1
#          hTclModule 0x55930000 pTclStubs 0x00000000
#          pClrRuntimeHost 0x00061508 bClrStarted 1
#          bBridgeStarted 1
#
#          The actual output from the "dumpstate" sub-command
#          will be a valid list (actually a dictionary).
#
garuda dumpstate

Here are some examples of using the Garuda meta-command itself for CLR control operations (i.e. garuda):

#
# NOTE: This example disables the automatic starting of the CLR
#       and managed bridge, allowing full manual control.
#
namespace eval Garuda {set verbose true; set logCommand tclLog}
namespace eval Garuda {set startClr false; set startBridge false}

package require Garuda

#
# NOTE: This will attempt to start the CLR in the process.
#
garuda clrstart

#
# NOTE: This will attempt to setup the managed bridge, using the
#       current Tcl interpreter.
#
garuda startup

#
# NOTE: This will attempt to send a "control" signal to the managed
#       bridge using the current Tcl interpreter.
#
garuda control

#
# NOTE: This will attempt to execute the specified managed method
#       (which must conform to the method prototype "public static
#       int MethodName(string argument)").
#
garuda clrexecute {C:\path\to\Eagle.dll} Eagle._Tests.Default TestMethod 1234

#
# NOTE: This will attempt to remove the current Tcl interpreter from
#       the managed bridge.
#
garuda detach

#
# NOTE: This will attempt to shutdown the managed bridge using the
#       current Tcl interpreter.
#
garuda shutdown

#
# NOTE: This will attempt to stop and unload the CLR from the current
#       process.  This action cannot be reversed without restarting
#       the process.
#
garuda clrstop

kap 2015-02-04: Given a C# object 'foo' created as such, from the class 'Foo':

eagle {set foo [object create -alias Foo]}

The class 'Foo' has a method 'Bar' which returns a List<String>. When invoked by Eagle as so:

eagle {$foo Bar}

The string 'System.Collections.Generic.List`1System.String' is printed. Is there a way to easily serialize the contents of this to a string, within Eagle, such that Tcl could easily work with it? Like a Tcl list of strings?

JJM The easiest way is to create an Eagle._Containers.Public.StringList instance, passing $foo into the constructor, and then calling ToString on that, e.g.:

eagle {
  #
  # NOTE: Create an instance of the System.Collections.Generic.List<string>
  #       class, which is built into the .NET Framework.  This will be passed
  #       into the constructor for the Eagle._Containers.Public.StringList
  #       class (below).
  #
  set bar [object create -alias \
      System.Collections.Generic.List`1\[System.String\]]

  #
  # NOTE: Add some example data to the newly created list.
  #
  $bar Add 1; $bar Add 2; $bar Add 3

  #
  # NOTE: Create an instance of the Eagle._Containers.Public.StringList class,
  #       using the generic list created above.
  #
  set res [object create -alias StringList $bar]

  #
  # NOTE: The StringList.ToString method always returns a properly formatted
  #       Tcl list.  This has the effect of "serializing the contents of the
  #       list passed to the StringList constructor to a string".
  #
  $res ToString
}

JJM FYI, the -create option forces an opaque object handle to be created, instead of returning the result of the ToString method on the object. The -alias option forces a command alias to be created for the new opaque object handle.


MG Is there any documentation on using Garuda anywhere beyond the small amount of info on this page?


MHo 2015-10-01 Tcl 8.6.4:

% package require Garuda
ICLRRuntimeHost_ExecuteInDefaultAppDomain: failure (code 0x80070002).
ICLRRuntimeHost_ExecuteInDefaultAppDomain: failure (code 0x80070002).

JJM 2015-10-01: Just tried the same thing here and it works fine. Do you have an "Eagle.dll" file next to your "Garuda.dll"? If you installed from the Teapot, you should.

MHo Sorry, I had some old beta in my search path. After removing and doing a fresh teacup install Garuda the package require works... Thank you.

MHo 2017-11-13 Me again. Just tried this with the latest package from a 64bit Tclkit-Shell:

set auto_path [linsert $auto_path 0 [file normalize .]]
package require Garuda

which results in that:

d:\home\Hoffmann\pgm\tcl\usr\Tst\garuda>tclkitsh gtest1.tcl
couldn't load library "D:/home/Hoffmann/pgm/tcl/usr/Tst/garuda/Garuda1.0/Garuda.dll": invalid argument
    while executing
"load $packageBinaryFileName $packageName"
    (procedure "setupAndLoad" line 111)
    invoked from within
"setupAndLoad $packagePath"
    (in namespace eval "::Garuda" script line 1410)
    invoked from within
"namespace eval ::Garuda {
  #############################################################################
  #**************************** SHARED PROCE..."
    (file "D:/home/Hoffmann/pgm/tcl/usr/Tst/garuda/Garuda1.0/helper.tcl" line 35)
    invoked from within
"source D:/home/Hoffmann/pgm/tcl/usr/Tst/garuda/Garuda1.0/helper.tcl"
    ("package ifneeded Garuda 1.0" script)
    invoked from within
"package require Garuda"
    (file "gtest1.tcl" line 2)

MHo 2020-02-11: Website rejects downloads. No binary archives for latest release. 2020-02-14: Ok, downloads are working, but current zips etc. do no longer exist, it seems.

I try a few more constellations. Now it seems to me that loading garuda from within TCLKITs always failes.....:

c:\Users\matthiasu\usr\pgm\tcl\usr\Tst\garuda>tclkitsh
% info pa
8.6.10
% parray tcl_platform
tcl_platform(byteOrder)     = littleEndian
tcl_platform(engine)        = Tcl
tcl_platform(machine)       = amd64
tcl_platform(os)            = Windows NT
tcl_platform(osVersion)     = 10.0
tcl_platform(pathSeparator) = ;
tcl_platform(platform)      = windows
tcl_platform(pointerSize)   = 8
tcl_platform(threaded)      = 1
tcl_platform(user)          = matthiasu
tcl_platform(wordSize)      = 4
% info loaded
{{} twapi_base} {{} registry} {{} dde} {{} vfs} {{} rechan} {{} tclkitpath} {{} vlerq}
% set auto_path [linsert $auto_path 0 [file normalize .]]
C:/Users/matthiasu/usr/pgm/tcl/usr/Tst/garuda C:/Users/matthiasu/usr/bin/MiscTool64/tclkitsh.exe/lib/tcl8.6 C:/Users/matthiasu/usr/bin/MiscTool64/tclkitsh.exe/lib
% package require Garuda
startup return value not TCL_OK, method: "Eagle._Components.Public.NativePackage.Startup", assembly: "C:/Program Files/Eagle/bin/Eagle.dll": failure (code 1).
# ok, got the wrong (32bit) version here....
% set auto_path [linsert $auto_path 0 [file join . Garuda 1.0]]]; # here resides the 64bit variant (I forgot from where I got it...)
./Garuda/1.0 C:/Users/matthiasu/usr/pgm/tcl/usr/Tst/garuda C:/Users/matthiasu/usr/bin/MiscTool64/tclkitsh.exe/lib/tcl8.6 C:/Users/matthiasu/usr/bin/MiscTool64/tclkitsh.exe/lib]
% package require Garuda
ICLRRuntimeHost_Start: failure (code 0x80004005).
CLR not started
# again failed
%
# And now from a "classic" tclsh:
% set auto_path [linsert $auto_path 0 [file join . Garuda1.0]]
./Garuda1.0 ./Garuda/1.0 {C:/Program Files/Tcl86/lib/tcl8.6} {C:/Program Files/Tcl86/lib} {C:/Program Files/Tcl86/lib/cawt2.4.7} {C:/Program Files/Tcl86/lib/tcllib1.20} {C:/Program Files/Tcl86/lib/tklib0.6} {C:/Program Files/Tcl86/lib/vfs1.4.2/template}
% package require Garuda
1.0
%

For me, using garuda from within starkits and starpacks is a requirement.... Ok, years later, I saw the information near the top of the page. Now, garuda loads from a tclkit(sh), but one have to manually copy the right DLLs over (64bit or 32bit); I expected that the package loading adjusts things....