dbohdan 2016-09-19: The following is, to my best knowledge, about as simple as a Tcl extension written in Go can get. The wrapper functions *_cgo are necessary since you can't pass a pointer to a Go function to C code. In a real extension those wrappers could be generated automatically via go generate and a Tcl script.
The code has been tested with Go v1.6.3 on Linux x86_64. To build it yourself save example.go, wrappers.go and Makefile in the same directory, replace the spaces with tabs with
$ sed 's/ /\t/g' -i example.go wrappers.go Makefile
which is necessary for the Makefile to work, then run make test.
libtclgoexample.so: example.go wrappers.go go build -o $@ -buildmode=c-shared example.go wrappers.go test: libtclgoexample.so echo 'load libtclgoexample.so; puts [hello]; puts [square 5]' | tclsh .PHONY: test
//example.go v0.2.0 package main /* #include <stdlib.h> #include <tcl.h> #include <tclDecls.h> #cgo LDFLAGS: -ltcl8.6 int Hello_Cmd_cgo(ClientData cdata, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); int Square_Cmd_cgo(ClientData cdata, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); */ import "C" import ( "reflect" "unsafe" ) const ( TCL_OK = 0 TCL_ERROR = 1 TCL_RETURN = 2 TCL_BREAK = 3 TCL_CONTINUE = 4 ) func (interp *C.struct_Tcl_Interp) createCommand(name string, f *C.Tcl_ObjCmdProc) { cName := C.CString(name) defer C.free(unsafe.Pointer(cName)) C.Tcl_CreateObjCommand(interp, cName, f, nil, nil) } func (interp *C.struct_Tcl_Interp) wrongNumArgs(objc C.int, objv **C.Tcl_Obj, message string) { var cMessage *C.char if message == "" { cMessage = nil } else { cMessage = C.CString(message) defer C.free(unsafe.Pointer(cMessage)) } C.Tcl_WrongNumArgs(interp, objc, objv, cMessage) } func slicify(objc C.int, objv **C.Tcl_Obj) (objs []*C.Tcl_Obj) { // http://stackoverflow.com/a/14828189 sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&objs)) sliceHeader.Cap = (int)(objc) sliceHeader.Len = (int)(objc) sliceHeader.Data = uintptr(unsafe.Pointer(objv)) return } //export Hello_Cmd func Hello_Cmd(cdata C.ClientData, interp *C.struct_Tcl_Interp, objc C.int, objv **C.Tcl_Obj) C.int { if objc != 1 { interp.wrongNumArgs(1, objv, "") return TCL_ERROR } result := C.CString("Hello, World!") defer C.free(unsafe.Pointer(result)) C.Tcl_SetObjResult(interp, C.Tcl_NewStringObj(result, -1)) return TCL_OK } //export Square_Cmd func Square_Cmd(cdata C.ClientData, interp *C.struct_Tcl_Interp, objc C.int, objv **C.Tcl_Obj) C.int { if objc != 2 { interp.wrongNumArgs(1, objv, "value") return TCL_ERROR } objs := slicify(objc, objv) var i C.int if C.Tcl_GetIntFromObj(interp, objs[1], &i) != TCL_OK { return TCL_ERROR } C.Tcl_SetObjResult(interp, C.Tcl_NewIntObj((C.int)(i*i))) return TCL_OK } //export Tclgoexample_Init func Tclgoexample_Init(interp *C.struct_Tcl_Interp) C.int { interp.createCommand("::hello", (*C.Tcl_ObjCmdProc)(C.Hello_Cmd_cgo)) interp.createCommand("::square", (*C.Tcl_ObjCmdProc)(C.Square_Cmd_cgo)) return TCL_OK } func main() {}
//wrappers.go package main /* #include <tcl.h> #include <tclDecls.h> int Hello_Cmd_cgo(ClientData cdata, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { int Hello_Cmd(ClientData cdata, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); return Hello_Cmd(cdata, interp, objc, objv); } int Square_Cmd_cgo(ClientData cdata, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { int Square_Cmd(ClientData cdata, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); return Square_Cmd(cdata, interp, objc, objv); } */ import "C"