Article originally written by Corbin Dunn (Borland R&D Software Engineer).
First part of this article explained what is CodeDOM. The CodeDOM is a collection of classes used to represent source code. Once source code is represented in a CodeDOM, it can then be printed, compiled to an assembly, or compiled to memory and executed.
We are now ready for Delphi code.
Creating a CodeDOM in Delphi Code
Create a CodeDOM is very easy. You first create a CodeCompileUnit (since it contains everything else) and then add a namespace to it. The namespace can then specify what other namespaces (or units) that it uses:
var
Namespace: CodeNamespace;
begin
// Create the primary code
//unit that contains everything
FCodeUnit := CodeCompileUnit.Create;
// Create a namespace and
//add it to that unit
Namespace := CodeNamespace.Create
('MyUnitName');
FCodeUnit.Namespaces.Add(Namespace);
// Add a few items to
//the "uses" clause
Namespace.Imports.Add(
CodeNamespaceImport.Create
('System.Collections'));
Namespace.Imports.Add(
CodeNamespaceImport.Create
('System.Data'));
Namespace.Imports.Add(
CodeNamespaceImport.Create
('System.Xml'));
...
|
Unfortunately, one of the drawbacks of using the CodeDOM is that it doesnt know about the Delphi language. There is no way to specify for a CodeNamespaceImport to be in the implementation sections uses clause.
The next thing you will want to do is add one ore more types to the CodeDOM. The CodeDOM has no knowledge of global procedures or units, so unfortunately there is no way of adding them at this time (although, this may change at some later time).
var
...
MyType: CodeTypeDeclaration;
begin
...
// Create a type and add it to the namespace
MyType := CodeTypeDeclaration.Create('TMyClass');
Namespace.Types.Add(MyType);
...
|
A blank type isnt very interesting, so you probably should add some members to it:
var
...
MyMethod: CodeMemberMethod;
MyField: CodeMemberField;
begin
...
// Now add a field to the members in the type
MyField := CodeMemberField.Create('Integer', 'FInt');
MyType.Members.Add(MyField);
// Create method to put in this type
MyMethod := CodeMemberMethod.Create;
MyMethod.Name := 'MyMethod';
MyType.Members.Add(MyMethod);
|
The real interesting part is adding statements and expressions to the method:
var
...
Statement: CodeStatement;
LeftExpr, RightExpr: CodeExpression;
MethodExpr: CodeExpression;
Target: CodeTypeReferenceExpression;
VarReference: CodeVariableReferenceExpression;
begin
...
// Create a variable, and assign a value to it.
Statement := CodeVariableDeclarationStatement.Create
('string','LocalStr');
MyMethod.Statements.Add(Statement);
// Assign a value to that variable
LeftExpr := CodeVariableReferenceExpression.Create('LocalStr');
RightExpr := CodePrimitiveExpression.Create('LocalValue');
Statement := CodeAssignStatement.Create(LeftExpr, RightExpr);
MyMethod.Statements.Add(Statement);
// Write it out to the console
Target := CodeTypeReferenceExpression.Create
('System.Console');
VarReference := CodeVariableReferenceExpression.Create
('LocalStr');
MethodExpr := CodeMethodInvokeExpression.Create(
Target, // The thing we are calling on
'WriteLine', // The method we are going to call
[VarReference]); // Parameters
MyMethod.Statements.Add(MethodExpr);
...
|
Doing anything else is a matter of figuring out the correct CodeDOM object to create and add it in the appropriate place.
Printing a CodeDOM
Now that you have a CodeDOM you probably want to do something with it. Printing it is as simple as selecting the CodeDomProvider for the language of your choice, and printing it out:
var
Provider: CodeDomProvider;
Generator: ICodeGenerator;
GeneratorOptions: CodeGeneratorOptions;
Writer: TextWriter;
Builder: StringBuilder;
begin
// Print the code in delphi
if FCodeUnit = nil then
Exit;
Provider := DelphiCodeProvider.Create
// Create a writer to output to
Builder := StringBuilder.Create;
Writer := StringWriter.Create(Builder);
// And options to control printing
GeneratorOptions := CodeGeneratorOptions.Create;
GeneratorOptions.IndentString := ' ';
GeneratorOptions.BracingStyle := 'C';
GeneratorOptions.BlankLinesBetweenMembers := True;
Generator := Provider.CreateGenerator;
Generator.GenerateCodeFromCompileUnit
(FCodeUnit, Writer,GeneratorOptions);
// Get the text that was written out
TextBox1.Text := Builder.ToString;
end;
|
This example uses the ICodeGenerator.GenerateCodeFromCompileUnit method to print out the whole CodeCompileUnit. Additionally, you can use some of the other methods on ICodeGenerator to print just expressions, statements, etc.
Compiling and Executing
Another cool thing you can do with a CodeDOM is compile it or execute it. The CodeDomProvider of your choice can give an ICodeCompiler that can be used to compile the source:
var
Compiler: ICodeCompiler;
Parameters: CompilerParameters;
Results: CompilerResults;
begin
// Create an assembly from the code unit
Compiler := CSharpCodeProvider.Create.CreateCompiler;
Parameters := CompilerParameters.Create(
['mscorlib.dll',
'System.dll',
'System.Data.dll'],
'NewAssembly.dll');
Results := Compiler.CompileAssemblyFromDom
(Parameters, FCodeUnit);
if Results.Errors.Count > 0 then
MessageBox.Show('Could not compile unit: ' +
Results.Errors[0].ErrorText);
end;
|
The reason for using one CodeDomProvider versus another has to do with how your CodeDOM was created. For instance, if you use types that are native to your compiler, such as Integer in Delphi or int in C#, then you will have to use that compiler to compile your source. Or, if you use CodeSnippetXXX CodeObjects then the snippets are placed directly in the compiled source code, and must be in the same target language. If you stick to fairly safe CodeDOM expressions, and use types found in the CLR, then you should be safe.
For a complete example of all these concepts, take a look at the included project, DelphiDomPrinter.bdsproj.
We now move for the last part of the article: the DelphiProvider.dll - CodeDomProvider for Delphi code.
Next part > DelphiProvider, a CodeDomProvider designed for Delphi developers > Part 1, 2, 3