Welcome to EMC Consulting Blogs Sign in | Join | Help

Paulo Reichert's Blog

Code Generation in Visual Studio 2005

One of the many nice new features in .NET 2.0 and Visual Studio 2005 are the strongly typed resources. Well, the idea is very simple: You create an XML .resX file on your project and the IDE creates a .cs class file, mapping strongly typed properties to the resources you create.

So, instead of using the ResourceManager directly, you can only use the generated class and it will deal with the resource stuff for you on the background. Very nice and clean solution.

However, the resource stuff is not what I want to talk about today. What I want to talk about is the inner workings that make it happen. I ever wanted to create my own custom files that got converted to program usable code easily inside the IDE, so I started investigating on how to do it.

Visual Studio supports the concept of code generation tools. You can associate these tools with the files, by setting their “Custom Tool” property as in the screenshot below:

Well, these custom tools you can set there are actually classes that implement two interfaces:
• Microsoft.VisualStudio.Shell.Interop.IVsSingleFileGenerator
• Microsoft.VisualStudio.OLE.Interop.IObjectWithSite

The first interface, IVsSingleFileGenerator defines two methods:
• int DefaultExtension(out string)
• int Generate(string, string, string, System.IntPtr[], out uint, Microsoft.VisualStudio.Shell.Interop.IVsGeneratorProgress)

The first method, DefaultExtension, is called by the IDE to retrieve the default extension of the files generated by the tool. The second method is the one that does the job of getting the contents of the file you create on the IDE and transforming them in program code.

The second interface defines other two methods:
• void GetSite(ref System.Guid, out System.IntPtr)
• void SetSite(object)

This interface is actually a Interop interface to the Internet Explorer API and it gives you access to the container site.

To show you how it all works, I have created a very simple code generator tool. The idea is simple: I have some XML documents describing entity classes (only fields and properties). I want to have a tool that automatically translates these XML documents in class definitions:

What I have is the following:

<xml version="1.0"encoding="utf-8" ?>

<class name="GeneratedClass" nameSpace="MyNamespace.Generated" access="public">

      <fields>

            <field name="stringField" type="System.String" access="private" />

            <field name="intField" type="System.Int32" access="private" />

      </fields>

      <properties>

            <property name="StringProperty" type="System.String" access="public" hasGet="true" hasSet="true">

                  <reference fieldName="stringField" />

            </property>

            <property name="IntProperty" type="System.Int32" access="public" hasGet="true" hasSet="true">

                  <reference fieldName="intField" />

            </property>

      </properties>

<class>

What I want to get is:

namespace MyNamespace.Generated {

    public class GeneratedClass {

       

        private string stringField;

       

        private int intField;

        

        public virtual string StringProperty {

            get {

                return stringField;

            }

            set {

                stringField = value;

            }

        }

       

        public virtual int IntProperty {

            get {

                return intField;

            }

            set {

                intField = value;

            }

        }

    }

}

To achieve this, the first thing I have created is a helper class with methods to actually write the code. The class is named ClassGenerator and implements only one public static method:

public static CodeCompileUnit Create(XmlDocument doc, CodeDomProvider codeProvider)

The method gets an XmlDocument object with the class definition and a CodeDomProvider object used to generate the code. It returns a CodeCompileUnit object with the generated class code.

The method implementation can be seen below:

public static CodeCompileUnit Create(XmlDocument doc, CodeDomProvider codeProvider)

{

    XmlElement rootElement = doc.DocumentElement;

    string className, nameSpaceName, accessModifier;

 

    if (rootElement.Name != "class")

    {

        throw new ArgumentException(string.Format(GlobalResources.InvalidRootElement, rootElement.Name));

    }

 

    if (rootElement.Attributes.GetNamedItem("name") != null)

    {

        className = rootElement.Attributes.GetNamedItem("name").Value;

    }

    else

    {

        throw new InvalidOperationException(GlobalResources.ClassNameMissing);

    }

 

    if (rootElement.Attributes.GetNamedItem("nameSpace") != null)

    {

        nameSpaceName = rootElement.Attributes.GetNamedItem("nameSpace").Value;

    }

    else

    {

        throw new InvalidOperationException(GlobalResources.NameSpaceMissing);

    }

 

    if (rootElement.Attributes.GetNamedItem("access") != null)

    {

        accessModifier = rootElement.Attributes.GetNamedItem("access").Value;

    }

    else

    {

        accessModifier = "public";

    }

 

    CodeCompileUnit code = new CodeCompileUnit();

    code.UserData.Add("AllowLateBound", false);

    code.UserData.Add("RequireVariableDeclaration", true);

 

    CodeNamespace nameSpace = new CodeNamespace(nameSpaceName);

    code.Namespaces.Add(nameSpace);

 

    CodeTypeDeclaration classObject = new CodeTypeDeclaration(className);

    nameSpace.Types.Add(classObject);

               

    if (accessModifier == "private")

    {

        classObject.TypeAttributes = TypeAttributes.NotPublic;

    }

    else

    {

        classObject.TypeAttributes = TypeAttributes.Public;

    }

 

    classObject.Comments.Add(new CodeCommentStatement(GlobalResources.DocCommentSummaryStart, true));

    classObject.Comments.Add(new CodeCommentStatement("", true));

    classObject.Comments.Add(new CodeCommentStatement(GlobalResources.DocCommentSummaryEnd, true));

 

    XmlNodeList xmlFields = rootElement.GetElementsByTagName("field");

    ClassGenerator.EmitFields(classObject, xmlFields);

 

 

    XmlNodeList xmlProperties = rootElement.GetElementsByTagName("property");

    ClassGenerator.EmitProperties(classObject, xmlProperties);

 

    CodeGenerator.ValidateIdentifiers(code);

    return code;

}

The most important pieces of the method above are:

CodeCompileUnit code = new CodeCompileUnit();

code.UserData.Add("AllowLateBound", false);

code.UserData.Add("RequireVariableDeclaration", true);

 

CodeNamespace nameSpace = new CodeNamespace(nameSpaceName);

code.Namespaces.Add(nameSpace);

 

CodeTypeDeclaration classObject = new CodeTypeDeclaration(className);

nameSpace.Types.Add(classObject);

That code creates a new CodeCompileUnit object that represents the code that can be compiled. You can see it as a code file. It then sets two properties, preventing late binding and requiring variable declarations. In C# code these two are required, but in VB .NET they aren’t.

Then it creates a CodeNamespace object, setting it to the namespace provided in the XML document. After it creates a CodeTypeDeclaration object that can be used to declare types such as classes, as I did in this case. Then I add the namespace to the compile unit and the class to the namespace.

I have then two other methods, EmitFields and EmitProperties. The first one emits the private fields and the second one the public properties. You can see the most important pieces of both methods below:

Type fieldType = Type.GetType(fieldTypeName);

if (fieldType == null)

{

    throw new InvalidOperationException(string.Format(GlobalResources.InvalidFieldType, fieldTypeName));

}

 

CodeTypeReference fieldTypeReference = new CodeTypeReference(fieldType, CodeTypeReferenceOptions.GlobalReference);

CodeMemberField field = new CodeMemberField(fieldTypeReference, fieldName);

 

if (fieldAccessModifier == "public")

{

    field.Attributes = MemberAttributes.Public;

}

else

{

    field.Attributes = MemberAttributes.Private;

}

 

classObject.Members.Add(field);

The above code from EmitFields loads a reference to the type set for the field being created in the XML document, then it creates an instance of CodeTypeReference, which is a reference from the generated code to the type passed in as parameter.

It creates a CodeMemberField, which is the field being declared, sets its access modifier and adds it to the class.

Type propertyType = Type.GetType(propertyTypeName);

if (propertyType == null)

{

    throw new InvalidOperationException(string.Format(GlobalResources.InvalidPropertyType, propertyTypeName));

}

 

CodeTypeReference propertyTypeReference = new CodeTypeReference(propertyType, CodeTypeReferenceOptions.GlobalReference);

 

CodeMemberProperty property = new CodeMemberProperty();

property.Name = propertyName;

property.HasGet = propertyHasGet;

property.HasSet = propertyHasSet;

property.Type = propertyTypeReference;

 

if (propertyAccessModifier == "private")

{

    property.Attributes = MemberAttributes.Private;

}

else

{

    property.Attributes = MemberAttributes.Public;

}

 

if (propertyHasGet)

{

    CodeFieldReferenceExpression fieldReferenceExpression = new CodeFieldReferenceExpression(null, propertyFieldReferenceName);

    property.GetStatements.Add(new CodeMethodReturnStatement(fieldReferenceExpression));

}

 

if (propertyHasSet)

{

    CodeArgumentReferenceExpression valueFieldReference = new CodeArgumentReferenceExpression("value");

    property.SetStatements.Add(new CodeAssignStatement(fieldReferenceExpression, valueFieldReference));

}

classObject.Members.Add(property);

  

The above code from EmitProperties creates a new CodeMemberProperty object that represents the property being created. It then sets all the values for the property with the values loaded from the XML document. It then adds (if required) getter and setter for the property. The getter is very simple, adding an instance of CodeMethodReturnStatement that literally emits a “return” passing the name of the field you provide.


The setter is slightly more complex, as you have to create a CodeArgumentReferenceExpression to the literal “value” and then add a CodeAssignmentExpression passing first the left hand side of the assignment and second the right hand side of the expression.


After you have created the class, then we have to implement the actual Visual Studio tool that will use this class to generate code.

I have created a class named XmlClassGenerator (sorry by the lack of originality but I’m terrible with names) and added the implementations for the two interfaces mentioned above. I have also added the following using statements:


using System.CodeDom;

using System.IO;

using System.Xml;

using System.Runtime.InteropServices;

using Microsoft.VisualStudio.Shell.Interop;

using System.ComponentModel;

using System.CodeDom.Compiler;

using Microsoft.VisualStudio.Shell;

using VSOLE = Microsoft.VisualStudio.OLE.Interop;

 

You can see that the last using has an alias to it, as I use the IServiceProvider interface. There are two of these, one in System and another one in the namespace aliased above.

That means that every type on that namespace I reference I have to use the alias, so I’m using the new C# 2.0 syntax for it:

 

public class XmlClassGenerator : IVsSingleFileGenerator, VSOLE::IObjectWithSite


It is also needed to add a Guid attribute to this class, as this class will be referenced by unmanaged code:

 

[Guid("DDD23356-DC41-4eed-9D3B-84973D5EC57B")]

public class XmlClassGenerator : IVsSingleFileGenerator, VSOLE::IObjectWithSite

 

I have added other five member variables to it:

 

private CodeDomProvider codeProvider;

 

private string codeFileNameSpace;

private string codeFilePath;

 

private object site;

 

private IVsGeneratorProgress codeGeneratorProgress;

 

The CodeDomProvider variable keeps a reference to the CodeDomProvider being used, the object variable keeps a reference to our site container and the IVsGeneratorProgress informs the IDE of the progress on the code generation.

I have added a property as well, mostly to manage the creation of the CodeDomProvider:


public CodeDomProvider CodeProvider

{

    get

    {

        if (this.codeProvider == null)

        {

            codeProvider = CodeDomProvider.CreateProvider("C#");

        }

 

        return this.codeProvider;

    }

 

    set

    {

        if (value == null)

        {

            throw new ArgumentNullException();

        }

 

        this.codeProvider = value;

    }

}

 

Now we have all the plumbings in place, let’s implement the methods we need to get it working:

The first one is:


public int DefaultExtension(out string ext)

{

    string defExt;

    ext = string.Empty;

 

    defExt = this.CodeProvider.FileExtension;

 

    if (((defExt != null) && (defExt.Length > 0)) && (defExt[0] != '.'))

    {

        defExt = "." + defExt;

    }

 

    if (!string.IsNullOrEmpty(defExt))

    {

        ext = ".Designer" + defExt;

    }

 

    return 0;

}

 

This method is used by the IDE to get the file extension it should assign to the files you create. You can see that I add a “.Designer” extension, so you get a FileName.Designer.cs file generated by the IDE. Nice.


The next method is the actual place where things happen (or are launched as it relies on internal methods as well :-)):


public int Generate(string wszInputFilePath, string bstrInputFileContents, string wszDefaultNamespace, IntPtr[] pbstrOutputFileContents, out uint pbstrOutputFileContentSize, IVsGeneratorProgress pGenerateProgress)

{

    if (bstrInputFileContents == null)

    {

        throw new ArgumentNullException(bstrInputFileContents);

    }

 

    this.codeFilePath = wszInputFilePath;

    this.codeFileNameSpace = wszDefaultNamespace;

    this.codeGeneratorProgress = pGenerateProgress;

 

    byte[] generatedStuff = this.GenerateCode(wszInputFilePath, bstrInputFileContents);

 

    if (generatedStuff == null)

    {

        pbstrOutputFileContents[0] = IntPtr.Zero;

        pbstrOutputFileContentSize = 0;

    }

    else

    {

        pbstrOutputFileContents[0] = Marshal.AllocCoTaskMem(generatedStuff.Length);

        Marshal.Copy(generatedStuff, 0, pbstrOutputFileContents[0], generatedStuff.Length);

        pbstrOutputFileContentSize = (uint)generatedStuff.Length;

    }

    return 0;

}

 

After asserting that the file contents provided by the IDE aren’t null and setting the member variables, it calls the private GenerateCode method, passing the file name and its contents. After it returns, the method checks if the contents generated are null. If they are null, the code returns a pointer to nothing and returns to the IDE. If there are generated stuff, however, it copies the contents to the pbstrOutputFileContents variable and returns to the IDE. The IDE will get that variable and will write its contents to a file.


Now lets take a look at the GenerateCode method:


protected byte[] GenerateCode(string inputFileName, string inputFileContent)

{

    CodeCompileUnit compileUnit;

 

    StreamWriter writer = new StreamWriter(new MemoryStream(), Encoding.UTF8);

 

    XmlDocument doc = new XmlDocument();

    doc.LoadXml(inputFileContent);

 

    compileUnit = ClassGenerator.Create(doc, this.codeProvider);

 

    if (this.codeGeneratorProgress != null)

    {

        this.codeGeneratorProgress.Progress(0x4b, 100);

    }

 

    this.CodeProvider.GenerateCodeFromCompileUnit(compileUnit, writer, null);

 

    if (this.codeGeneratorProgress != null)

    {

        this.ThrowOnFailure(this.codeGeneratorProgress.Progress(100, 100));

    }

    writer.Flush();

 

    return this.StreamToBytes(writer.BaseStream);

}

 

The above method is fairly simple: It declares a variable of type CodeCompileUnit that will hold the code generated by out other class. Then it creates a StreamWriter to write the contents of the generated stuff and calls the Create method of our helper class.


When the method returns, it sets the progress to the IDE and call the method GenerateCodeFromCompileUnit, which actually converts the DOM stuff into C# code.


Note that the GenerateCodeFromCompileUnit method now belongs to the CodeProvider class and not to the ICodeGenerator interface any more, and that interface is now obsolete.


Now the writer contains the C# code generated by our code generator. The only remaining thing to do is to return the byte buffer of that stream, so I call another helper method that does it for us:


protected byte[] StreamToBytes(Stream stream)

{

    if (stream.Length == 0)

    {

        return new byte[0];

    }

 

    long pos = stream.Position;

 

    stream.Position = 0;

 

    byte[] buffer = new byte[(int)stream.Length];

    stream.Read(buffer, 0, buffer.Length);

 

    stream.Position = pos;

    return buffer;

}

 

We have finished implementing the members of IVsSingleFileGenerator, so we should implement the methods if IObjectWithSite. I have implemented these methods but I won’t list them here. You can download the zip file containing the whole source code and you will see that they are fairly simple.


We have now to install all this stuff. Well, the installation is straightforward, but some steps are required:

First, create a key pair using the SN.exe tool and strongly name your assembly. Then you can add it to the GAC. After you have it on the GAC, you have to register it with Visual Studio.


The registration is by adding the following keys to the registry:


Windows Registry Editor Version 5.00

 

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\CLSID\{DDD23356-DC41-4eed-9D3B-84973D5EC57B}]

@="XmlClassGenerator.XmlClassGenerator"

"InprocServer32"="C:\\WINDOWS\\system32\\mscoree.dll"

"Class"="XmlClassGenerator.XmlClassGenerator"

"Assembly"="XmlClassGenerator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=35e4ca7ea38d2508"

"ThreadingModel"="Both"

 

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\Generators\{164B10B9-B200-11D0-8C61-00A0C91E29D5}\XmlClassGenerator]

@="Sample XML Class Generator"

"CLSID"="{DDD23356-DC41-4eed-9D3B-84973D5EC57B}"

"GeneratesDesignTimeSource"=dword:00000001

 

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\Generators\{E6FDF8B0-F3D1-11D4-8576-0002A516ECE8}\XmlClassGenerator]

@="Sample XML Class Generator"

"CLSID"="{DDD23356-DC41-4eed-9D3B-84973D5EC57B}"

"GeneratesDesignTimeSource"=dword:00000001

 

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\Generators\{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}\XmlClassGenerator]

@="Sample XML Class Generator"

"CLSID"="{DDD23356-DC41-4eed-9D3B-84973D5EC57B}"

"GeneratesDesignTimeSource"=dword:00000001

 

Well, after you do that, you can create a new XML file, type its contents as the example on the beginning of this article and set its Custom Tool property to your newly created class. It should automatically create the new C# file as shown in the screenshot below:


I have implemented a code similar to this one on a client project I’m working, so to allow the other developers to easily use the tool I have created an Item Template for Visual Studio with the Custom Tool property already set. Now when we create a file from that property we already get the code generation.

Download the complete source code for my sample project.

UPDATE: I forgot to mention that the code was written and tested on Visual Studio 2005 Beta 2. Don't know if it will work on other versions.

Published Saturday, May 21, 2005 5:59 PM by paulo.reichert

Comments

 

paulo.reichert said:

You're a nutter mate. Need to get out more.
May 23, 2005 12:40 PM
 

paulo.reichert said:

Pretty cool stuff, Paulo. I take it the item template isn't in the download?

S.
May 23, 2005 2:35 PM
 

TheChaseMan's Frenetic SoapBox said:

December 2, 2005 7:53 PM
 

TheChaseMan's Frenetic SoapBox said:

December 2, 2005 7:55 PM
 

Pedram Rezaei's Ramblings said:

I was asked today how easy it is to hook into the WCF client proxy generation process in Visual Studio

August 10, 2007 10:30 AM
 

Noticias externas said:

I was asked today how easy it is to hook into the WCF client proxy generation process in Visual Studio

August 10, 2007 11:14 AM
 

MSDN Blog Postings » Customising WCF Proxy Generation in Visual Studio 2008 said:

August 10, 2007 1:06 PM
 

working with resx file c said:

April 18, 2008 12:22 AM
 

Designing Out Loud in the .NET Space said:

Interesting bits from searching the web: MSDN Code Gallery has a page on extending Visual Studio .&#160;

October 25, 2008 1:50 PM
 

Code generator output opens in notepad | keyongtech said:

January 22, 2009 3:26 AM
 

TeraBIThia said:

Pasos para crear una custom tool para visual studio 1 - Crear proyecto Class Library, agregar referencia

March 23, 2009 10:38 PM
 

Visual Studio Custom Tool « TeraBIThia said:

March 23, 2009 10:40 PM
Anonymous comments are disabled
Powered by Community Server (Personal Edition), by Telligent Systems