Chapter 2. Automatically Create Server and Client Sources

Table of Contents

2.1. Sources From your C++ Headers
2.1.1. Parsing The Header File
2.1.2. Generating The Files
2.2. Sources From Introspection

2.1. Sources From your C++ Headers

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.

2.1.1. Parsing The Header File

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:

ulxr_constructor

Exists only for historical reasons and has no effect.

ulxr_methods

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.

ulxr_function

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.

[Note]The Difficulty of Parsing C++

Parsing even valid C++ source code is is a difficult task. xml2ulxr handles most of the common files. But there may be problems with some code, for example nested templates. In the worst case you may have to remove or comment out the problematic parts while parsing. Probably it is possible to use #define's with the preprocessor to do this automatically.

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 ulxr/stubber/ulxr2xml/dcopidl_test.h and ulxr/stubber/ulxr2xml/ulxridl_test.h give an overview of what the parser is able to process.

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.

2.1.2. Generating The Files

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:

--gen-server

Generate files that are used on the server side to receive the requests and forward them to the actual worker methods.

--gen-client

Generate files used for the client code to transform a regular C++ invocation into an XML-RPC method call sent to the network.

--gen-class=classname

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.

--gen-funcs=prefix

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.

[Note]Also Select Client or Server Code

This option additionally needs --gen-client and --gen-server to select the desired source type.

--file-name=filename.xml

The name of the XML file with the class data.

--dest-dir=dest-path

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.

--disable-timestamp

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:

1

Instantiate the class for the actual worker code.

2

Prepare a multithreaded method dispatcher or something similar as you need it.

3

Instantiate the object which connects the XML-RPC methods with the worker code in the class. In case there are several worker objects you have to instantiate a connector for each of them. The method registration is done implicitly in the constructor.

4

Register the free functions. In case you have several files with free functions you need to call each of the according register functions.

5

Once your server has finished you should clean up and remove all methods pointing to free functions. The deregistration of methods in objects is implicitly done in their destructors when the object leaves its scope.


  ulxr::TcpIpConnection server_conn (true, host, port);
  ulxr::HttpProtocol server_prot(&server_conn);

  UlxrIdlTest worker;                                         1
  ulxr::MultiThreadRpcServer handler(&server_prot, 2, false); 2
  UlxrIdlTestServer server(handler, worker);                  3
  UlxrIdlTestFuncs_setupServerMethods(handler);               4

  ...

  UlxrIdlTestFuncs_removeServerMethods(handler);              5

Preparing the client code requires also some additional steps:

1

Store the credentials for remote access and the name of the remote resource in variables since you might need them several times for different objects.

2

Instantiate the proxy object for the remote methods.

3

Free functions need additional setup for each file with exported functions.

4

Since the proxy object supports all the "exported" methods from the object on the server side the code looks exactly the same as it would on the remote machine.


  ulxr::TcpIpConnection client_conn (false, "localhost", 32000);
  ulxr::HttpProtocol client_prot(&client_conn);
  ulxr::Requester requester(&client_prot);

  ulxr::CppString user = "ali-baba";                                     1
  ulxr::CppString pass = "open-sesame";
  ulxr::CppString realm = "/RPC2";

  UlxrIdlTestClient client(requester, realm, user, pass);                2
  UlxrIdlTestFuncs_setClientCredentials(requester, realm, user, pass);   3

  client.remoteCall("abc");                                              4
  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