Table of Contents
The process for the creation of the source files from C++ headers is split into two parts:
The first application ulxr2xml
parses the
C++ header file and writes the constructors and methods you need to an intermediate
XML file.
In the second step xml2ulxr
reads the
XML file and creates a C++ header and the according implementation files.
An extra file is created where user defined information is located.
Splitting the process into two parts enables postprocessing the XML file with
simple methods. Otherwise a rather complicated C++ parser based on less known
tools like yacc
and lex
would have to
be changed.
Additionally one could create a different application for the second step. Based on the XML file it could create totally different user defined C++ sources.
If you integrate the two applications into your build process, your client and server files are automatically kept up to date with the production sources. You simply make the files depend on the according header file. Your specialized constuctors and method names are placed in the third file. This file is never updated automatically. Instead a template file is written from where you can copy-and-paste what you need and adjust the rest manually.
In the first step ulxr2xml
parses the header file
and outputs the
according XML data stream to standard output. If you want to have an XML file you
must redirect standard outout to the desired file. The only possible parameter is the name
of the C++ header file. All the classes in the header file are written to the
XML stream.
The parser needs markers whch methods and constructors shall be written to the XML stream. This is done by appending a pseudo keyword after one of the access modifiers public, protected or private before the definition.
The parser distinguishes between constructors and methods. Each of them uses a different keyword:
Exists only for historical reasons and has no effect.
All the methods after this keyword are written to the XML stream. The inclusion stops at the next access modifier unless it contains this keyword again.
Free function need markers as well. Unlike methods within a class each function needs its own marker.
Destructors are never included even if they have a starting marker. To make these keywords invisible for the compiler you define these two with empty content.
A sample class could then look like this:
#define ulxr_methods class ulxrIdlTest : public IdlInterface { ulxrIdlTest(int i, const std::string &s) : IdlInterface("name") { } private ulxr_methods: virtual std::string * url4() = 0; };
The following line shows how to invoke the C++ parser and write the XML stream to a file:
ulxr2xml header.h >header-data.xml
If you are familiar with with development around KDE and QT you are probably familiar with this approach.
It might also be interesting to know that the basis for this parser was taken from the KDE Project. For that reason it is able to parse (in the meaning of to ignore) the special keywords related to DCOP and QOBJECT like k_dcop or signals.
The Difficulty of Parsing C++ | |
---|---|
Parsing even valid C++ source code is is a difficult task.
There also an inherent limitation in the data types you can use. The generator is only able to handle data types used in XML-RPC that can be mapped to native C++ data types like int, std::string, float or const char*. No arrays or structures from XML-RPC are supported. On the other side, the generator is able to resolve pointers and references in parameters and return values. The test files In case you have a file which is not parsed correctly but you think this should be the case since it looks trivial: please post a bug report. |
In the second step the XML file is processed and the C++ files are created.
Each invocation of xml2ulxr
creates a set consisting of
three or more files depending on the given options and the XML file. The
filenames are composed of the according C++ class name and a file specific
extension depending on the intended type of code either for a server or a client.
A C++ header file with the filename extension "_ulxr_client.h".
A C++ implementation file with the filename extension "_ulxr_client.cpp".
A C++ implementation file containing user defined information with the filename ending in "_ulxr_client_user.cpp". If this file already exists it is never overwritten but a forth file ending in "_ulxr_client_user.cpp.new" is created. From there you must manually copy-and-paste the new parts.
A C++ header file with the filename extension "_ulxr_server.h".
A C++ implementation file with the filename extension "_ulxr_server.cpp".
A C++ implementation file containing user defined information with the filename ending in "_ulxr_server_user.cpp". This file is also never overwritten.
A C++ header file with the filename extension "_names.h" that contains #defines for the method names which is used for both the server and the client files. This file is also never overwritten.
When you export free functions there is another set of up to 6 more files containing
code for server, client and names. These filenames are built with the prefix you passed
via --gen-funcs plus the literal "Funcs"
plus the aforementioned suffixes to distinguish between server, client and names.
There are several options that influence the file creation process:
Generate files that are used on the server side to receive the requests and forward them to the actual worker methods.
Generate files used for the client code to transform a regular C++ invocation into an XML-RPC method call sent to the network.
Without additional options each of the classes in the XML file result in a set of header and source files. If you want to create files for only a selected set of classes you have to pass the class name using this option. When you need more than one class you have to pass this option multiple times.
Generate code for free functions. The prefix is prepended to filenames and member names similar to the code for classes to prevent name clashes. The method names of the client stubs itself remaing the same and are placed into the according namespaces.
Also Select Client or Server Code | |
---|---|
This option additionally needs --gen-client and --gen-server to select the desired source type. |
The name of the XML file with the class data.
Without further options the resulting files are written to the current directory. If you want a different destination you provide the desired path with this parameter.
When generating the code a timestamp is added into the comment header. Mainly for testing purposes this feature can be disabled.
The following line creates the C++ files
ulxrIdlTest_ulxr_server.h
,
ulxrIdlTest_ulxr_server.cpp
and ulxrIdlTest_ulxr_server_user.cpp
from the XML file
header-data.xml
. These files are based on
and they are written
to the subdirectory forwarders
. For better readability the long
command line is broken into two lines.
xml2ulxr --gen-server --file-name=header-data.xml \ --dest-dir=forwarders --gen-class=UlxrIdlTest
Once the files are generated there are just a few changes needed. On the server side you must register the existing production code with the XML-RPC method dispatcher:
ulxr::TcpIpConnection server_conn (true, host, port); ulxr::HttpProtocol server_prot(&server_conn); UlxrIdlTest worker; ulxr::MultiThreadRpcServer handler(&server_prot, 2, false); UlxrIdlTestServer server(handler, worker); UlxrIdlTestFuncs_setupServerMethods(handler); ... UlxrIdlTestFuncs_removeServerMethods(handler);
Preparing the client code requires also some additional steps:
ulxr::TcpIpConnection client_conn (false, "localhost", 32000); ulxr::HttpProtocol client_prot(&client_conn); ulxr::Requester requester(&client_prot); ulxr::CppString user = "ali-baba"; ulxr::CppString pass = "open-sesame"; ulxr::CppString realm = "/RPC2"; UlxrIdlTestClient client(requester, realm, user, pass); UlxrIdlTestFuncs_setClientCredentials(requester, realm, user, pass); client.remoteCall("abc"); remoteFunction(1,2);
And you should not forget to give the XML-RPC methods meaningful names.
The generator prepares macros which contain the method names. Both the macro and
method names are based on the class and methods to achieve unique naming. Most of
time you will want to choose better method names by editing the according header
*_names.h
. Don't forget that this file is never overwritten. So if the number of
exported methods increases you have to copy-and-paste the new methods manually from
*_names.h.new
. For better orientation a comment with the complete
method signature is prepended to each #define:
// mapped to: std::wstring * UlxrIdlTest::nextView1(int i, long int * l, std::string & s, const bool * b, char c) const #define ULXR_CALLTO_UlxrIdlTest_nextView1 \ ULXR_PCHAR("UlxrIdlTest_nextView1")
The last step is to complete the method description in the user files named
*_server_user.cpp
. Just change the last line of each block
to a meaningful comment about the remote method:
// mapped to: void first_url4(); (there are overloaded methods) method_adder.addMethod(ulxr::make_method(*this, &UlxrIdlTestServer::first_url4_ovr4), ulxr::Void::getValueName(), ULXR_CALLTO_UlxrIdlTest_first_url4_ovr4, ulxr::Signature(), ulxr_i18n(ULXR_PCHAR("Some descriptive comment about 'void UlxrIdlTest::first_url4()'."))); // TODO adjust comment