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 - (talk2neighbor)
21 \section intro_sec Introduction
22 libt2n (talk2neighbor) 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. 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.
24 It then generates the stubs needed.
25 The exported procedures can be grouped. For each group the code generator is called which generates 6 outputfiles: 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.
27 \section install_sec Installation
29 \subsection requirements Requirements
30 - boost <http://www.boost.org/> (serialization <http://www.boost.org/libs/serialization/doc/>)
31 - gccxml <http://www.gccxml.org>
32 - libxmlpp <http://libxmlplusplus.sourceforge.net/>
34 \section usage Usage example
36 In this example we create two packages:
37 - server program and library to connect to the server. The server exports a simple procedure using one group: "t2nexample"
38 - client program using the library
40 \subsection server Example server program and client library
42 \par The procedure to export (input for the code generator - libt2n-codegen): t2nexample.cpp:
43 \verbinclude libt2n-example1/t2nexample.cpp
45 \par Required includes must be put into a seperate group header file: t2nexample.hxx:
46 \verbinclude libt2n-example1/t2nexample.hxx
48 \par The server program:
49 \verbinclude libt2n-example1/server.cpp
51 \par Using autoconf and automake to build a example server program and a client library.
52 In the configure.in(.ac) we put a check for libt2n:
53 \verbinclude libt2n-example1/configure.in
54 Writing the Makefile.am isn't difficult either:
55 \verbinclude libt2n-example1/Makefile.am
57 \subsection client Client using the library
58 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)
59 \par We only have to check that the library is installed
60 \verbinclude libt2n-example1-client/configure.in
62 \verbinclude libt2n-example1-client/Makefile.am
63 \par The client program
64 \verbinclude libt2n-example1-client/client.cpp
69 \example t2nexample.cpp
70 example input for libt2n-codegen
73 #include <libxml++/libxml++.h>
80 #include <boost/lexical_cast.hpp>
86 //! map group to class name
88 groupClass(const std::string &group) {
89 return std::string("cmd_group_")+group;
92 //! convert string to upper case
94 toupper(std::string s) {
95 for (unsigned i=0; i<s.length(); ++i) s[i]=toupper(s[i]);
99 //! replace all characters f by r in string s
101 replace(std::string s, char f, char r) {
102 for (unsigned i=0; i<s.length(); ++i) if (s[i]==f) s[i]=r;
106 //! strip prefix from string s
108 \return string s without prefix or an empty string on error
111 strip(std::string s, std::string prefix)
114 if ( (prefix.length()>s.length() ) || ( std::string(s,0,prefix.length())!=prefix ) ) return error;
115 return std::string(s, prefix.length(), s.length()-prefix.length());
118 //! get child element by id
120 \return pointer to element having id or null on error
121 \todo find libxmlpp pendant
123 const xmlpp::Element* get_element_by_id(const xmlpp::Element* element, const std::string &id)
125 const xmlpp::Attribute* cid = element->get_attribute("id");
126 if ( cid && ( cid->get_value() == id)) return element;
128 //Recurse through child nodes:
129 xmlpp::Node::NodeList list = element->get_children();
130 for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
132 const xmlpp::Element* element = dynamic_cast<const xmlpp::Element*>(*iter);
134 const xmlpp::Element* match = get_element_by_id(element, id);
135 if (match) return match;
142 get_file(const xmlpp::Element* root, const std::string &file_id)
145 const xmlpp::Element* e=get_element_by_id(root, file_id);
146 if ((!e)||(!e->get_attribute("name"))) return error;
147 return e->get_attribute("name")->get_value();
150 //! get namespace by id
152 \return namespace name or empty string on error
154 std::string get_namespace(const xmlpp::Element* root, const std::string &id)
157 const xmlpp::Element* element(get_element_by_id(root, id));
158 if ((!element)||(!element->get_attribute("name"))) return error;
159 return element->get_attribute("name")->get_value();
162 //! extract group from attributes
164 extract_group(const std::string &attrs)
166 // todo: improve this
168 std::string to_match("gccxml(libt2n-");
169 std::string::size_type p(attrs.find(to_match));
170 if (p==std::string::npos) return error;
171 std::string group(attrs, p+to_match.length(), attrs.length());
172 p=group.find_first_of(')');
173 assert(p!=std::string::npos);
174 return std::string(group,0,p);
180 std::string noref_name;
181 bool operator==(const type_info& o) {return (name==o.name) && (noref_name == o.noref_name);}
182 std::string noref() const {return noref_name.empty() ? name : noref_name;}
185 std::ostream &operator<<(std::ostream &o, const type_info &t) {
190 struct parse_error : public std::runtime_error
192 parse_error(const std::string &file, unsigned line, const std::string &msg)
193 : std::runtime_error(file+":"+boost::lexical_cast<std::string>(line)+": error: "+msg)
199 \return type name or empty string on error
201 type_info get_type(const xmlpp::Element* root, const std::string &id)
204 const xmlpp::Element* element(get_element_by_id(root, id));
205 if (!element) return error;
207 // TODO: not yet complete
208 // if we recurse - when do we stop?
209 // if it is a typedef? yes? (hmm if the typedef is in the file parsed this will not work)
211 // TODO: const and reference types handling is a ugly hack
213 std::string tag(element->get_name());
214 if (tag=="ReferenceType") {
215 assert(element->get_attribute("type"));
216 type_info ret(get_type(root, element->get_attribute("type")->get_value()));
217 if (ret==error) return error;
218 // at the moment we only support const &
219 // todo: nice error message!
220 if ((ret.noref_name=strip(ret.name,"const ")).empty()) return error;
221 ret.name=ret.name+"&";
223 }else if (tag=="CvQualifiedType") {
224 assert(element->get_attribute("type"));
225 type_info ret(get_type(root, element->get_attribute("type")->get_value()));
226 if (ret==error) return error;
227 ret.name=std::string("const ")+ret.name;
229 }else if (tag=="PointerType") {
234 assert(element->get_attribute("name"));
236 if (element->get_attribute("context")) {
237 ret.name=get_namespace(root, element->get_attribute("context")->get_value());
241 // do not explicitely add ::
244 ret.name+=element->get_attribute("name")->get_value();
250 typedef std::list<std::pair<std::string, type_info> > Args;
257 std::string ret_classname() const {
258 return name+mangled+"_res";
260 std::string cmd_classname() const {
261 return name+mangled+"_cmd";
265 std::ostream &operator<<(std::ostream &o, const t2n_procedure::Args &args) {
266 for (t2n_procedure::Args::const_iterator it=args.begin();it!=args.end();++it) {
267 if (it!=args.begin()) o << ", ";
268 o << it->second << " " << it->first;
273 std::ostream &operator<<(std::ostream &o, const t2n_procedure &f) {
274 o << f.ret_type << " " << f.name << "(" << f.args << ")";
281 Parser(const std::string &fname) : m_fname(fname) {}
283 std::list<t2n_procedure> get_procedures() {
284 xmlpp::DomParser parser;
285 // parser.set_validate();
286 parser.set_substitute_entities(); //We just want the text to be resolved/unescaped automatically.
287 parser.parse_file(m_fname);
291 const xmlpp::Node* pNode = parser.get_document()->get_root_node(); //deleted by DomParser.
292 const xmlpp::Element* root = dynamic_cast<const xmlpp::Element*>(pNode);
300 std::list<t2n_procedure> m_procedures;
302 void parse_function(const xmlpp::Element* root, const xmlpp::Node* node) {
303 const xmlpp::Element* element = dynamic_cast<const xmlpp::Element*>(node);
304 if (!element) return;
306 const xmlpp::Attribute* attributes = element->get_attribute("attributes");
307 const xmlpp::Attribute* name = element->get_attribute("name");
308 const xmlpp::Attribute* mangled = element->get_attribute("mangled");
309 const xmlpp::Attribute* returns = element->get_attribute("returns");
310 if ((!attributes)||(!name)||(!mangled)||(!returns)) return;
312 // check wether the procedure is marked (TODO: improve)
313 // attributes are speparated by spaces?
316 if (extract_group(attributes->get_value()).empty()) return;
318 // we need the return type
319 f.ret_type=get_type(root, returns->get_value());
320 f.name=name->get_value();
321 f.mangled=mangled->get_value();
323 xmlpp::Node::NodeList list = node->get_children("Argument");
324 for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
326 const xmlpp::Element* arg = dynamic_cast<const xmlpp::Element*>(*iter);
328 assert(arg->get_name() == "Argument");
329 assert(arg->get_attribute("name"));
330 assert(arg->get_attribute("type"));
331 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())));
332 // todo: ugly - could be any other error
333 if (f.args.back().second.name.empty()) {
334 assert(element->get_attribute("file"));
335 assert(element->get_attribute("line"));
336 throw parse_error(get_file(root, element->get_attribute("file")->get_value()),
337 boost::lexical_cast<unsigned>(element->get_attribute("line")->get_value())-1,
338 std::string("type of parameter `")+f.args.back().first+"' not (yet?) supported");
342 std::cerr << "Found function: " << f << std::endl;
343 m_procedures.push_back(f);
346 void visit_node(const xmlpp::Element* root, const xmlpp::Node* node = NULL, unsigned int indentation = 0)
348 if (!node) node=root;
350 const xmlpp::ContentNode* nodeContent = dynamic_cast<const xmlpp::ContentNode*>(node);
351 const xmlpp::TextNode* nodeText = dynamic_cast<const xmlpp::TextNode*>(node);
352 const xmlpp::CommentNode* nodeComment = dynamic_cast<const xmlpp::CommentNode*>(node);
354 if(nodeText && nodeText->is_white_space()) //Let's ignore the indenting - you don't always want to do this.
357 std::string nodename = node->get_name();
359 if(!nodeText && !nodeComment && !nodename.empty()) //Let's not say "name: text".
361 if (node->get_name() == "Function") parse_function(root, node);
365 //Recurse through child nodes:
366 xmlpp::Node::NodeList list = node->get_children();
367 for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
369 visit_node(root, *iter, indentation + 2); //recursive
375 void output_common_hpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs) {
376 o << "class " << groupClass(group) << " : public libt2n::command\n"
379 << " friend class boost::serialization::access;\n"
380 << " template<class Archive>\n"
381 << " void serialize(Archive & ar, const unsigned int /* version */)\n"
382 << " {ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(libt2n::command);}\n"
385 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
386 o << "class " << it->ret_classname() << " : public libt2n::result\n"
389 << " " << it->ret_type << " res;\n"
390 << " friend class boost::serialization::access;\n"
391 << " template<class Archive>\n"
392 << " void serialize(Archive & ar, const unsigned int /* version */)\n"
394 << " ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(libt2n::result);\n"
395 << " ar & BOOST_SERIALIZATION_NVP(res);\n"
398 << " " << it->ret_classname() << "() {}\n"
399 << " " << it->ret_classname() << "(const " << it->ret_type << " &_res) : res(_res) {}\n"
400 << " " << it->ret_type << " get_data() { return res; }\n"
403 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
404 o << "class " << it->cmd_classname() << " : public " << groupClass(group) << "\n"
407 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
408 o << " " << ait->second.noref() << " " << ait->first << ";\n";
410 o << " friend class boost::serialization::access;\n"
411 << " template<class Archive>\n"
412 << " void serialize(Archive & ar, const unsigned int /* version */)\n"
414 << " ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(" << groupClass(group) << ");\n";
415 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
416 o << " ar & BOOST_SERIALIZATION_NVP(" << ait->first << ");\n";
419 // default constructor
423 << " " << it->cmd_classname() << "() {}\n";
425 // constructor taking all arguments
426 o << " " << it->cmd_classname() << "(";
427 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
428 if (ait!=it->args.begin()) o << ", ";
429 o << ait->second << " _" << ait->first;
432 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
433 if (ait!=it->args.begin()) o << ", ";
434 o << ait->first << "(_" << ait->first << ")";
437 << " libt2n::result* operator()();\n"
442 void output_common_cpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs, const std::string &common_hpp) {
443 o << "#include \"" << common_hpp << "\"\n"
444 << "#include <boost/serialization/export.hpp>\n"
446 << "/* register types with boost serialization */\n";
447 o << "BOOST_CLASS_EXPORT(" << groupClass(group) << ")\n";
448 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
449 o << "BOOST_CLASS_EXPORT("<<it->ret_classname()<<")\n"
450 << "BOOST_CLASS_EXPORT("<<it->cmd_classname()<<")\n";
454 void output_client_hpp(std::ostream &o, const std::string &group, const std::list<t2n_procedure> &procs) {
455 o << "#include <command_client.hxx>\n";
457 o << "class " << groupClass(group) << "_client : public libt2n::command_client\n"
460 << groupClass(group) << "_client(libt2n::client_connection &_c,\n"
461 << " long long _command_timeout_usec=command_timeout_usec_default,\n"
462 << " long long _hello_timeout_usec=hello_timeout_usec_default)\n"
463 << " : libt2n::command_client(_c,_command_timeout_usec,_hello_timeout_usec)\n"
465 for (std::list<t2n_procedure>::const_iterator pit=procs.begin();pit!=procs.end();++pit) {
466 o << " " << *pit << ";\n";
471 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) {
472 o << "#include \"" << client_hpp << "\"\n"
473 << "#include \"" << common_hpp << "\"\n"
475 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
476 o << "libt2n::result* " << it->cmd_classname() << "::operator()() { return NULL; }\n";
479 for (std::list<t2n_procedure>::const_iterator pit=procs.begin();pit!=procs.end();++pit) {
480 o << pit->ret_type << " " << groupClass(group) << "_client::" << pit->name << "(" << pit->args << ")\n"
482 << " libt2n::result_container rc;\n"
483 << " send_command(new " << pit->cmd_classname() << "(";
484 for (t2n_procedure::Args::const_iterator ait=pit->args.begin();ait!=pit->args.end();++ait) {
485 if (ait!=pit->args.begin()) o << ", ";
489 << " " << pit->ret_classname() << "* res=dynamic_cast<" << pit->ret_classname() << "*>(rc.get_result());\n"
490 << " if (!res) throw libt2n::t2n_communication_error(\"result object of wrong type\");\n"
491 << " return res->get_data();\n"
495 // include in this compilation unit to ensure the compilation unit is used
497 // http://www.google.de/search?q=g%2B%2B+static+initializer+in+static+library
498 o << "#include \"" << common_cpp << "\"\n";
501 void output_server_hpp(std::ostream &o, const std::string & /* group */, const std::list<t2n_procedure> &procs, const std::string &common_hpp) {
502 o << "#include \"" << common_hpp << "\"\n";
504 // output function declarations
505 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it)
509 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) {
510 o << "#include \"" << common_hpp << "\"\n";
512 for (std::list<t2n_procedure>::const_iterator it=procs.begin();it!=procs.end();++it) {
514 o << "libt2n::result* " << it->cmd_classname() << "::operator()() { return new " << it->ret_classname() << "(" << it->name << "(";
515 for (t2n_procedure::Args::const_iterator ait=it->args.begin();ait!=it->args.end();++ait) {
516 if (ait!=it->args.begin()) o << ", ";
521 o << "#include \"" << common_cpp << "\"\n";
524 struct header_file : public std::ofstream
526 header_file(const char* fname) : std::ofstream(fname) {
527 std::cerr << "create header: '" << fname << "'" << std::endl;
528 std::string macro(replace(toupper(fname),'.','_'));
529 *this << "// automatically generated code (generated by libt2n-codegen " << VERSION << ") - do not edit\n" << std::endl;
530 *this << "#ifndef " << macro << "\n"
531 << "#define " << macro << "\n";
534 *this << "#endif" << std::endl;
538 struct cpp_file : public std::ofstream
540 cpp_file(const char* fname) : std::ofstream(fname) {
541 std::cerr << "create cpp: '" << fname << "'" << std::endl;
542 *this << "// automatically generated code - do not edit\n" << std::endl;
547 main(int argc, char* argv[])
549 // todo: maybe use getopt
550 if ((argc>1)&&(std::string(argv[1])=="--version")) {
551 std::cerr << VERSION << std::endl;
556 std::cerr << "Usage: " << argv[0] << "default-group gccxml-file1 gccxml-file2 ... " << std::endl;
561 std::string group(argv[1]);
562 std::list<std::string> xmlfiles;
563 for (int i=2;i<argc;++i)
564 xmlfiles.push_back(argv[i]);
566 std::string prefix=group+"_";
567 std::list<t2n_procedure> procedures;
568 for (std::list<std::string>::iterator it=xmlfiles.begin();it!=xmlfiles.end();++it) {
569 std::cerr << "Parse " << *it << std::endl;
571 const std::list<t2n_procedure> &p(parser.get_procedures());
572 std::copy(p.begin(), p.end(), std::back_inserter(procedures));
575 std::cerr << "Procedures:" << std::endl;
576 for (std::list<t2n_procedure>::const_iterator it=procedures.begin();it!=procedures.end();++it)
577 std::cerr << *it << ";" << std::endl;
579 std::string common_hpp_fname(prefix+"common.hxx");
580 std::string common_cpp_fname(prefix+"common.cpp");
581 std::string client_hpp_fname(prefix+"client.hxx");
582 std::string client_cpp_fname(prefix+"client.cpp");
583 std::string server_hpp_fname(prefix+"server.hxx");
584 std::string server_cpp_fname(prefix+"server.cpp");
586 header_file common_hpp(common_hpp_fname.c_str());
587 common_hpp << "// boost serialization is picky about order of include files => we have to include this one first\n"
588 << "#include \"codegen-stubhead.hxx\"\n"
589 << "#include \"" << group << ".hxx\"\n";
591 output_common_hpp(common_hpp, group, procedures);
593 cpp_file common_cpp(common_cpp_fname.c_str());
594 output_common_cpp(common_cpp, group, procedures, common_hpp_fname);
596 header_file client_hpp(client_hpp_fname.c_str());
597 client_hpp << "// boost serialization is picky about order of include files => we have to include this one first\n"
598 << "#include \"codegen-stubhead.hxx\"\n"
599 << "#include \"" << group << ".hxx\"\n";
600 output_client_hpp(client_hpp, group, procedures);
602 cpp_file client_cpp(client_cpp_fname.c_str());
603 output_client_cpp(client_cpp, group, procedures, common_hpp_fname, common_cpp_fname, client_hpp_fname);
605 header_file server_hpp(server_hpp_fname.c_str());
606 output_server_hpp(server_hpp, group, procedures, common_hpp_fname);
608 cpp_file server_cpp(server_cpp_fname.c_str());
609 output_server_cpp(server_cpp, group, procedures, common_hpp_fname, common_cpp_fname);
610 }catch(const parse_error &e){
611 std::cerr << e.what() << std::endl;