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:
- The namespace for the module.
- The library name as appears in [info loaded].
- The version for the module/package (default 0.0).
- 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.
| Option | Default | Description |
| -all | False | Include commands without #CBODY: or -impl entry |
| -capi | 1 | Version of CAPI (read-only) |
| -delns | False | Delete namespace on module unload |
| -impl | | Implementation file |
| -libname | | Library name to use for XXX_Init |
| -modtype | 1 | Type 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 |
| -version | 0.0 | Version number for module |
| -wize | True | Use wize typechecking api |
© 2008 Peter MacDonald