I particuarly liked the way different applications of the PuTTY suite (the well known PuTTY SSH GUI is just one amongst others, there is also plink, pscp ...) are sharing the implementation for certain communication protocols. The next screenshot shows where to select those protocols in the PuTTY SSH GUI:
Choosing between different connection protocols in the PuTTY GUI |
Overview
Before looking at the code lets have a look at this little sketch: (made with Asciiflow)
The implementation of the communication protocols is shared amongst all applications of the PuTTY suite |
This overview should assist you while continue reading. As can be seen, the central header file PUTTY.H contains the definition of the Backend interface.
Interfaces
Short stop: The term interface is widely used in object oriented languages but not so often when it comes to C. However, books like "C Programming: A Modern Approach" or "Test Driven Development for Embedded C" are heavly promoting (particuarly the latter one) the concept of an interface ( a C header file) and its implementation ( a C module). It's nice to see that an old school language like C already contained everything required for this recommendable programming approach.
Back to PuTTY and its sources. From a technical side the Backend interface is a struct mainly consisting out of function pointers. The name of the function pointers are the ones which will be later implemented by the single backend modules (RAW.C, TELNET.C, ...)
// file PUTTY.H // Defining the backend interface: struct backend_tag { const char *(*init) (void *frontend_handle, void **backend_handle, Conf *conf, char *host, int port, char **realhost, int nodelay, int keepalive); void (*free) (void *handle); /* back->reconfig() passes in a replacement configuration. */ void (*reconfig) (void *handle, Conf *conf); /* back->send() returns the current amount of buffered data. */ int (*send) (void *handle, char *buf, int len); /* back->sendbuffer() does the same thing but without attempting a send */ int (*sendbuffer) (void *handle); void (*size) (void *handle, int width, int height); void (*special) (void *handle, Telnet_Special code); const struct telnet_special *(*get_specials) (void *handle); int (*connected) (void *handle); int (*exitcode) (void *handle); /* If back->sendok() returns FALSE, data sent to it from the frontend * may be lost. */ int (*sendok) (void *handle); int (*ldisc) (void *handle, int); void (*provide_ldisc) (void *handle, void *ldisc); void (*provide_logctx) (void *handle, void *logctx); /* * back->unthrottle() tells the back end that the front end * buffer is clearing. */ void (*unthrottle) (void *handle, int); int (*cfg_info) (void *handle); char *name; int protocol; int default_port; }; // create the "Backend" data type for struct backend_tag" typedef struct backend_tag Backend;A typedef for a struct is usually combined with the definition of the struct tag but can ( like here ) of course be written separately.
Interface Implementations
Now that we've seen the interface of the Backend lets have a look inside one of the Backend implementations. For simplicity I've chosen module RAW.C.
At the header of the module the raw_backend struct ( I don't dare writting "object" ;-) ) is declared and initialized with the function names which actually implement the interface. The functions themself are living in the same module file:
// RAW.C - declaring and initializing the raw_backend struct // with the names of the modul's functions which // implement the interface in PUTTY.H plus some default values. ... Backend raw_backend = { raw_init, raw_free, raw_reconfig, raw_send, raw_sendbuffer, raw_size, raw_special, raw_get_specials, raw_connected, raw_exitcode, raw_sendok, raw_ldisc, raw_provide_ldisc, raw_provide_logctx, raw_unthrottle, raw_cfg_info, "raw", PROT_RAW, 0 } ...After publishing the names of the functions, the actual implementation follows in the same file, for demonstration, I just give you the stubs of the first two, raw_init and raw_free.
// RAW.C - local module functions (static) containing // the implementation of the Backend interface. static const char *raw_init(void *frontend_handle, void **backend_handle, Conf *conf, char *host, int port, char **realhost, int nodelay, int keepalive) { ... } static void raw_free(void *handle) { ... }You might share my excitement ;-) when we look at the implementation of the next protocol. This time we're brave and browse through SSH.C, the implementation of interface Backend for the SSH protocol.
First, again the declaration and initialization of the Backend struct:
// SSH.C - the implementation of interface "Backend" // for the SSH protocol Backend ssh_backend = { ssh_init, ssh_free, ssh_reconfig, ssh_send, ssh_sendbuffer, ssh_size, ssh_special, ssh_get_specials, ssh_connected, ssh_return_exitcode, ssh_sendok, ssh_ldisc, ssh_provide_ldisc, ssh_provide_logctx, ssh_unthrottle, ssh_cfg_info, "ssh", PROT_SSH, 22 }Again, the actual functions containing the implementation reside in the same file (just the stubs of the first two):
// SSH.C - private functions implementing // the interface "Backend" static const char *ssh_init(void *frontend_handle, void **backend_handle, Conf *conf, char *host, int port, char **realhost, int nodelay, int keepalive) { ... } static void ssh_free(void *handle) { ... }Since I'm just interested in the mechanism of interface vs. implementation in C, I omitted the content of the functions - but did you see how the two modules RAW.C and SSH.C are following the same pattern - specified by (again) the interface PUTTY.H.
Now that there is the implementation of the protocols we need to investigate how the actual applications are using them. This is something for part two of this little series.
Keine Kommentare:
Kommentar veröffentlichen