People know that Tcl is widely used in the world. It uses C-like syntax and provides Tcl/C binding. For people who know C and want their applications to support script, Tcl is a good solution.
Tcl is written in C. If a Tcl interpreter is required to be embedded in the application and you want to write Tcl command in C/C++, we usually need to do something that makes Tcl a bit object-oriented. How to create an object-oriented Tcl language is not my intention. What I am trying to do is to wrap Tcl in a way that writing of Tcl commands can be more object-oriented (maybe you can see it as a pattern). And if you know any better way, please let me know.
If we are required to create a command called run_me in Tcl interpreter and it is kinda expensive to write this command in tcl script, we can write it in C/C++. I am sure we all agree to this. The internal work is that an associated C function gets called when run_me is executed in tclsh.
First, a Tcl_CreateObjCommand can be used to create this Tcl to C binding and it needs parameters as follows:
Tcl_CreateObjCommand(interp, /* Tcl_Interp* */ "run_me", /* const char*: Tcl command name */ &my_run_me_c_func, /* C function to be called */ 0, 0 );
In the simplest form, you can just add my_run_me_c_func at global namespace. Of course, it's not a good enough solution for C++ developer because you may add too many functions in global namespace. It shouldn't surprise you that I use a class hierarchy to manage these C bindings. And it shouldn't surprise you again that I apply Command Pattern in this scenario. The base class of the hierarchy may look like,
class TclCommand { public: virtual ~TclCommand() {} int execute( ClientData clientData, Tcl_Interp* interp, int objc, Tcl_Obj* const objv[] ) { // print help if having -h option // syntax checking by is_valid() // everything is fine, call do_execute() return TCL_OK; } protected: TclCommand(); virtual int do_execute( /* necessary arguments */ ) = 0; virtual bool is_valid( /* arguments from command line */ ) = 0; virtual void help( /* necessary arguments */ ) = 0; }; class RunMeTclCommand : public TclCommand { private: // Override pure virtual functions in base class. };
For different Tcl commands, different TclCommand class should be added in this class hierarchy. The problem is how to let Tcl know run_me and RunMeTclCommand are connected?
First, every Tcl command is linked to a local static function TclCommandLinker and it dispatches call to appropriate TclCommand object. Its content may look like
static int TclCommandLinker( ClientData clientData, Tcl_Interp* interp, int objc, Tcl_Obj* const objv[] ) { // ... int length = 0; TclCommand* cmd = TclCommandMap::instance()->getMapping( Tcl_GetStringFromObj( objv[0], &length ) ); if ( cmd ) { return cmd->execute( clientData, interp, objc, objv ); } return TCL_ERROR; }
You can see from above that a singleton object TclCommandMap used here to get proper TclCommand mapping. This object can be implemented by std::map as,
class TclCommandMap { public: static TclCommandMap* instance(); void addMapping(const std::string& name, TclCommand* cmd); Command* findMapping(const std::string& name); // ... };
Second, Tcl knows this TclCommandLinker through,
Tcl_CreateObjCommand( interp, "run_me", TclCommandLinker, 0, 0 );
Finally, when a Tcl command is registered into Tcl interpreter,
// ... TclCommandMap::instance()->addMapping("run_me", new RunMeTclCommand( /* parameters */ ); // ...
That's it. We're done.
沒有留言:
張貼留言