5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 /*! \mainpage libt2n - (talk to neighbor)
21 \section intro_sec Introduction
22 libt2n (talk to neighbor) is a C++ library for inter-process communication (IPC, s.a. http://en.wikipedia.org/wiki/Inter-process_communication)
23 with an additional code generator to make remote procedure calls simple.
25 XXX: improve this paragraph: The input for the code generator is standard C++ code (in fact we use gccxml to parse the C++ code and the code generator takes the XML as input) and you mark the procedures you want to expose to other processes.
26 It then generates the stubs needed.
27 The exported procedures can be grouped. For each group the code generator is called and generates 6 output files: group_common.hxx, group_common.cpp, group_client.hxx, group_client.cpp, group_server.hxx, group_server.cpp. The _common files are used by client and server whereas the _client files are used by the client and the _server files by the server only.
29 To simplify the build process a Makefile snippet is provided that allows to create a server program and a client library (including a corresponding .pc file) using the autotools easily.
30 \section install_sec Installation
32 \subsection requirements Requirements
33 - boost <http://www.boost.org/> (serialization <http://www.boost.org/libs/serialization/doc/>)
34 - gccxml <http://www.gccxml.org>
35 - libxmlpp <http://libxmlplusplus.sourceforge.net/>
37 \subsection recommended Recommended
38 - pkg-config <http://pkgconfig.freedesktop.org/wiki/>
39 - autotools (automake, autoconf, libtool)
41 \subsection Compilation
43 ./configure && make install
47 \section usage Usage example
49 In this example we create two packages using the autotools:
50 - server program and client library to connect to the server. The server exports a simple procedure using one group: "t2nexample"
51 - client program using the library
53 \subsection server Example server program and client library
55 \par The procedure to export (input for the code generator - libt2n-codegen): t2nexample.cpp:
56 \verbinclude libt2n-example1/t2nexample.cpp
58 \par Required includes go into the group header file: t2nexample.hxx:
59 libt2n uses boost for serialization. This means all types involved in a remote procedure call must be boost serializable. In this example we use std::string and boost
60 already provides serialization for std::string in the boost/serialization/string.hpp header file.
61 \verbinclude libt2n-example1/t2nexample.hxx
63 \par The server program:
64 \verbinclude libt2n-example1/server.cpp
66 \par Using autoconf and automake to build a example server program and a client library.
67 In the configure.in(.ac) we put a check for libt2n:
68 \verbinclude libt2n-example1/configure.in
69 Writing the Makefile.am isn't difficult either:
70 \verbinclude libt2n-example1/Makefile.am
72 \par Build and install the package
74 autoreconf -f -i && ./configure && make install
77 \subsection client Client using the library
78 Using the library is as simple as using any other library using pkg-config (the pkg-config .pc file is created automatically by the included Makefile snippet)
79 \par We only have to check that the library is installed
80 \verbinclude libt2n-example1-client/configure.in
81 \par The Makefile.am needs nothing special
82 \verbinclude libt2n-example1-client/Makefile.am
83 \par The client program
84 \verbinclude libt2n-example1-client/client.cpp
86 \par Build and install the package
88 autoreconf -f -i && ./configure && make install
95 socket: cannot open `socket' (No such file or directory)
96 $ libt2n-example1-server &
100 $ libt2n-example1-client && echo ok
110 \example t2nexample.cpp
111 example input for libt2n-codegen
114 #include <libxml++/libxml++.h>
121 #include <boost/lexical_cast.hpp>
127 //! map group to class name
129 groupClass(const std::string &group) {
130 return std::string("cmd_group_")+group;
133 //! convert string to upper case
135 toupper(std::string s) {
136 for (unsigned i=0; i<s.length(); ++i) s[i]=toupper(s[i]);
140 //! replace all characters f by r in string s
142 replace(std::string s, char f, char r) {
143 for (unsigned i=0; i<s.length(); ++i) if (s[i]==f) s[i]=r;
147 //! strip prefix from string s
149 \return string s without prefix or an empty string on error
152 strip(std::string s, std::string prefix)
155 if ( (prefix.length()>s.length() ) || ( std::string(s,0,prefix.length())!=prefix ) ) return error;
156 return std::string(s, prefix.length(), s.length()-prefix.length());
159 //! get child element by id
161 \return pointer to element having id or null on error
162 \todo find libxmlpp pendant
164 const xmlpp::Element* get_element_by_id(const xmlpp::Element* element, const std::string &id)
166 const xmlpp::Attribute* cid = element->get_attribute("id");
167 if ( cid && ( cid->get_value() == id)) return element;
169 //Recurse through child nodes:
170 xmlpp::Node::NodeList list = element->get_children();
171 for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
173 const xmlpp::Element* element = dynamic_cast<const xmlpp::Element*>(*iter);
175 const xmlpp::Element* match = get_element_by_id(element, id);
176 if (match) return match;
183 get_file(const xmlpp::Element* root, const std::string &file_id)
186 const xmlpp::Element* e=get_element_by_id(root, file_id);
187 if ((!e)||(!e->get_attribute("name"))) return error;
188 return e->get_attribute("name")->get_value();
191 //! get namespace by id
193 \return namespace name or empty string on error
195 std::string get_namespace(const xmlpp::Element* root, const std::string &id)
198 const xmlpp::Element* element(get_element_by_id(root, id));
199 if ((!element)||(!element->get_attribute("name"))) return error;
200 return element->get_attribute("name")->get_value();
203 //! extract group from attributes
205 extract_group(const std::string &attrs)
207 // todo: improve this
209 std::string to_match("gccxml(libt2n-");
210 std::string::size_type p(attrs.find(to_match));
211 if (p==std::string::npos) return error;
212 std::string group(attrs, p+to_match.length(), attrs.length());
213 p=group.find_first_of(')');
214 assert(p!=std::string::npos);
215 return std::string(group,0,p);
221 std::string noref_name;
222 bool operator==(const type_info& o) {return (name==o.name) && (noref_name == o.noref_name);}
223 std::string noref() const {return noref_name.empty() ? name : noref_name;}
226 std::ostream &operator<<(std::ostream &o, const type_info &t) {
231 struct parse_error : public std::runtime_error
233 parse_error(const std::string &file, unsigned line, const std::string &msg)
234 : std::runtime_error(file+":"+boost::lexical_cast<std::string>(line)+": error: "+msg)
240 \return type name or empty string on error
242 type_info get_type(const xmlpp::Element* root, const std::string &id)
245 const xmlpp::Element* element(get_element_by_id(root, id));
246 if (!element) return error;
248 // TODO: not yet complete
249 // if we recurse - when do we stop?
250 // if it is a typedef? yes? (hmm if the typedef is in the file parsed this will not work)
252 // TODO: const and reference types handling is a ugly hack
254 std::string tag(element->get_name());
255 if (tag=="ReferenceType") {
256 assert(element->get_attribute("type"));
257 type_info ret(get_type(root, element->get_attribute("type")->get_value()));
258 if (ret==error) return error;
259 // at the moment we only support const &
260 // todo: nice error message!
261 if ((ret.noref_name=strip(ret.name,"const ")).empty()) return error;
262 ret.name=ret.name+"&";
264 }else if (tag=="CvQualifiedType") {
265 assert(element->get_attribute("type"));
266 type_info ret(get_type(root, element->get_attribute("type")->get_value()));
267 if (ret==error) return error;
268 ret.name=std::string("const ")+ret.name;
270 }else if (tag=="PointerType") {
275 assert(element->get_attribute("name"));
277 if (element->get_attribute("context")) {
278 ret.name=get_namespace(root, element->get_attribute("context")->get_value());
282 // do not explicitely add ::
285 ret.name+=element->get_attribute("name")->get_value();
291 typedef std::list<std::pair<std::string, type_info> > Args;
298 std::string ret_classname() const {
299 return name+mangled+"_res";
301 std::string cmd_classname() const {
302 return name+mangled+"_cmd";
306 std::ostream &operator<<(std::ostream &o, const t2n_procedure::Args &args) {
307 for (t2n_procedure::Args::const_iterator it=args.begin();it!=args.end();++it) {
308 if (it!=args.begin()) o << ", ";
309 o << it->second << " " << it->first;
314 std::ostream &operator<<(std::ostream &o, const t2n_procedure &f) {
315 o << f.ret_type << " " << f.name << "(" << f.args << ")";
322 Parser(const std::string &fname) : m_fname(fname) {}
324 std::list<t2n_procedure> get_procedures() {
325 xmlpp::DomParser parser;
326 // parser.set_validate();
327 parser.set_substitute_entities(); //We just want the text to be resolved/unescaped automatically.
328 parser.parse_file(m_fname);
332 const xmlpp::Node* pNode = parser.get_document()->get_root_node(); //deleted by DomParser.
333 const xmlpp::Element* root = dynamic_cast<const xmlpp::Element*>(pNode);
341 std::list<t2n_procedure> m_procedures;
343 void parse_function(const xmlpp::Element* root, const xmlpp::Node* node) {
344 const xmlpp::Element* element = dynamic_cast<const xmlpp::Element*>(node);
345 if (!element) return;
347 const xmlpp::Attribute* attributes = element->get_attribute("attributes");
348 const xmlpp::Attribute* name = element->get_attribute("name");
349 const xmlpp::Attribute* mangled = element->get_attribute("mangled");
350 const xmlpp::Attribute* returns = element->get_attribute("returns");
351 if ((!attributes)||(!name)||(!mangled)||(!returns)) return;
353 // check wether the procedure is marked (TODO: improve)
354 // attributes are speparated by spaces?
357 if (extract_group(attributes->get_value()).empty()) return;
359 // we need the return type
360 f.ret_type=get_type(root, returns->get_value());
361 f.name=name->get_value();
362 f.mangled=mangled->get_value();
364 xmlpp::Node::NodeList list = node->get_children("Argument");
365 for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
367 const xmlpp::Element* arg = dynamic_cast<const xmlpp::Element*>(*iter);
369 assert(arg->get_name() == "Argument");
370 assert(arg->get_attribute("name"));
371 assert(arg->get_attribute("type"));
372 f.args.push_back(std::pair<std::string, type_info>(arg->get_attribute("name")->get_value(), get_type(root, arg->get_attribute("type")->get_value())));
373 // todo: ugly - could be any other error
374 if (f.args.back().second.name.empty()) {
375 assert(element->get_attribute("file"));
376 assert(element->get_attribute("line"));
377 throw parse_error(get_file(root, element->get_attribute("file")->get_value()),
378 boost::lexical_cast<unsigned>(element->get_attribute("line")->get_value())-1,
379 std::string("type of parameter `")+f.args.back().first+"' not (yet?) supported");
383 std::cerr << "Found function: " << f << std::endl;
384 m_procedures.push_back(f);
387 void visit_node(const xmlpp::Element* root, const xmlpp::Node* node = NULL, unsigned int indentation = 0)
389 if (!node) node=root;
391 const xmlpp::ContentNode* nodeContent = dynamic_cast<const xmlpp::ContentNode*>(node);
392 const xmlpp::TextNode* nodeText = dynamic_cast<const xmlpp::TextNode*>(node);
393 const xmlpp::CommentNode* nodeComment = dynamic_cast<const xmlpp::CommentNode*>(node);
395 if(nodeText && nodeText->is_white_space()) //Let's ignore the indenting - you don't always want to do this.
398 std::string nodename = node->get_name();
400 if(!nodeText && !nodeComment && !nodename.empty()) //Let's not say "name: text".
402 if (node->get_name() == "Function") parse_function(root, node);
406 //Recurse through child nodes:
407 xmlpp::Node::NodeList list = node->get_children();
408 for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
410 visit_node(root, *iter, indentation + 2); //recursive
416 void output_common_hpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs) {
417 o << "class " << groupClass(group) << " : public libt2n::command\n"
420 << " friend class boost::serialization::access;\n"
421 << " template<class Archive>\n"
422 << " void serialize(Archive & ar, const unsigned int /* version */)\n"
423 << " {ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(libt2n::command);}\n"
426 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
427 o << "class " << it->ret_classname() << " : public libt2n::result\n"
430 << " " << it->ret_type << " res;\n"
431 << " friend class boost::serialization::access;\n"
432 << " template<class Archive>\n"
433 << " void serialize(Archive & ar, const unsigned int /* version */)\n"
435 << " ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(libt2n::result);\n"
436 << " ar & BOOST_SERIALIZATION_NVP(res);\n"
439 << " " << it->ret_classname() << "() {}\n"
440 << " " << it->ret_classname() << "(const " << it->ret_type << " &_res) : res(_res) {}\n"
441 << " " << it->ret_type << " get_data() { return res; }\n"
444 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
445 o << "class " << it->cmd_classname() << " : public " << groupClass(group) << "\n"
448 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
449 o << " " << ait->second.noref() << " " << ait->first << ";\n";
451 o << " friend class boost::serialization::access;\n"
452 << " template<class Archive>\n"
453 << " void serialize(Archive & ar, const unsigned int /* version */)\n"
455 << " ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(" << groupClass(group) << ");\n";
456 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
457 o << " ar & BOOST_SERIALIZATION_NVP(" << ait->first << ");\n";
460 // default constructor
464 << " " << it->cmd_classname() << "() {}\n";
466 // constructor taking all arguments
467 o << " " << it->cmd_classname() << "(";
468 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
469 if (ait!=it->args.begin()) o << ", ";
470 o << ait->second << " _" << ait->first;
473 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
474 if (ait!=it->args.begin()) o << ", ";
475 o << ait->first << "(_" << ait->first << ")";
478 << " libt2n::result* operator()();\n"
483 void output_common_cpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs, const std::string &common_hpp) {
484 o << "#include \"" << common_hpp << "\"\n"
485 << "#include <boost/serialization/export.hpp>\n"
487 << "/* register types with boost serialization */\n";
488 o << "BOOST_CLASS_EXPORT(" << groupClass(group) << ")\n";
489 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
490 o << "BOOST_CLASS_EXPORT("<<it->ret_classname()<<")\n"
491 << "BOOST_CLASS_EXPORT("<<it->cmd_classname()<<")\n";
495 void output_client_hpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs) {
496 o << "#include <command_client.hxx>\n";
498 o << "class " << groupClass(group) << "_client : public libt2n::command_client\n"
501 << groupClass(group) << "_client(libt2n::client_connection &_c,\n"
502 << " long long _command_timeout_usec=command_timeout_usec_default,\n"
503 << " long long _hello_timeout_usec=hello_timeout_usec_default)\n"
504 << " : libt2n::command_client(_c,_command_timeout_usec,_hello_timeout_usec)\n"
506 for (std::list<t2n_procedure>::const_iterator pit=procs.begin();pit!=procs.end();++pit) {
507 o << " " << *pit << ";\n";
512 void output_client_cpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs, const std::string &common_hpp, const std::string &common_cpp, const std::string &client_hpp) {
513 o << "#include \"" << client_hpp << "\"\n"
514 << "#include \"" << common_hpp << "\"\n"
516 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
517 o << "libt2n::result* " << it->cmd_classname() << "::operator()() { return NULL; }\n";
520 for (std::list<t2n_procedure>::const_iterator pit=procs.begin();pit!=procs.end();++pit) {
521 o << pit->ret_type << " " << groupClass(group) << "_client::" << pit->name << "(" << pit->args << ")\n"
523 << " libt2n::result_container rc;\n"
524 << " send_command(new " << pit->cmd_classname() << "(";
525 for (t2n_procedure::Args::const_iterator ait=pit->args.begin();ait!=pit->args.end();++ait) {
526 if (ait!=pit->args.begin()) o << ", ";
530 << " " << pit->ret_classname() << "* res=dynamic_cast<" << pit->ret_classname() << "*>(rc.get_result());\n"
531 << " if (!res) throw libt2n::t2n_communication_error(\"result object of wrong type\");\n"
532 << " return res->get_data();\n"
536 // include in this compilation unit to ensure the compilation unit is used
538 // http://www.google.de/search?q=g%2B%2B+static+initializer+in+static+library
539 o << "#include \"" << common_cpp << "\"\n";
542 void output_server_hpp(std::ostream &o, const std::string & /* group */, const std::list<t2n_procedure> &procs, const std::string &common_hpp) {
543 o << "#include \"" << common_hpp << "\"\n";
545 // output function declarations
546 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it)
550 void output_server_cpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs, const std::string &common_hpp, const std::string &common_cpp) {
551 o << "#include \"" << common_hpp << "\"\n";
553 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
555 o << "libt2n::result* " << it->cmd_classname() << "::operator()() { return new " << it->ret_classname() << "(" << it->name << "(";
556 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
557 if (ait!=it->args.begin()) o << ", ";
562 o << "#include \"" << common_cpp << "\"\n";
565 struct header_file : public std::ofstream
567 header_file(const char* fname) : std::ofstream(fname) {
568 std::cerr << "create header: '" << fname << "'" << std::endl;
569 std::string macro(replace(toupper(fname),'.','_'));
570 *this << "// automatically generated code (generated by libt2n-codegen " << VERSION << ") - do not edit\n" << std::endl;
571 *this << "#ifndef " << macro << "\n"
572 << "#define " << macro << "\n";
575 *this << "#endif" << std::endl;
579 struct cpp_file : public std::ofstream
581 cpp_file(const char* fname) : std::ofstream(fname) {
582 std::cerr << "create cpp: '" << fname << "'" << std::endl;
583 *this << "// automatically generated code - do not edit\n" << std::endl;
588 main(int argc, char* argv[])
590 // todo: maybe use getopt
591 if ((argc>1)&&(std::string(argv[1])=="--version")) {
592 std::cerr << VERSION << std::endl;
597 std::cerr << "Usage: " << argv[0] << "default-group gccxml-file1 gccxml-file2 ... " << std::endl;
602 std::string group(argv[1]);
603 std::list<std::string> xmlfiles;
604 for (int i=2;i<argc;++i)
605 xmlfiles.push_back(argv[i]);
607 std::string prefix=group+"_";
608 std::list<t2n_procedure> procedures;
609 for (std::list<std::string>::iterator it=xmlfiles.begin();it!=xmlfiles.end();++it) {
610 std::cerr << "Parse " << *it << std::endl;
612 const std::list<t2n_procedure> &p(parser.get_procedures());
613 std::copy(p.begin(), p.end(), std::back_inserter(procedures));
616 std::cerr << "Procedures:" << std::endl;
617 for (std::list<t2n_procedure>::const_iterator it=procedures.begin();it!=procedures.end();++it)
618 std::cerr << *it << ";" << std::endl;
620 std::string common_hpp_fname(prefix+"common.hxx");
621 std::string common_cpp_fname(prefix+"common.cpp");
622 std::string client_hpp_fname(prefix+"client.hxx");
623 std::string client_cpp_fname(prefix+"client.cpp");
624 std::string server_hpp_fname(prefix+"server.hxx");
625 std::string server_cpp_fname(prefix+"server.cpp");
627 header_file common_hpp(common_hpp_fname.c_str());
628 common_hpp << "// boost serialization is picky about order of include files => we have to include this one first\n"
629 << "#include \"codegen-stubhead.hxx\"\n"
630 << "#include \"" << group << ".hxx\"\n";
632 output_common_hpp(common_hpp, group, procedures);
634 cpp_file common_cpp(common_cpp_fname.c_str());
635 output_common_cpp(common_cpp, group, procedures, common_hpp_fname);
637 header_file client_hpp(client_hpp_fname.c_str());
638 client_hpp << "// boost serialization is picky about order of include files => we have to include this one first\n"
639 << "#include \"codegen-stubhead.hxx\"\n"
640 << "#include \"" << group << ".hxx\"\n";
641 output_client_hpp(client_hpp, group, procedures);
643 cpp_file client_cpp(client_cpp_fname.c_str());
644 output_client_cpp(client_cpp, group, procedures, common_hpp_fname, common_cpp_fname, client_hpp_fname);
646 header_file server_hpp(server_hpp_fname.c_str());
647 output_server_hpp(server_hpp, group, procedures, common_hpp_fname);
649 cpp_file server_cpp(server_cpp_fname.c_str());
650 output_server_cpp(server_cpp, group, procedures, common_hpp_fname, common_cpp_fname);
651 }catch(const parse_error &e){
652 std::cerr << e.what() << std::endl;