C++ Code Generation using T4 Templates

Introduction

Microsoft Visual Studio comes with a code generation tool which they call T4 Templates. It is somewhat integrated into Visual Studio but I have found the integration to be not that streamlined yet, and if your a C++ user its not there at all.

So in order to show you how to use T4 Templates in your projects I am just going to explain it usage solely via the command line using the TextTransform.exe program.

Find the Tool

The program they use to generate the code is called TextTransform.exe. It should be able to be found in any visual studio installation. If you have Visual Studio 10 installed then it will be in the following location on your computer…


C:\Program Files (x86)\Common Files\microsoft shared\TextTemplating\10.0\TextTransform.exe


Find this file on your computer. Now create a new folder somewhere on your system. Make it an easy location you can get to via a commandline like “c:\templatetest”. Copy the TextTransform.exe to this new folder. You are now ready to start experimenting.

Generating your first code

Open up a command prompt (go to the windows menu and just type cmd.exe).


Next navigate to your new folder (cd c:\templatetest).


Now navigate to the folder on your desktop and create a new file in that folder called “test.tt”.


If you used the right click mouse button menu to create a text file then you might find it actually created a file called test.tt.txt on your computer. If it does then on the folder click on “Organize – Folder And Search Options”. In the window that pops up select the “View” tab find the option “Hide Extensions for known file types”. Uncheck this checkbox and press OK. Go back to your folder and you will see the “text.tt.txt” file. Right click and rename this file to test.tt”.


Open the test.tt file in any text editor of your choosing (You can use Visual Studio if you wish and there is even a syntax highlighting plugin that you can install to make it all pretty, but I will leave this for another post since its not vital to actually using the code generator).

In the file paste the following code...


<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".h" #>
<# var members = new string [] {"variableA", "variableB", "variableC"}; #>
class MyClass
{
public:
MyClass();
private:
<#
 foreach (string memberName in members)
 { #>
int m_<#= memberName #>;
<# } #>
}


Save the file and go to your command window and type in the following: TextTransform.exe test.tt


This code will generate a file called “test.h” that looks like the following.


class MyClass
{
public:
MyClass();
private:
int m_variableA;
int m_variableB;
int m_variableC;
}


Great! You have just generated your first file!

So what just happened?

The TextTransform.exe program just took your input file and parsed through it and generated the output file.


<#@ template debug="false" hostspecific="false" language="C#" #>


This line tells the generator that the language used in the template is C#. You can also use VB if you know visual basic instead.


<#@ output extension=".h" #>


This line tells it that your out file is going to have the extension “.h”. So here you can swap it for anything you want (.xml, .cpp, .customformat).


The rest of the code uses C# code that is between special flags to let the generator know there is some C# code that will be used to generate some code.


In this example I just wrote out a simple class with 3 member variables.

How to use a file to list the member variables

If you are going to be generating a bunch of files, say for marshaling and un-marshaling of data for a custom networked protocol, then you want to have the model that describes this in some file, that is then read in and parsed, to generate all the required output files (by using multiple template.tt files that generate a specific output file based on the same data).


A simple approach would be to use a plain text file, so lets do that.


Create another file in your directory called “vars.txt”.


Open it up and paste the following into it


variableA
variableB
variableC


Now modify your test.tt file to be the following


<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.IO" #>
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".h" #>
<# var members = File.ReadLines("vars.txt");#>
class MyClass
{
public:
MyClass();
private:
<#
 foreach (string memberName in members)
 { #>
int m_<#= memberName #>;
<# } #>
}


Now go to the command prompt and run the TextTemplate again TextTransform.exe test.tt


You will now see the following, which is the same as before except this time all the member variables were read in from a file.


class MyClass
{
public:
MyClass();
private:
int m_variableA;
int m_variableB;
int m_variableC;
}


So now if we wanted to we could write another template file that would output a .cpp file that initializes all these variables. Lets do that.


Create a new file called testcpp.tt and paste in the following code


<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.IO" #>
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cpp" #>
<# var memberVars = File.ReadLines("vars.txt");#>
#include "test.h"
MyClass::MyClass()
{
<# foreach (string member in memberVars) { #>
m_<#=member #> = 0;
<# } #>
}


Run this on in the command prompt using TextTransform.exe testcpp.tt


And it will generate a file called testcpp.cpp (OK the naming is not that great yet and we need to make the name of the file part of the template, I will leave this for now).


The file will look like the following…


#include "test.h"
MyClass::MyClass()
{
m_variableA = 0;
m_variableB = 0;
m_variableC = 0;
}


So now we have generated a .h and a .cpp file based off a crude text based model description.

Using an XML file as the model

I much prefer to use XML as my model file format and then generate specific parts of the file based off of specific attributes in the model.


Create a new file called class1.xml and paste in the following text


<Class>
   <MemberVariable Name="variableA"/>
   <MemberVariable Name="variableB"/>
   <MemberVariable Name="variableC"/>
 </Class>


Now open up the test.tt file and replace it with the following


<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".h" #>
class MyClass
{
public:
MyClass();
private:
<#  
XmlDocument doc = new XmlDocument();
   string absolutePath = Path.GetFullPath("class1.xml");                
   doc.Load(absolutePath);
   foreach (XmlNode node in doc.SelectNodes("/Class/MemberVariable"))
   {
#>
int m_<#=node.Attributes["Name"].InnerText #>;
<#    } #>

};

Run the TextTemplate on the test.tt file (you should know how to do this by now) and you will get the following (which should also look familiar).


class MyClass
{
public:
MyClass();
private:
int m_variableA;
int m_variableB;
int m_variableC;

};

How to use a function in a template

You could also arrange the code into a function to clean it up and make it easier to read.


Paste the following into test.tt


<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".h" #>
class MyClass
{
public:
MyClass();
private:
<# foreach (string member in this.GetMemberVariables()) { #>
int m_<#=member #>;
<# } #>
};


<#+
   public List<string> GetMemberVariables()
   {
       List<string> result = new List<string>();
       XmlDocument doc = new XmlDocument();
       string absolutePath = Path.GetFullPath("class1.xml");                
       doc.Load(absolutePath);
       foreach (XmlNode node in doc.SelectNodes("/Class/MemberVariable"))
       {
           result.Add(node.Attributes["Name"].InnerText);
       }
       return result;
   }
#>


When you run this it will produce the same result as before.

Clean up the function and make it more usable

Lets parse this data a bit better so that we can generate a file from multiple attributes described in the XML file. It would also be nice if we could generate multiple classes as well so lets do that!


Paste this code into test.tt.


<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".h" #>
<# foreach (ClassDefinition classDef in this.GetClasses()) { #>
class <#=classDef.name #>
{
public:
<#=classDef.name #>();
private:
<# foreach (MemberDefinition member in classDef.privateMembers) { #>
<#=member.type #> m_<#=member.name #>; <# if(member.comment.Length > 0) { #> //<#=member.comment #> <# } #>

<# } #>
};
<# } #>

<#+
public class ClassDefinition
{
public string name;
       public List<MemberDefinition> privateMembers;

public ClassDefinition()
{
privateMembers = new List<MemberDefinition>();
}
}

public class MemberDefinition
{
public string name;
public string comment;
public string defaultValue;
public string type;
}

   public List<ClassDefinition> GetClasses()
   {
       List<ClassDefinition> classes = new List<ClassDefinition>();
       XmlDocument doc = new XmlDocument();
       string absolutePath = Path.GetFullPath("class2.xml");                
       doc.Load(absolutePath);
       foreach (XmlNode node in doc.SelectNodes("/root/Class"))
       {
ClassDefinition newClass = new ClassDefinition();
newClass.name = node.Attributes["Name"].InnerText;

       foreach (XmlNode memberNode in node.SelectNodes("MemberVariable"))
   {
MemberDefinition newMember = new MemberDefinition();
newMember.name = memberNode.Attributes["Name"].InnerText;
newMember.defaultValue = memberNode.Attributes["Default"].InnerText;
newMember.comment = memberNode.Attributes["Comment"].InnerText;
newMember.type = memberNode.Attributes["Type"].InnerText;
newClass.privateMembers.Add(newMember);
}
           classes.Add(newClass);
       }
       return classes;
   }
#>


Now create a new class2.xml file and paste in the following data


<root>
 <Class Name="MyClassA">
   <MemberVariable Name="variableA" Default="1" Type="int" Comment="This is some documentation describing the variable"/>
   <MemberVariable Name="variableB" Default="2" Type="double" Comment="This is some documentation describing the variable"/>
   <MemberVariable Name="variableC" Default="3" Type="float" Comment="This is some documentation describing the variable"/>
 </Class>
 <Class Name="MyClassB">
   <MemberVariable Name="variableD" Default="4" Type="int" Comment="This is some documentation describing the variable"/>
   <MemberVariable Name="variableE" Default="5" Type="double" Comment="This is some documentation describing the variable"/>
   <MemberVariable Name="variableF" Default="6" Type="float" Comment="This is some documentation describing the variable"/>
 </Class>
</root>


Run the TextTemplate executable and you will now get the following file


class MyClassA
{
public:
MyClassA();
private:
int m_variableA;  //This is some documentation describing the variable
double m_variableB;  //This is some documentation describing the variable
float m_variableC;  //This is some documentation describing the variable
};
class MyClassB
{
public:
MyClassB();
private:
int m_variableD;  //This is some documentation describing the variable
double m_variableE;  //This is some documentation describing the variable
float m_variableF;  //This is some documentation describing the variable
};

Conclusion

As you can see TextTransform.exe (or if you prefer the T4 Template Code Generator) is a very powerful tool. I will leave it up to you to create the cpp output file for this template.


I hope you enjoyed this little tutorial. I may expand on this in future with a more fully featured example to generate some actual usable code in a real life situation, but for now this should at least have opened your eyes to the world of code generation. Enjoy!

Comments

Popular posts from this blog

Creating a renderer using a video post plugin

Selecting a Game Engine

A C++ library for communicating with Amazon S3