The Middleware Writer takes two kinds of input and writes C++
code which can be used as part of a distributed application.
Input:
The two kinds of input are C++ include files and "Middle"
files. A Middle file is required as input; include files
are optional. The Middle language is something we made up
and is used to specify what gets written and how. Here is
an example of Middle code:
MessageManager
(vector<vector<double> >, map<string, double>)
(list<Account>, string [10])
//(vector<Account>, string [10])
}
The name you give, in this case, MessageManager, is used as the
name of the generated class.
After the name, you specify one or more lines as needed. The
program will write one or two methods based on each line. The
lines have the following general form:
(T1 [@AS @HI], T2 [@AS @HI], .... Tn [@AS @HI]) [@OUT @IN @MSGID.*]
Each line begins with a left paren. T1 through Tn are types and
should follow C++ syntax. The braces indicate that what is
contained in them is optional. Here are some more examples:
MessageManager
(double, string)
(long, long)
}
This will produce a class called MessageManager. Methods called
Send and Receive are written. The method prototypes would be:
int Send(Buffer*, const double, const string&);
int Receive(Buffer*, double&, string&);
int Send(Buffer*, const long, const long);
int Receive(Buffer*, long&, long&);
If instead you wrote:
MessageManager
(double, string) @OUT
(long, long) @OUT
}
only the Send methods will be written. By default both Send and
Receive methods are written. If you only want one of them, use @IN
or @OUT to tailor the output.
Sometimes one only needs to send a subset of a container. The
adaptive send option, @AS, is helpful in such circumstances.
This option is only applicable with container classes and makes
the most sense with a container that is sorted:
Message
(set<string> @AS, double)
}
In this example, the generated Send function would take a
set<string>::const_iterator and an integer count of how many
items to send. This is handy because it helps to avoid copying
a subset of the set into another container and then passing that
object to the generated code. @AS may only be used at the top
most level. In other words, this won't work:
Message
(vector<set<int> @AS >)
}
The @HI option stands for hinted insert. The sorted container classes: (multi)set, (multi)map and rb_tree have insert functions that take a hint. If you specify @HI, the generated code will use the insert function that takes a hint. This option can be easily misused. There are several things to consider:
Message
(set<int, greater<int> > @HI)
}
MsgManager
(set<int> @AS @HI)
}
And another:
MsgManager
(set<int, greater<int> >)
}
It is ok to use greater<> with containers, but you can't mix it
with @HI. For the time being @HI is only supported at the
top most level. So this won't work:
Nope
(vector<set<int> @HI >)
}
This option is strictly performance related. The tests we've done indicate using the hinted insert function cuts the elapsed time by 50%. (Those tests aren't comparing two versions of Receive(). They just time how long it takes to insert into a container with and without hinted insert.)
The options have to be in upper case. // or /* ... */ comments are acceptable in Middle code. A lone } is used to indicate the end of the construct.
The other kind of input is C++ include files. There are some restrictions in this area. We don't support:
We accept namespace definitions, but we don't organize types with
respect to namespaces. If you have two types with the same name
in different namespaces, we will flag that as an error. Template
support is coming along, but isn't finished. We don't support
template template arguments at this time.
Here is an example of an include file that is acceptable to
the program:
#ifndef MENU
#define MENU
#include <string>
#include <vector>
#include <iostream>
class Buffer;
namespace llama {
struct Base
{
Base() {}
int Send(Buffer*, int = 0) const; // These prototypes have to
int Receive(Buffer*); // be added to your code.
std::vector<std::string> baseFileNames;
};
short const INSERT = 301;
short const DELETE = 302;
class Derived : public Base
{
public:
Derived() {}
int Send(Buffer*, int = 0) const;
int Receive(Buffer*);
private:
short op[12];
unsigned int offset;
};
class FileInfo
{
public:
FileInfo()
{}
~FileInfo()
{}
friend struct FWKey;
int Send(Buffer*, int = 0) const;
int Receive(Buffer*);
void Reset();
private:
#ifdef SERVER_SIDE
short isnew;
long modTime;
#endif
std::string introducedFileName;
Derived parts;
};
struct FWKey
{
const std::string& operator() (const FileInfo* inst)
{
return inst->introducedFileName;
}
};
}
#endif
Given the above definitions we can write Middle code that uses
the types:
Messages
(rb_tree<FileInfo*, string, FWKey>)
(vector<Base*>, short)
}
Don't let the rb_tree throw you. We prefer using the red-black tree that underpins map<> to map<>. And so the program is able to work with it. Note that the Value precedes the Key argument.
There is one option that is invoked from include files. The #ifdef SERVER_SIDE line above is interpreted by the generator in a special way. This option requires some explaining.
Output:
A C++ class definition is returned as text on the screen.
Setup Instructions:
Two function prototypes have to be added to types that may
have instances sent/received:
int Send(Buffer*, int = 0) const;
int Receive(Buffer*);
It is your responsibility to add these to your code. The
function definitions will be produced by the Writer.
Depending on the type and how it is used, a method called
Reset may also be written. In that case the following
prototype is also needed.
void Reset();
The Send/Receive functions return 1 if successful or
0 if not.
If you sent header files as input, make sure those files are included prior to a generated file.
Warnings:
If you intend to use the generated output in a heterogenous
environment, you should take care with the sizeof's for the various
basic types. There is ongoing work among hardware companies and
C++ compiler vendors to reduce the difficulties in this area.
We don't address little/big endian differences.
Be careful when it comes to pointers. The Send functions don't
check that pointers are non null.
We support Boost::multi_index_container, but with some
limitations:
1. At most one sequenced index.
2. If a sequenced index is part of the class makeup, it
must be the first index listed in the "IndexSpecifierList."
3. Transmission of a multi_index_container will probably not
preserve the relative order of elements that are
considered equal under an ordered_non_unique index.
These limitations permit the transmission of a
multi_index_container to a "single index container" such as vector
or list if desired.
Tips:
Make sure any header files you send as input compile before you
send them.
Dealing with Stragglers:
Often a new version of a server is required to support multiple
versions of ambassadors(clients). The following is an idea of
how to go about that.
release
1.1 1.2 1.3 1.4
---------------------------------------------------------
class name Account Account Account_13 Account_13
The class name incorporates the release in which it was last
changed. Let's say that the 1.3 version of the server is
required to support 1.1 clients. To do this the server is
built with both the Account and Account_13 definitions.
When only additions to Account are needed between releases,
the software might be structured like this
AccountBase
|
Account
|
Account_13
If members need to be relocated out of Account in 1.3, the
hierarchy might be
AccountBase
/ \
Account Account_13
In either case, the 1.3 server may use polymorphism and
accomplish what is needed pretty simply. When working
with a 1.1 ambassador, it produces 1.1 data by executing
what originally was 1.1 code.
Protocol:
There are a few simple rules that are followed:
When a container or string is sent, the size/length of
the container is sent ahead of the elements/characters.
When an object is sent via a base class pointer, an
integer identifier, produced by the Writer, is sent
ahead of the object data. The identifier is used on
the receiving end to construct the correct object.
Project Goals:
The interfaces should be easy to learn.
The code should build and run correctly.
The code should be thread safe.
The code should execute efficiently.
Advantages:
We don't impose a language mapping or a second type system on
users. Therefore, users don't waste time copying data between
objects of the two type systems. That sort of copying requires
both programming effort and runtime cycles. It is also
possible to maintain data in different ways on the two sides of
the application. For example, a client may send a set<int>
and a server could receive the data as a vector<int>.
Other approaches to marshalling C++ objects often use a library
that defines templates. While templates are often helpful,
there are difficulties with using them in this context.
For one thing having a variable number of arguments isn't
accomplished satisfactorily with the existing template mechanism.
This leads to a performance penalty when a message is made up of
multiple objects. A second problem stems from the fact that C++
compilers don't output the C++ code they generate from templates.
This makes it difficult for users of an open source application
that has used a marshalling library to build the application.
They have to obtain a correct version of the library used by the
developer of the open source program in order to build the soft-
ware. With our approach, you publish both hand written files
and one or more generated files. This simplifies things for
users, and programs built with this approach are generally smaller
than those built with a library that defines C++ templates.
Competing approaches often require changes to a program to support
marshalling. We don't introduce macros and we don't require
duplication of information to support marshalling.
We don't know of other middleware frameworks that support
hinted insert.
Other approaches use buffers, but to our knowledge they use
buffering internally and do not permit the rest of the application
to access the buffers. In our approach, you pass a buffer to the
generated middleware when you want a function to use it and you may
use the buffer for other purposes in other parts of the application.
Other:
We use C and a library called CGIC to support the forms on the
site. The author of CGIC requires the following paragraph be
included here. It has no bearing on our ability to offer this
service freely. C++ source code obtained via this site,
whether written by a person or a computer, is free. In the
spirit of days gone by, the C++ code is not copyrighted.
CGIC, copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002 by Thomas Boutell
and Boutell.Com, Inc.. Permission is granted to use CGIC in any
application, commercial or noncommercial, at no cost. HOWEVER,
this copyright paragraph must appear on a "credits" page accessible
in the public online and offline documentation of the program.
Modified versions of the CGIC library should not be distributed without
the attachment of a clear statement regarding the author of the
modifications, and this notice may in no case be removed.
Modifications may also be submitted to the author for inclusion
in the main CGIC distribution.
The above does not impinge on our freedom to offer code generation
services free of copyrights. C++ source code obtained through this
website is not copyrighted.
| C++ in a Nutshell By Ray Lischner. |
|