CodeDOM与Delphi IDE

The CodeDOM and the Delphi IDE By Corbin Dunn

Borland R&D Software Engineer

cdunn@borland.com

翻译: visli

 

CodeDOM是什么?

 

CodeDOM是代码文档对象模型(Code Document Object Model)的缩写,它允许.Net开发者以多种语言在运行时产生和编译源代码。CodeDOM是用于描述源代码的类的一个集合。一旦源代码被描述为CodeDOM,它就能被打印、编译成汇编语言、或编译到内存中并执行。应用程序也能使用CodeDOM,来作为阅读源代码的一个抽象层,而无需有下面语言的内部知识。

 

WinForms / WebForms 设计师

 

CodeDOM主流使用是在.NET框架的WinForms 和 WebForms设计者。 These designers require a CodeDOM to be provided for them, and walk the DOM looking for types that can be designed. Once a type is found, they look for the InitializeComponents method and walk each statement in the method, executing them as it does along. The end result is a deserialized form (or web) designer.

 

When changes are made to the form designer, the current state of the designer is flushed out to a CodeDOM object. This object can then be injected back into the original source code. By using an abstract set of classes, the WinForms and WebForms designers do not need to know anything about the underlying programming language.

 

ASP. NET

 

ASP. NET also uses the CodeDOM. When ASPX pages have source code mixed in, the ASP. NET engine must some how compile this source. One of the underlying problems stems from the fact that someone can write an ASPX page in a variety of different languages. In order to keep it well abstracted, the ASP. NET engine creates a CodeDOM for the page. The CodeDOM is language independent, making the engine able to manipulate any language that has a CodeDOM provider. It can then compile the CodeDOM to an assembly, execute it, and return the resulting HTML.

 

CodeDOM 基础

 

使用CodeDOM相当简单。但要比较好的掌握它,我强烈推荐阅读下面的帮助:

 

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconGeneratingCompilingSourceCodeDynamicallyInMultipleLanguages.asp

 

另外,CodeDOM快速参考也是必不可少的:
 

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconCodeDOMQuickReference.asp

 

CodeDOM概述

 

System.CodeDom名字空间包含了用于构造一个CodeDOM的全部类。基础封装如下:

 

CodeCompileUnit

囊括所有且位于CodeDOM对象最顶层。它主要包含一个或多个 CodeNamespace 对象。

CodeNamespace

在源代码中描绘一个名字空间。该名字空间可以包含在它内部的类型,每个类型都是一个 CodeTypeDeclaration 或它的子代。

CodeTypeDeclaration

The basic CodeTypeDeclaration is used to represent classes, enumerations, interfaces, and structures (records). A CodeTypeDelegate is used to represent a delegate (which is an event declaration). The CodeTypeDeclaration contains CodeTypeMembers to represent the members in the type.

CodeTypeMember

This is the base abstract class for all type members. There are things to represent all basic code members, including: CodeMemberMethod, CodeMemberField, CodeMemberProperty, CodeConstructor, and a CodeTypeConstructor (a class constructor). Most CodeTypeMembers can have attributes applied to them; this is done via a CodeAttributeDeclaration, which, unlike most all the other classes, does not descend from CodeObject.

CodeAttributeDeclaration

This class allows you to specify an attribute. It basically contains the name of the attribute, and any arguments passed to it (which are also represented by CodeDOM objects).

CodeMemberMethod

The CodeMemberMethod  contains Statements each of which are an instance of a CodeStatement object.

CodeStatement

The CodeStatement is the base class for all statements. There are CodeAssignStatement,  CodeConditionStatement (used for if or while) along with many others. Each statement is generally made of up expressions (or it is an expression, with the CodeExpressionStatement), represented by the CodeExpression abstract class.

CodeExpression

Represents all expressions. Expressions include ways of referencing fields, properties, and variables (CodeFieldReferenceExpression, CodePropertyReferenceExpression, and CodeVariableReferenceExpression), along with ways of invoking events, or referencing primitive values (such as integers, or strings).

CodeSnippetXXX

The CodeSnippetXXX classes (such as CodeSnippetExpression) are useful for representing a snippet of source code that cannot be represented in the CodeDOM. The problem with snippets is that they are language dependent.

CodeObject

The CodeObject class is the base abstract class for most all of the other CodeDOM classes. The main thing that it introduces is the UserData property; allowing a user of the CodeDOM to store information in a particular CodeObject.

 

But what can you do with CodeDOM objects once you have created them? The System.CodeDom.Compiler namespace has most of the classes and interfaces used to do things with a CodeDOM.

 

One thing you may want to do, is print the CodeDOM in a particular language. To do this, you must get a code generator (ICodeGenerator) for that particular language. An ICodeGenerator is generally acquired through a CodeDomProvider. A CodeDomProvider also allows you to create a code compiler (ICodeCompiler) to compile the code.

 

For example of this, see the included source 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 doesn’t know about the Delphi language. There is no way to specify for a CodeNamespaceImport to be in the implementation section’s 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 isn’t 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, Delphi DomPrinter.bdsproj.

The CodeDOM and Delphi The DelphiProvider

 

The CodeDomProvider is the base factory class that provides a way of accessing an ICodeCompiler and ICodeGenerator. This allows you to respectively compile and print code in a particular language.

 

The . NET Framework includes generators for C#, VB, and Java (MS style). To use the C# or VB one, all you have to do is use the Microsoft.CSharp or Microsoft.VisualBasic namespace. To use the Java one, you must use Microsoft.VJSharp and reference the assembly VJSharpCodeProvider.dll.

Borland has provided a CodeDomProvider to use for Delphi . To use it, you must reference the assembly DelphiProvider.dll and use the namespace Borland.Delphi.

WinForms/WebForms Only?

 

One thing to be aware of is that the CodeDOM is available for general VCL for . NET development, and not just WinForms or WebForms. Since the CodeDOM classes and interfaces are in the base . NET Framework Class Library  (FCL), one can easily use them at anytime, without any dependency on System.Windows.Forms.dll.

 

Using the CodeDOM in the Delphi IDE

 

You may have noticed that the CodeDomProvider has a CreateParser method on it. Unfortunately, all of the standard CodeDomProviders (including the Delphi one) do not implement this interface. So, there is no easy way for an external application to go from source code to a CodeDOM. But, Borland implemented a parser for C# and Delphi to create a CodeDOM, and inside the BDS IDE you can access it.

Using the OpenTools API to access the CodeDOM

 

You can create an OpenTools API (OTA) plug-in that utilizes the Delphi and C# CodeDOM’s from inside the BDS IDE.

 

Look at the sample project for the implementation details of exactly how to create an OTA plug-in, if you are not familiar with how to do so.

 

To install the plug-in into the IDE, start regedit, and add a string value under the key at:

 

HKCU/Software/Borland/ BDS /(your version)/ Known IDE Assemblies

 

Set the Name to your plug in assembly’s location (ie: $( BDS )/bin/MyPlugin.dll) and set the value to anything but an empty string (ie: “My IDE Assembly”).

Reading the CodeDOM

 

Only can only get a DOM from files that are open in the IDE. But, don’t worry, it is possible to open files using the IOTAModuleService. An opened file in the IDE is represented by an IOTAModule.

 

Once you have an IOTAModule, you can query for the IOTACodeDomProvider interface:

 

var

  CurrentModule: IOTAModule;

  Provider: IOTACodeDomProvider;

  DomFile: IOTACodeDomFile;

begin

  CurrentModule := BorlandIDE.ModuleServices.CurrentModule;

  Provider :=

IOTACodeDomProvider(

CurrentModule.GetService(typeof(IOTACodeDomProvider)));

 

 

If the module doesn’t support a CodeDOM, it will return nil. After you have the provider, you can access the IOTACodeDomFile from it. It is always good to check for nil in this case too:

 

  if Provider <> nil then

  begin

    DomFile := Provider.CodeDomFile;

    if DomFile <> nil then

      ... // Do something with the DomFile

  end;

 

Once you have an IOTACodeDomFile, you can finally get a CodeDOM:

 

  MyCompileUnit := CodeCompileUnit(DomFile.GetDom);

 

It is that easy! You can now do things, like walk all the namespaces and types in that CodeDOM, doing whatever operations you want to it.

Browsing the CodeDOM

 

Let’s say that you want to know what line and column a particular CodeMemberMethod is. That’s easy; you can use the CodeTypeMember.LinePragma property. But, an examination of the CodeLinePragma type reveals that it only has the FileName and LinNumber for the given member; there is no column information!

 

To handle this situation, BDS produced CodeDOM’s have special UserData added to the CodeObject.UserData property. If you access CodeMemberMethod.UserData[‘Line’] and CodeMemberMethod.UsetData[‘ Col ’] you can get the line and column of a method’s implementation body! This is how the IDE finds where to go when you add a new method with the WindowsForms designer.

Committing Changes

 

Once you have made some changes to the CodeDOM, you will probably want to commit those changes back to source code in the IDE. You can easily do this with the IOTACodeDomFile.WriteDom(...) method. The first parameter to WriteDom is the DOM you want to commit. The second parameter is the formatting options you want to use; if you pass nil, the default IDE options will be used.

 

Important notes

 

There are several things to be aware of when using a CodeDOM from inside the IDE:

 

·        You can only update entire methods; you cannot update individual statements or expressions.

·        Things which can’t be represented in the CodeDOM are ignored (ie: global procedures)

·        The Delphi CodeDOM can only ever contain one namespace. Attempting to add another namespace to it will be ignored.

·        When committing a CodeDOM to the IDE, the underlying source code cannot have had changes made to it since you requested the CodeDOM. It is best to always grab a fresh CodeDOM from the IOTACodeDomFile, make your changes, and then immediately commit the changes to the IOTACodeDomFile.

Conclusion

Well, that’s it! You should now have the knowledge of how to leverage the ultimate power of the CodeDOM.

 

Be sure to take a look at the example code for a tutorial of how to use the CodeDOM; both from a stand alone application, and an IDE plug-in wizard.

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值