Introduction to Mash hacking in C++ ----------------------------------- by José María González This document is intended to developers that are confident with the otcl world and want to play with the C++ one. So now you know how to edit the vic script and do changes on the otcl part. What about the C++ part? There are three types of classes in mash: pure otcl ones, pure C++ ones, and hybrid ones. You should be able by now to create and modify the pure otcl ones and the otcl part of the hybrid ones. Pure C++ classes cannot be used directly from the otcl part. They are like any C++ classes, and using mash, you can create instances, destroy, or use them only from other pure C++ or hybrid classes. We'll therefore concentrate on the hybrid ones. Hybrid classes have some methods written in C++, some in otcl, and some in both. To create a hybrid class, you first create its C++ part as a normal C++ class. It may have a constructor, destructor, and maybe some methods and variables. The only requirement is that it inherits from the TclObject class, which is defined at tclcl/tclcl.h Once you're happy with the C++ class, you have to tell otcl that you want to create or use objects of that class from C++. To do so, you define an object of a subclass from the class TclClass, where the "create" method indeed creates the C++ object. This sounds obscure, so let's take a look at one example of hybrid class, namely the H.261 decoder. Objects from this class are needed in the otcl world to pipe a packet receiver object and a renderer object. The code is located at: mash-code/codec/decoder-h261.[h,cc] The following code snippet is from the .cc file: static class H261DecoderClass : public TclClass { public: H261DecoderClass() : TclClass("Module/VideoDecoder/H261") {} TclObject* create(int /* argc */, const char*const* /* argv */) { return (new H261Decoder); }; } dm_h261; This code is creating an otcl class called "Module/VideoDecoder/H261", and linking it to the H261Decoder C++ class. This will create an H261Decoder C++ object. H261Decoder inherits from Decoder, PacketHandler, PacketModule, PacketModule, Module, and finally TclObject. The "H261DecoderClass" and "dm_h261" are not used anywhere else in the code, so don't worry about them. Once the decoder-h261.o is linked into mash, you can fire mash and type: % set my_decoder [new Module/VideoDecoder/H261] Parameters can be passed to the C++ constructor by judicious use of the argc and argv parameters in the "create" method. Just as a note, receiving a bad set of parameters should not impel "create" to call exit() or abort(), but to return "(TclObject *)NULL". Now that you have create a hybrid object from otcl, the next step is controlling it. To do so, you use the C++ "command" method. The "command" method maps otcl strings to C++ methods and their parameters. When you call the otcl object id you received from creating the object (we've called it "my_decoder"), otcl tries to dispatch the otcl method with that name. For example, if I write in otcl the following: % $my_decoder width The interpreter tries to dispatch the "width" method from Module/VideoDecoder/H261. The latter doesn't exist, but as the class is a hybrid one, instead of returning an error message, the interpreter dispatches the C++ H261Decoder::command method, with the argument "width". The H261Decoder class has no "command" method, but its parent (Decoder, located at mash-code/codec/decoder.[h,cc]) has it. Decoder::command, as any other "command" method in mash, checks the number of arguments and their content. We can see that one of the comparisons is for the string "width". Decoder::width() is called and its result returned in the typical tcl fashion. Please note that any "command" method that doesn't implement the requested otcl string calls its parent class' "command" method. If no class in the inheritance chain implements the method, TclObject::command will return an error message. A final detail before the examples is that there is the possibility of having a method implemented in both otcl and C++. The otcl code is the one run. If you want to run also the C++ part, use "$self next" at the end of the otcl implementation. Take a look at the "destroy" method in the otcl definition of the Source/RTP class (mash-code/tcl/net/agent-rtp.tcl). Note: calling the otcl version of the code is especially important with object destructors. If you don't manage to get your call dispatched to the C++ world, the object is never deleted (memory leak). Demo ---- If you want to verify everything I just said, the best method is debugging a mash script. From here on, I assume you compiled everything in mash with symbols. I'm using Linux, so the instructions are valid for any Unix flavor. There should be some equivalent method in the Windows world. 1. Open a mash shell and hook a gdb window to it. To do so, get the pid of the process: > ps -auwx |grep mash chema 2335 0.0 2.9 8740 3760 pts/2 S 18:37 0:00 mash chema 2346 0.0 0.4 1732 592 pts/8 S 18:57 0:00 grep -i mash And then run gdb with the mash binary and the pid: > gdb ./bin/mash 2335 2. Set breakpoints at H261DecoderClass::create and Decoder::command, and let the process run: (gdb) break H261DecoderClass::create Breakpoint 1 at 0x82183af: file codec/decoder-h261.cc, line 53. (gdb) break Decoder::command Breakpoint 2 at 0x80b8b9f: file codec/decoder.cc, line 82. (gdb) continue Continuing. 3. Create the hybrid object from otcl: % set my_decoder [new Module/VideoDecoder/H261] _o2 Now the debugger returns: Breakpoint 1, H261DecoderClass::create (this=0x83c4e4c) at codec/decoder-h261.cc:53 53 return (new H261Decoder); Let the debugger continue again: (gdb) continue Continuing. 4. Call a defined method from otcl: % $my_decoder width Explore the arguments from the debugger. Breakpoint 2, Decoder::command (this=0x8485a78, argc=2, argv=0xbfffe2ec) at codec/decoder.cc:82 82 Tcl& tcl = Tcl::instance(); (gdb) p argc $1 = 2 (gdb) p argv $2 = (char **) 0xbfffe2ec (gdb) p argv[0] $3 = 0x8484290 "cmd" (gdb) p argv[1] $4 = 0x8438738 "width" You should be able to bootstrap yourself from here.