In my last post I covered dynamically compiling code with Delphi Prism and this time I’m going to introduce dynamically creating code which could then be written out to a source file or compiled from in memory. You might want to generate code dynamically for a wide variety of reasons, whether it be to bootstrap a project easily, automatically add some utility code or generate wrapper classes automatically. Dynamically generating code with Delphi Prism is (like almost everything in .NET) no different to the process in which it is done in C# or VB.NET. It is easy to forget that Delphi Prism is fully compatible with the whole of the .NET Framework and this means that you can now safely go back to having the MSDN Documentation as your best friend.
Before you get too hasty with building your own class generators it’s also worth noting that Microsoft’s xsd.exe tool can also be used with Delphi Prism (thanks to Jeroen Pluimers and Peter Nowotnick for sharing this tip with us).
In Order to get started you’ll need to reference the RemObjects.Oxygene.CodeModel.dll and add the CodeDom namespace from this plus System.CodeDom to your uses.
It is worth noting that automated code generation is like our work with the Delphi Prism AOP Cirrus framework in that the code required to create a small amount of target output code can become quite hard to read at a glance so organise it well.
We’re going to start off by creating a basic class for Saying Hello (highly innovative you understand! 😉 ) and then generating the code and writing it to a new .pas file. Our basic first steps in generating a class involve setting up the unit (with a CodeCompileUnit), the namespace (with a CodeNamespace) and the class itself (with a CodeTypeDeclaration):
namespace CodeDomGeneration; interface uses System.Linq, System.Reflection, System.IO, System.CodeDom, System.CodeDom.Compiler, RemObjects.Oxygene.CodeDom; type ConsoleApp = class public class method Main; end; implementation class method ConsoleApp.Main; const outputFile = 'uMyClass.pas'; var uNewUnit: CodeCompileUnit; MyHelloNS: CodeNamespace; MyNewClass: CodeTypeDeclaration; provider: CodeDomProvider; begin uNewUnit := new CodeCompileUnit; MyHelloNS := new CodeNamespace('Hello'); MyHelloNS.Imports.Add(new CodeNamespaceImport('System')); MyNewClass := new CodeTypeDeclaration('TSayHelloClass'); MyNewClass.IsClass := true; MyNewClass.TypeAttributes := TypeAttributes.Public + TypeAttributes.Sealed; MyNewClass.Comments.Add(new CodeCommentStatement('This is our CodeDom created class :-)')); MyHelloNS.Types.Add(MyNewClass); uNewUnit.Namespaces.Add(MyHelloNS); // Get our CodeDomProvider provider := CodeDomProvider.CreateProvider('Oxygene'); // Options. var options: CodeGeneratorOptions := new CodeGeneratorOptions(); // Create our output filestream. var sourceWriter: StreamWriter; sourceWriter := new StreamWriter(outputFile); // Generate our code. provider.GenerateCodeFromCompileUnit(uNewUnit, sourceWriter, options); // Close our output filestream sourceWriter.Close(); Console.WriteLine('Press Enter to exit..'); Console.ReadLine; end; end.
If you run this code, you’ll find a new uMyClass.pas file has been generated in the output directory which contains little more than a class shell and a couple of comments: One to note that the source file was auto-generated by a tool and another to which is our own comment on the TSayHelloClass. You’ll also note that unlike the previous article, we’re using:
To Generate code from the Code Document Object Model rather than the Compile method we used last time. You could also compile directly from a CodeDom object as alluded to in my previous post on Dynamically compiling Delphi Prism code using the CodeDomProvider.CompileAssemblyFromDom method.
var MyNameProperty: CodeMemberProperty; var MyNameField: CodeMemberField; // Add our private field. MyNameField := new CodeMemberField; MyNameField.Name := 'fFullName'; MyNameField.Type := new CodeTypeReference('System.String'); MyNameField.Attributes := MemberAttributes.Private; // Add the public property with a Getter and Setter MyNameProperty := new CodeMemberProperty; MyNameProperty.Name := 'FullName'; MyNameProperty.Type := new CodeTypeReference('System.String'); MyNameProperty.Attributes := MemberAttributes.Public; MyNameProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), 'fFullName'))); MyNameProperty.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), 'fFullName'), new CodePropertySetValueReferenceExpression())); // Add our two new objects to the parent code tree MyNewClass.Members.Add(MyNameField); MyNewClass.Members.Add(MyNameProperty);
It could very easy to get carried away creating an initialising your new class, methods, fields or properties and forget to .Add() them to the target CodeStatementCollection so if your new objects do not show up in the generated code, check that you have actually added them. We can now add a public SayHello method to our class which will greet the previously named entity:
var MySayHello: CodeMemberMethod; // Our public method to say "Well Hi there <fFullName>" MySayHello := new CodeMemberMethod(); MySayHello.Name := 'SayHello'; MySayHello.ReturnType := new CodeTypeReference(typeof(System.String)); MySayHello.Attributes := MemberAttributes.Public; MySayHello.Statements.Add(new CodeMethodReturnStatement(new CodeBinaryOperatorExpression(new CodePrimitiveExpression('Well Hi there '), CodeBinaryOperatorType.Add, new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), 'fFullName')))); // Add our new method to the parent class. MyNewClass.Members.Add(MySayHello);
The Method statements that I have added here have been created on the fly and not been structured well for readability at all. As I mentioned at the beginning, you will need to structure your code carefully to ensure readability. If you run this code again now, you should see our SayHello Method as below:
method TSayHelloClass.SayHello: System.String; begin exit(('Well Hi there ' + self.fFullName)); end;
You can also add statements from “snippets“, which allows you to essentially inject code as a string using the CodeSnippetExpression class:
var snippetSayHelloMethod: CodeSnippetExpression := new CodeSnippetExpression('Result := Self.fFieldName'); //Convert the snippets into Expression statements. var stmtSayHelloResult: CodeExpressionStatement := new CodeExpressionStatement(snippetSayHelloMethod); // Add it to our SayHello Method MySayHello.Statements.Add(stmtSayHelloResult);
Warning: Adding method statements from snippets in this manner should be used with great care as you have very little protection against a typo causing a mal-formed code output and I would not recommend generating complex code statements from the CodeSnippetExpression.
There is a Code object representation of anything that you could want to generate, you simply need to find the right class. For example, if we wanted to add a class entry point to our class then we could create one with the CodeEntryPointMethod class:
var MyMain: CodeEntryPointMethod := new CodeEntryPointMethod(); MyMain.Name := 'Main'; MyMain.Attributes := MemberAttributes.Public + MemberAttributes.Static; var cs1: CodeMethodInvokeExpression := new CodeMethodInvokeExpression( new CodeTypeReferenceExpression('System.Console'), 'WriteLine', new CodePrimitiveExpression('Hello World!') ); MyMain.Statements.Add(cs1); MyNewClass.Members.Add(MyMain);
The System.CodeDom space is a very very powerful tool and one that developers used to developing native Win32 Delphi might not be aware of. You will probably be able to dream up of a number of cases where generating some of your Delphi Prism code dynamically could save you time. Use it wisely.