Mod /
CAPI
Search:  


1.  CAPI

CAPI converts a Tcl module into a C extension which automatically unloads arguments to C variables, performs type checking. and provides validation (when used with Wize).

CAPI works on modules (namespaces) of commands, and the emphasis is on Tcl arguments rather than C signatures (unlike Critcl).

2.  An Inline Example

Here is an example using inline-C in the body part after the marker #CBODY:.

# File "math.tcl"

namespace eval ::app::math {

  #Mod export
  namespace ensemble create
  namespace export {[a-z]*}

  proc add {i j} {#TYPES: . Double Double
    # Add two doubles together.
    return [expr {$i+$j}]

    #CBODY:
    Tcl_SetObjResult(interp, Tcl_NewDoubleObj(arg_i + arg_j));

  }

  proc sub {i j} {#TYPES: . Double Double
    # Subtract two doubles.
    return [expr {$i-$j}]

    #CBODY:
    Tcl_SetObjResult(interp, Tcl_NewDoubleObj(arg_i - arg_j));

  }

}

Generation of a .c file and shared library is thus:

  % wize /zvfs/wiz/capi.tcl math.tcl
  Generated: Appmath math.c ::app::math (2 commands)
  % gcc -g -shared -o libAppmath.so math.c

The last line compiles the generated file math.c into a unix shared .so library. Note the shared library name is based on libname returned from CAPI (the name after Generated:).

For each proc, commands arguments are unloaded to the C double variables arg_i and arg_j. If #TYPES: weren't specified they would default to char *. All argument indexes are stored in opt_NAME,

3.  Library Load

When a CAPI library is loaded it returns 4 values:

  1. The namespace for the module.
  2. The library name as appears in [info loaded].
  3. The version for the module/package (default 0.0).
  4. Various of the non-empty options.

Note: these values are also set in the namespace variable ::app::math::pd(load).

Here is an example:

  # File "tst.tcl"
  set inf [load ./libAppmath.so]
  puts "LOAD: $inf"
  set ns [lindex $inf 0]
  foreach i [info commands ${ns}::*] {
     puts "$i [extern $i]"
  }
  puts [::app::math::add 9 8]
  puts [$ns add 9 10]
  unload ./libAppmath.so
  exit 0

When run this outputs:

  % wize tst.tcl
  LOAD: ::app::math Appmath 0.0 {-safe True -wize True -capi 1 -modtype 1}
  ::app::math::add {i j} {. Double Double} I {Add two doubles together.}
  ::app::math::sub {i j} {. Double Double} I {Subtract two doubles.}
  17
  19

The use of extern is explained in the next section.

4.  Validation

Validation refers to using wize -wall to statically check Tcl. It is available, unless the library was generated with -wize False.

Following is an example:

# File "foo.tcl"
load ./libappmath.so

proc Foo {} {
   return [app::math add 8 9 9]
}

proc Bar {} {
   set n [app::math add x 9]
   return [app::math div $n 9]
}

And here is a sample run:

  % wize -Wall foo.tcl
  /tmp/foo.tcl:5: warning: too many args, expected parameters
      {i j} for "app::math add 8 9 9" in proc [::Foo] <args,30>.
  /tmp/foo.tcl:9: warning: for argument #1 "i", the value "x"
      does not match type <Double>  for "app::math add x 9" in
      proc [::Bar] <types,4>.
  /tmp/foo.tcl:10: warning: module sub-cmd "div" is not one of
      "add sub" for "::app::math div $n 9" in proc [::Bar]
       <args,18>.

5.  Mixing Tcl and C

It is not uncommon for a module to be implemented partly in C and partly in Tcl. The following example demonstrates not only this, but also fall-back to Tcl if the extension is unavailable:

# File "math.tcl"
namespace eval ::app::math {

  #Mod export
  namespace ensemble create
  namespace export {[a-z]*}

  proc mult {i j} {#TYPES: . Double Double
    # multiply two doubles.
    return [expr {$i*$j}]
  }


  if {![catch { load [file dirname [info script]]/libAppmath.so }]} return
  # BEGIN C-ABLE FUNCTIONS

  proc add {i j} {#TYPES: . Double Double
    # Add two doubles together.
    return [expr {$i+$j}]

    #CBODY:
    Tcl_SetObjResult(interp, Tcl_NewDoubleObj(arg_i + arg_j));

  }

  proc sub {i {j 1}} {#TYPES: . Double Double
    # Subtract two doubles.
    return [expr {$i-$j}]

    #CBODY:
    Tcl_SetObjResult(interp, Tcl_NewDoubleObj(arg_i - arg_j));

  }

}

Here is a test file:

#!/usr/bin/env wize
# File "mathtst.tcl"
source math.tcl
puts [app::math add 9 10]
puts [app::math mult 9 10]
puts "ADD: [expr {[catch {info body ::app::math::add}]?{C}:{Tcl}}]"
exit 0

and a script for running the test:

#!/bin/sh
# File "runtest.sh"
set -x
rm -f libAppmath.so
wize /zvfs/wiz/capi.tcl math.tcl
gcc -g -shared -o libAppmath.so math.c

wize mathtst.tcl
rm libAppmath.so
wize mathtst.tcl

Here is the output from runtest.sh:

+ rm -f libAppmath.so
+ wize /zvfs/wiz/capi.tcl math.tcl
skipping command: ::app::math::mult
Generated: Appmath math.c ::app::math (2 commands)
+ gcc -g -shared -o libAppmath.so math.c
+ wize mathtst.tcl
19.0
90
ADD: C
+ rm libAppmath.so
+ wize mathtst.tcl
19
90
ADD: Tcl

Note: we ensure to remove libAppmath.so before re-running capi or we will get 0 commands generated.

6.  Non Inlined Code

Inline code doesn't play well with validation and will generate warnings if used with -Wall.

There are two alternatives to inlining C-code.

The first is to use the -all option and then manually editing the resulting math.c. The problem with this approach is that later regeneration of math.c will lose these edits.

A better alternative provides code bodies in an implementation file, specified using the -impl option. The implementation files defines an input array with one entry per proc. Here is a simple implementation file for a non-inlined math.tcl.

# "File main.inc"
::app::math::add {
   Tcl_SetObjResult(interp, Tcl_NewDoubleObj(arg_i + arg_j));
}
::app::math::sub {
   Tcl_SetObjResult(interp, Tcl_NewDoubleObj(arg_i - arg_j));
}

which can be generated with:

  wize /zvfs/wiz/capi.tcl math.tcl -impl math.inc

Other options include: body load unload cmddelete cmdvars vars excludes.

Here is an implementation file using all these options:

# "File main.inc"
::app::math::add {
   Tcl_SetObjResult(interp, Tcl_NewDoubleObj(arg_i + arg_j));
}
::app::math::sub {
   Subfunc(modPtr, cmdPtr);
   Tcl_SetObjResult(interp, Tcl_NewDoubleObj(arg_i - arg_j));
}
body {
  /* Code for body, eg. static C functions, vars, etc. */
  static int Subfunc(ModData *modPtr, CmdData *cmdPtr) {
     modPtr->cnt++;
     cmdPtr->subcnt++;
  }
}
load {
  /* Code for the library Init section. */
  modPtr->cnt = 1;
}
unload {
  /* Code called at unload of library. */
  printf("MODCNT: %d\n", modPtr->cnt);
}
cmddelete {
  /* Code called at delete of each proc. */
}
cmdvars {
  /* User vars for CmdData. */
  int subcnt;
}
vars {
  /* User vars for ModData. */
  int cnt;
}
# "One or more exclude patterns"
excludes {
   [A-Z]* _*
}

Note 1: Comments use the # option.

Note 2: Although the two can be mixed, -impl entries will override #CBODY:.

7.  Mod export

By default CAPI detects if you have used Mod export and if so issues this call on load.

Another advantage of Mod export is that it defines the pd array containing elements script, dirname, etc.

8.  Stubs

Here is how to build a library with stubs:

 % wize /zvfs/wiz/capi.tcl main.tcl -stubs 8.5
 % gcc -g -shared -o libAppmath.so math.c  -ltclstub8.5 -ltkstub8.5

The stubs and include files for wize can be extracted using:

 % wize /zvfs Admin/Headers /DEST/DIR

The appropriate -L and -I is then used with gcc.

9.  Options

Following are the options for the capi command.

OptionDefaultDescription
-allFalseInclude commands without #CBODY: or -impl entry
-capi1Version of CAPI (read-only)
-delnsFalseDelete namespace on module unload
-impl Implementation file
-libname Library name to use for XXX_Init
-modtype1Type of module (reserved for future use)
-ns Namespace for output code
-out Override default output file XXX.c
-pkgname Package name to do a package provide with
-stubs Version of stubs library to use
-version0.0Version number for module
-wizeTrueUse wize typechecking api

© 2008 Peter MacDonald

Page last modified on November 30, 2009, at 08:50 AM