Introduction – Download – Tutorial – Documentation – Changelog – Contact
xPP
C++ Metaprocessor
Tutorial
This tutorial takes you through the steps required to create your own XPP templates, that is :
- How to make XPP generate source files for each of your instrumented classes
- How to enumerate a class' data members, methods, and method parameters
- How to use this information to construct source code
Our goal here will be to automatically generate some interface discovery code, which our classes will inherit from. The class we will instrument will be located in a pair of files named “myclass.h” and “myclass.cpp”, and It will look originally like the following :
myclass.h
:
#ifndef _MYCLASS_H
#define _MYCLASS_H
class
MyClass {
public:
MyClass();
virtual
~MyClass();
void Method1();
int
Method2();
void Method3(int i);
float
Method4(const char *s, int i);
int data1;
const
char *data2;
};
#endif // _MYCLASS_H
myclass.cpp :
#include "myclass.h"
MyClass::MyClass()
{
}
MyClass::~MyClass() {
}
void
MyClass::Method1() {
}
int MyClass::Method2() {
return
0;
}
void MyClass::Method3(int i) {
}
float
MyClass::Method4(const char *s, int i) {
return 0;
}
1. Setting up the project
The first thing we need to do is to create an xpp project file that references this class. To do this, we also need to create an xhd file, which will reference the .h file and its corresponding .cpp, and will later hold the class settings for XPP.
project.xpp :
<xpp>
<class name=”MyClass”
file=”myclass.xhd”/>
</xpp>
myclass.xhd :
<xhd>
<class
name="MyClass"
implementation="MyClass"
class_header="myclass.h"
class_module="myclass.cpp"
xhd_factories="InterfaceDiscovery">
</class>
</xhd>
Note that both “name” and “implementation” contain the name of the class, this is because our class is not split between an interface and an implementation version. If our instrumented class was an implementation sitting behind an abstract interface, we would fill “name” with the name of the interface, and “implementation” would keep the name of the class that is used in the .h we are instrumenting. This is used to make XPP generate abstract interfaces, so you may ignore this subtlety for now, as this tutorial does.
“class_header” points to the .h containing the class declaration
“class_module” points to the .cpp containing the implementation of our class' methods
“xhd_factories” lists the templates that XPP should use when instrumenting our class (use “;” to separate multiple factories)
Next, either create a batch file (or for unix, a shell script) that will run XPP, or insert project.xpp in your IDE project and setup a custom build step for it (or for unix, create a makefile step).
batch file :
@\path_to_xpp\xpp -i:project.xpp -t:\path_to_templates -y >
xpplog
@type xpplog
visualc++ custom build step :
\path_to_xpp\xpp -i:$(InputDir)\$(InputName).xpp -t:\path_to_templates -y
At this point, running XPP will give the following result :
xPP v0.3.9c
Processing project file project.xpp
Preprocessing
myclass.xhd
Template "InterfaceDiscovery"
not found, ignoring
Preprocessing myclass.h (no
change)
Preprocessing myclass.cpp (no change)
We are now ready to work on our InterfaceDiscovery template.
2. Creating a new XPP template
Create a file named InterfaceDiscovery.xpst in the directory specified by the -t parameter in the XPP command line, and insert the following (minimal) template :
InterfaceDiscovery.xpst :
<XPP:StandaloneTemplate classname="*Discovery"
inherit="parallel" desc="Interface Discovery"
>
<module id="h"
filename="*_ID.h">
</module>
</XPP:StandaloneTemplate>
Each XPP template is associated with a classname that you may use as an ancestor to derive your own class from. The name of this class is specified by the “classname” parameter on the XPP:StandaloneTemplate tag. The star is simply replaced by the name of the class this template is instrumenting, and so in our case, “*Discovery” will associate this template with a class ancestor named “MyClassDiscovery”.
inherit=”parallel” tells XPP that our user class will manually inherit from all its template-generated ancestors, rather than rely on the ancestors themselves inheriting from each other (inherit=”serial”), this is the recommended setting if you do not have a particular reason to do otherwise.
desc=”Interface Discovery” is a human readable description of what this template is.
XPP templates hold one or more modules. Modules are basically files which XPP will create each time it is ran for a particular class. Each module requires a unique id and a filename. Similar to the classname above, the filename is constructed by replacing the star with the name of the instrumented class this template is running on, and so in this case, the template will create a file named MyClass_ID.h.
Running XPP now gives the following :
xPP v0.3.9c
Processing project file project.xpp
Preprocessing
myclass.xhd
myclass.xhd -> MyClass_ID.h
(InterfaceDiscovery)
Preprocessing myclass.h (no
change)
Preprocessing myclass.cpp (no change)
3. Generating templatized code
MyClass_ID.h is currently empty, let's now generate some actual source code. Add the following inside the module :
#ifndef _<class data="classlayer"
ucase="1"/>_H<br/>
#define _<class
data="classlayer" ucase="1"/>_H<br/>
<br/>
#endif
// _<class data="classlayer" ucase="1"/>_H<br/>
<class asks XPP to access data from the currently instrumented class. What data is accessed depends on the data parameter. Here data=”classlayer” requests the name of the class associated with this template (the one we specified by typing “*Discovery” earlier). The ucase=”1” parameter is a common modifier (it can be used on any tag) which capitalizes the text returned by the class tag.
<br/> inserts a line break.
Therefore, running XPP will now generate the following MyClass_ID.h :
#ifndef _MYCLASSDISCOVERY_H
#define _MYCLASSDISCOVERY_H
#endif
// _MYCLASSDISCOVERY_H
Now it is time to create actual code ! Let's generate our ancestor (new code is bold) :
<module id="h" filename="*_ID.h">
#ifndef
_<class data="classlayer" ucase="1"/>_H<br/>
#define
_<class data="classlayer"
ucase="1"/>_H<br/>
<br/>
class
<class
data="classlayer"/>
<ob/><br/>
protected:<br/>
<indent>
<class
data="classlayer"/>() {}<br/>
virtual
~<class data="classlayer"/>()
{}<br/>
</indent>
<cb/>;<br/>
<br/>
#endif
// _<class data="classlayer"
ucase="1"/>_H<br/>
</module>
<ob/> opens a brace (inserts a line break or not,
depending on xpp config)
<indent>a piece of
code</indent> indents a piece of code.
Running XPP now generates :
#ifndef _MYCLASSDISCOVERY_H
#define _MYCLASSDISCOVERY_H
class
MyClassDiscovery {
protected:
MyClassDiscovery()
{}
virtual ~MyClassDiscovery() {}
};
#endif
// _MYCLASSDISCOVERY_H
We can now go to our user class and inherit from the generated ancestor :
myclass.h
:
#ifndef _MYCLASS_H
#define _MYCLASS_H
#include "MyClass_ID.h"
class MyClass : public
MyClassDiscovery {
public:
MyClass();
virtual
~MyClass();
void Method1();
int
Method2();
void Method3(int i);
float
Method4(const char *s, int i);
int data1;
const
char *data2;
};
#endif // _MYCLASS_H
Now lets go back to our template, and let's add the prototypes for our interface discovery methods :
<module id="h" filename="*_ID.h">
#ifndef
_<class data="classlayer" ucase="1"/>_H<br/>
#define
_<class data="classlayer"
ucase="1"/>_H<br/>
<br/>
class
<class
data="classlayer"/>
<ob/><br/>
protected:<br/>
<indent>
<class
data="classlayer"/>() {}<br/>
virtual
~<class data="classlayer"/>()
{}<br/>
</indent>
public:<br/>
<indent>
int
getNumMethods();<br/>
const char
*enumMethodName(int method);<br/>
const char
*enumMethodReturnType(int method);<br/>
void
*enumMethodPtr(int method);<br/>
int
getNumParams(int method);<br/>
const char
*getParamName(int method, int param);<br/>
const
char *getParamType(int method, int param);<br/>
int
getNumMembers();<br/>
const char
*getMemberName(int member);<br/>
const char
*getMemberType(int member);<br/>
</indent>
<cb/>;<br/>
<br/>
#endif
// _<class data="classlayer"
ucase="1"/>_H<br/>
</module>
This will now generate :
#ifndef _MYCLASSDISCOVERY_H
#define _MYCLASSDISCOVERY_H
class
MyClassDiscovery {
protected:
MyClassDiscovery()
{}
virtual ~MyClassDiscovery() {}
public:
int
getNumMethods();
const char *enumMethodName(int
method);
const char *enumMethodReturnType(int
method);
void *enumMethodPtr(int method);
int
getNumParams(int method);
const char
*enumParamName(int method, int param);
const char
*enumParamType(int method, int param);
int
getNumData();
const char *enumDataName(int
data);
const char *enumDataType(int data);
};
#endif
// _MYCLASSDISCOVERY_H
We're done with the header file, we now need to create a cpp file that implements these functions. Since this is a new file, we need to make a new module in our template :
<module id="cpp" filename="*_ID.cpp">
#include
%3Cstdio.h%3E<br/>
#include "<class
data="name"/>_ID.h"<br/>
</module>
Note %3C and %3E, which are used in place of '<' and '>' in order not to confuse the XPP xml syntax.
XPP's output should now look like this :
xPP v0.3.9c
Processing project file project.xpp
Preprocessing
myclass.xhd
myclass.xhd -> MyClass_ID.h
(InterfaceDiscovery)
myclass.xhd ->
MyClass_ID.cpp (InterfaceDiscovery)
Preprocessing
myclass.h (no change)
Preprocessing myclass.cpp (no
change)
4. Walking through methods, parameters and data members
We now want to implement the interface discovery mechanism within the cpp file. To do this we will access some more class data, and we'll walk through the list of methods :
<module id="cpp" filename="*_ID.cpp">
#include
%3Cstdio.h%3E<br/>
#include "<class
data="classlayer"/>.h"<br/>
<br/>
int
<class
data="classlayer"/>::getNumMethods()
<ob/><br/>
<indent>
return
<class data="nummethods"/>;<br/>
</indent>
<cb/><br/>
const
char *<class data="classlayer"/>::enumMethodName(int
method)
<ob/><br/>
<indent>
switch
(method)
<ob/><br/>
<indent>
<foreach
objects="methods">
case
<forindex/>: return "<method
data="name"/>";<br/>
</foreach>
</indent>
<cb/><br/>
return
NULL;<br/>
</indent>
<cb/><br/>
<br/>
</module>
<foreach lets you walk through a list, by running the
content of the foreach tag pair once for every matching
object. In this case, we are walking the list of methods.
<forindex/> outputs the current foreach iteration
(starts at zero)
<method lets you access a particular
method data, for instance, its name. This tag must be inside a method
context (for instance, inside a <foreach
objects=”methods”></foreach>).
Running XPP now yields a MyClass_ID.cpp file such as this :
#include <stdio.h>
#include "MyClass_ID.h"
int
MyClassDiscovery::getNumMethods() {
return 6;
}
const
char *MyClassDiscovery::enumMethodName(int method) {
switch
(method) {
case 0: return
"MyClass";
case 1: return
"~MyClass";
case 2: return
"Method1";
case 3: return
"Method2";
case 4: return
"Method3";
case 5: return
"Method4";
}
return NULL;
}
Which already gives us a minimal interface discovery implementation. Let us now complete it :
<XPP:StandaloneTemplate classname="*Discovery"
inherit="parallel" desc="Interface Discovery"
>
<!-- ######################### H FILE
######################### -->
<module id="h"
filename="*_ID.h">
#ifndef _<class
data="classlayer" ucase="1"/>_H<br/>
#define
_<class data="classlayer"
ucase="1"/>_H<br/>
<br/>
class
<class
data="classlayer"/>
<ob/><br/>
protected:<br/>
<indent>
<class
data="classlayer"/>() {}<br/>
virtual
~<class data="classlayer"/>()
{}<br/>
</indent>
public:<br/>
<indent>
int
getNumMethods();<br/>
const char
*enumMethodName(int method);<br/>
const char
*enumMethodReturnType(int method);<br/>
int
getNumParams(int method);<br/>
const char
*enumParamName(int method, int param);<br/>
const
char *enumParamType(int method, int param);<br/>
int
getNumData();<br/>
const char *enumDataName(int
data);<br/>
const char *enumDataType(int
data);<br/>
</indent>
<cb/>;<br/>
<br/>
#endif
// _<class data="classlayer"
ucase="1"/>_H<br/>
</module>
<!--
######################### CPP FILE #########################
-->
<module id="cpp"
filename="*_ID.cpp">
#include
%3Cstdio.h%3E
<br/>
#include "<class
data="name"/>_ID.h"<br/>
#include "<class
data="implementationheader"/>"<br/>
<br/>
<!--
Methods -->
int <class
data="classlayer"/>::getNumMethods()
<ob/><br/>
<indent>
return
<class data="nummethods"/>;<br/>
</indent>
<cb/><br/>
<br/>
const
char *<class data="classlayer"/>::enumMethodName(int
method)
<ob/><br/>
<indent>
switch
(method)
<ob/><br/>
<indent>
<foreach
objects="methods">
case
<forindex/>: return "<method
data="name"/>";<br/>
</foreach>
</indent>
<cb/><br/>
return
NULL;<br/>
</indent>
<cb/><br/>
<br/>
const
char *<class data="classlayer"/>::enumMethodReturnType(int
method)
<ob/><br/>
<indent>
switch
(method)
<ob/><br/>
<indent>
<foreach
objects="methods">
case
<forindex/>: return "<method data="returntype"
trim="1"/>";<br/>
</foreach>
</indent>
<cb/><br/>
return
NULL;<br/>
</indent>
<cb/><br/>
<br/>
<!--
Method params -->
int <class
data="classlayer"/>::getNumParams(int
method)
<ob/><br/>
<indent>
switch
(method)
<ob/><br/>
<indent>
<foreach
objects="methods">
case
<forindex/>: return <method
data="numparams"/>;<br/>
</foreach>
</indent>
<cb/><br/>
return
-1;<br/>
</indent>
<cb/><br/>
<br/>
const
char *<class data="classlayer"/>::enumParamName(int
method, int param)
<ob/><br/>
<indent>
switch
(method)
<ob/><br/>
<indent>
<foreach
objects="methods">
case
<forindex/>: <br/>
<indent>
<if
object="method" isoftype="voidparam"
not="1">
switch
(param)
<ob/><br/>
<foreach
objects="params">
<indent>
case
<forindex/>: return "<param
data="name"/>";<br/>
</indent>
</foreach>
<cb/><br/>
</if>
break;<br/>
</indent>
</foreach>
</indent>
<cb/><br/>
return
NULL;<br/>
</indent>
<cb/><br/>
<br/>
const
char *<class data="classlayer"/>::enumParamType(int
method, int param)
<ob/><br/>
<indent>
switch
(method)
<ob/><br/>
<indent>
<foreach
objects="methods">
case
<forindex/>: <br/>
<indent>
<if
object="method" isoftype="voidparam"
not="1">
switch
(param)
<ob/><br/>
<foreach
objects="params">
<indent>
case
<forindex/>: return "<param data="type"
trim="1"/>";<br/>
</indent>
</foreach>
<cb/><br/>
</if>
break;<br/>
</indent>
</foreach>
</indent>
<cb/><br/>
return
NULL;<br/>
</indent>
<cb/><br/>
<br/>
<!--
Member data -->
int <class
data="classlayer"/>::getNumData()
<ob/><br/>
<indent>
return
<class data="numdata"/>;<br/>
</indent>
<cb/><br/>
<br/>
const
char *<class data="classlayer"/>::enumDataName(int
data)
<ob/><br/>
<indent>
switch
(data)
<ob/><br/>
<indent>
<foreach
objects="data">
case
<forindex/>: return "<data
data="name"/>";<br/>
</foreach>
</indent>
<cb/><br/>
return
NULL;<br/>
</indent>
<cb/><br/>
<br/>
const
char *<class data="classlayer"/>::enumDataType(int
data)
<ob/><br/>
<indent>
switch
(data)
<ob/><br/>
<indent>
<foreach
objects="data">
case
<forindex/>: return "<data data="type"
trim="1"/>";<br/>
</foreach>
</indent>
<cb/><br/>
return
NULL;<br/>
</indent>
<cb/><br/>
<br/>
</module>
</XPP:StandaloneTemplate>
The template now generates :
#include <stdio.h>
#include "MyClass_ID.h"
#include
"myclass.h"
int MyClassDiscovery::getNumMethods()
{
return 6;
}
const char
*MyClassDiscovery::enumMethodName(int method) {
switch
(method) {
case 0: return
"MyClass";
case 1: return
"~MyClass";
case 2: return
"Method1";
case 3: return
"Method2";
case 4: return
"Method3";
case 5: return
"Method4";
}
return
NULL;
}
const char
*MyClassDiscovery::enumMethodReturnType(int method) {
switch
(method) {
case 0: return "";
case
1: return "";
case 2: return
"void";
case 3: return
"int";
case 4: return
"void";
case 5: return
"float";
}
return
NULL;
}
int MyClassDiscovery::getNumParams(int method)
{
switch (method) {
case 0:
return 0;
case 1: return 0;
case
2: return 0;
case 3: return 0;
case
4: return 1;
case 5: return
2;
}
return -1;
}
const char
*MyClassDiscovery::enumParamName(int method, int param) {
switch
(method) {
case 0:
break;
case
1:
break;
case
2:
break;
case
3:
break;
case
4:
switch (param)
{
case 0: return
"i";
}
break;
case
5:
switch (param)
{
case 0: return
"s";
case
1: return "i";
}
break;
}
return
NULL;
}
const char *MyClassDiscovery::enumParamType(int
method, int param) {
switch (method) {
case
0:
break;
case
1:
break;
case
2:
break;
case
3:
break;
case
4:
switch (param)
{
case 0: return
"int";
}
break;
case
5:
switch (param)
{
case 0: return
"const char *";
case
1: return "int";
}
break;
}
return
NULL;
}
int MyClassDiscovery::getNumData() {
return
2;
}
const char *MyClassDiscovery::enumDataName(int data)
{
switch (data) {
case 0:
return "data1";
case 1: return
"data2";
}
return
NULL;
}
const char *MyClassDiscovery::enumDataType(int
data) {
switch (data) {
case
0: return "int";
case 1: return
"const char *";
}
return
NULL;
}
Which concludes our tutorial.
You may want to download the tutorial's files.