I am extending my Itcl program with C++ code and I got the following problem.
I want to return an "reference" in my Itcl code to an object in my C++ code using the TCL-C API, The return value should be registerd to an object in my Itcl code so after this I could invoke it methods. My problem is that I don't really know how to do this from both sides.
I saw that with TCL_LINKVAR(...) API function I can create an link between this objects to an string in TCL but I don't really understand how to use this TCL_LINKVAR function when it comes to objects and not to primitive types such as int,double,etc...
I will give a small example: This is my C++ code:
Tcl-C API code
#include <tcl.h>
#include "classA.h"
class MyObject {
int id;
ClassA * a; // classA is defined somewhere else
public:
MyObject(int iid) : id(iid) {}
void returnA() { return a; }
};
extern "C" int do_something_command(ClientData clientData,
Tcl_Interp* interp,
int objc,
Tcl_Obj* const objv[])
{
if (objc < 2) {
Tcl_WrongNumArgs(interp, 1, objv, "method ?argument ...?");
return TCL_ERROR;
}
MyObject* p = (MyObject*)clientData;
classA * ret_val = p->returnA();
if (LINK_VAR.... != TCL_OK) { // here should be a linkage between ret_val to an Itcl object
return TCL_ERROR;
}
return TCL_OK;
}
extern "C" int test_create(ClientData clientData,
Tcl_Interp* interp,
int objc,
Tcl_Obj* const objv[])
{
static int obj_count = 0;
MyObject* p = new MyObject(obj_count);
char obj_name[13 + TCL_INTEGER_SPACE];
sprintf(obj_name, "::testobj%d", obj_count++);
Tcl_CreateObjCommand(interp, "cDoSomething",
(Tcl_ObjCmdProc*)do_something_command,
(ClientData) p, (Tcl_CmdDeleteProc *) NULL);
Tcl_SetObjResult(interp, Tcl_NewStringObj(obj_name, strlen(obj_name)));
return TCL_OK;
}
extern "C" DLLEXPORT int Test_Init(Tcl_Interp *interp)
{
if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {
return TCL_ERROR;
}
if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 0) == NULL) {
return TCL_ERROR;
}
if (Tcl_PkgProvide(interp, "test", "0.1") != TCL_OK) {
return TCL_ERROR;
}
Tcl_CreateObjCommand(interp, "test::create",
(Tcl_ObjCmdProc*)test_create,
(ClientData)NULL, (Tcl_CmdDeleteProc*)NULL);
return TCL_OK;
}
Itcl code
load test.o // this will be the name of .o file I will create from my C++ code
itcl::class A {
...
public method doSomething {}
... }
itcl::class B {
constructor {} {
set temp [test::create]
set a [$temp cDoSomething] // should register a as an classA object
// here I will call methods that are related to classA object that are registered in C++
}
...
}
First off, variable linking maps between simple C types — just numbers and char*
(where Tcl manages the memory allocation of the char*
) — and Tcl variables; it's not really suitable for what you're doing. In principle you could do the same with any struct; the linking code is a wrapper round the fundamental Tcl variable tracing primitive API. It's not really suitable for any opaque type or anything with a dynamic lifetime.
The simplest technique is to keep a mapping (e.g., a std::map<std::string,MyObject*>
) on the C++ side so that you can have a lookup-by-name operation. Once you've got that, you can then invent a simple mechanism for naming and keep the string name in a Tcl variable in the Itcl object. Methods on the C++ side are proxied by commands that take the name as one of the arguments, look the relevant object up, and pass the rest of the arguments through (with whatever additional parsing/type-conversion you prefer), and the Itcl methods are trivial wrappers that pass the additional name/handle in the right place (with the destructor being just the same except that's proxying for a remove-from-map and delete
). There are other ways to do this, but the one I've just described is easy to implement and the thing to try doing first; it lets you prototype the API without significant effort at the design level. Note that fields of the object don't map nicely this way; it's intended for methods only, and what makes for a good API in C++ is definitely not the same as what makes for a good API in Tcl/Itcl.
You can achieve similar things by using SWIG to generate the binding. What it does internally isn't very different, but it puts the binding more in C++ than on the Tcl side of things. The Itcl wrapping will work a little differently…
If you're using Itcl 4, the method binding can be done from C++ using the TclOO API (which Itcl 4 is built on top of). TclOO includes mechanisms for defining custom methods and for attaching C and C++ objects directly to its instances; this can make the binding rather more sophisticated (and will let you do things like subclassing on the Tcl side of things) but it's a bit more complex because of this. And the API you'll end up with looks, from the outside at least, very similar to what I outlined above.