您的位置:首页 > 编程语言

谈谈.NET中如何根据代码自动生成代码对象模型的解决思路

2010-09-28 21:10 639 查看
一、写作背景

最近笔者一直在从事如何利用Visual Studio Add-in和.NET 的CodeDOM来提升GIS领域程序员开发效率的研究。目前总算是得到了一些成果,当然,这些成果笔者准备开源,并陆续地写成博客和大家分享。其中这些成果当中,对.NET 的CodeDOM的研究是相对独立的一块,所以想先单独拿出来写点东西。但同时,笔者也观察到近来在园子里这方面的文章也非常多,如Artech的《从数据到代码——通过代码生成机制实现强类型编程[上篇]》,以及破狼的 CodeDom系列,都是非常不错的好文章。所以他们写过的笔者肯定是不能写的,主要是怕被拍砖。因此笔者准备写点他们都没涉及到的内容,比如题目中提到的CodeDomProvider.Parse的解决思路。CodeDomProvider.Parse主要是为了将代码文件中的代码文本动态地解析成CodeCompileUnit,即CodeDOM的内存对象模型。其中CodeCompileUnit的使用在本文不会涉及,感兴趣的朋友可以参考前面提到的相关文章。

二、问题描述
使用过CodeDomProvider.Parse的朋友也许知道,.NET4.0中并没有具体实现该方法,如果强行调用则会抛出异常,甚至微软官方解释目前只能要求程序员自己实现该方法,因此纯粹依赖.NET是无法实现将代码文件中的文本动态地解析成CodeCompileUnit的。但是,在代码自动生成的程序当中却会很自然地涉及到这方面的需求,所以笔者决定重写一个类CodeDOMSerializer来代替原来CodeDomProvider。

三、设计思路
该类CodeDOMSerializer继承了抽象类CodeDomProvider,并在其中定义了CodeDomProvider类型的成员变量来提供基本的根据CodeCompileUnit动态生成代码的功能。而为了解决将代码文件中的文本动态地解析成CodeCompileUnit的问题,则是先在实例动态生成代码的过程中自动维护一个与生成的代码配套的CodeCompileUnit二进制序列化文件(本文定义该文件后缀名为.dom),而当需要将代码文件中的文本动态地解析成CodeCompileUnit的时候则将该代码文件对应的.dom文件中反序列化成CodeCompileUnit,从而有效地回避了必须从代码文件生成CodeCompileUnit实例的问题。流程图如下所示:

CodeDOMSerializer

class CodeDOMSerializer : CodeDomProvider, SymbolEditor.Addin.CodeDom.ICodeDOMSerializer
{
#region fields

private CodeDomProvider provider = null;
private string objDir = "obj";

#endregion

#region constuctor

public CodeDOMSerializer(string language)
: base()
{
provider = CodeDomProvider.CreateProvider(language);
}

#endregion

#region IDisposable 成员

public void Dispose()
{
base.Dispose();
provider.Dispose();
provider = null;
}

#endregion

#region public methods

[Obsolete()]
public override ICodeCompiler CreateCompiler()
{
return provider.CreateCompiler();
}

[Obsolete()]
public override ICodeGenerator CreateGenerator()
{
return provider.CreateGenerator();
}

public override void GenerateCodeFromCompileUnit(System.CodeDom.CodeCompileUnit compileUnit, System.IO.TextWriter writer, CodeGeneratorOptions options)
{
if (writer is StreamWriter && ((writer as StreamWriter).BaseStream is FileStream))
{
string filename = ((writer as StreamWriter).BaseStream as FileStream).Name;
string parentDir = (new FileInfo(filename)).Directory.FullName;
string objDirectory = parentDir + "\\" + objDir;
if (!Directory.Exists(objDirectory))
{
Directory.CreateDirectory(objDirectory);
}
string domfilename = System.IO.Path.GetFileNameWithoutExtension(filename) + ".DOM";
string domfullname = objDirectory + "\\" + domfilename;
serializeCodeDOMUnit(compileUnit, domfullname);
provider.GenerateCodeFromCompileUnit(compileUnit, writer, options);
}
else
{
throw new NotSupportedException("non file streamwriter is not supported!");
}

}

public void GenerateCodeFromCompileUnit(System.CodeDom.CodeCompileUnit compileUnit, ProjectItem codeItem, CodeGeneratorOptions options)
{
if (codeItem.Kind == Constants.vsProjectItemKindPhysicalFile)
{
using (AddinHelper.SafeAccessDocument safeADoc = new AddinHelper.SafeAccessDocument(codeItem))
{
//AddinHelper.SafeAccessDocument(codeItem);
//check whether the file being in read only status
if (codeItem.Document.ReadOnly)
{
throw new FileLoadException("The document '" + codeItem.Document.FullName + "' is read only!");
}
/*
* get the dom file coresponding to the code document,
*if the file is not exsit ,auto create it .
*/
ProjectItem domDoc = getSameNameSubItem(codeItem);

using (AddinHelper.SafeAccessDocument safeADom = new AddinHelper.SafeAccessDocument(domDoc))
{
//AddinHelper.SafeAccessDocument(domDoc);

//get textwriter to the document file
string dompath = domDoc.Document.FullName;
using (StreamWriter writer = new StreamWriter(codeItem.Document.FullName))
{
serializeCodeDOMUnit(compileUnit, dompath);
provider.GenerateCodeFromCompileUnit(compileUnit, writer, options);
}
}
}
}
else
{
throw new InvalidDataException("Invalid project item");
}
}

public override System.CodeDom.CodeCompileUnit Parse(TextReader codeStream)
{
if (codeStream is StreamReader && ((codeStream as StreamReader).BaseStream is FileStream))
{
string filename = ((codeStream as StreamReader).BaseStream as FileStream).Name;
string parentDir = (new FileInfo(filename)).Directory.FullName;
string objDirectory = parentDir + "\\" + objDir;

string domfilename = System.IO.Path.GetFileNameWithoutExtension(filename) + ".DOM";
string domfullname = objDirectory + "\\" + domfilename;
return DeserializeCodeDOMUnit(filename);
}
else
{
throw new NotSupportedException("non file streamReader is not supported!");
}
}

public System.CodeDom.CodeCompileUnit Parse(ProjectItem codeItem)
{
CodeCompileUnit ret = null;
if (codeItem.Kind == Constants.vsProjectItemKindPhysicalFile)
{
ProjectItem domDoc = getSameNameSubItem(codeItem);
using (AddinHelper.SafeAccessDocument safeADoc = new AddinHelper.SafeAccessDocument(domDoc))
{
ret = DeserializeCodeDOMUnit(domDoc.Document.FullName);
}
}
return ret;
}

public override void GenerateCodeFromStatement(System.CodeDom.CodeStatement statement, System.IO.TextWriter writer, CodeGeneratorOptions options)
{
provider.GenerateCodeFromStatement(statement, writer, options);
}

#endregion

#region private methods

private ProjectItem getSameNameSubItem(ProjectItem parentItem)
{
string filename = parentItem.Name + ".DOM";
string filepath = parentItem.Document.Path + "\\" + filename;
ProjectItem targetItem = null;
IEnumerable<ProjectItem> targets = parentItem.ProjectItems.Cast<ProjectItem>().
Where(element => element.Name.ToUpper().Equals(filename.ToUpper()));
if (targets.Count() > 0)
{
targetItem = targets.First();
}
else
{
if (File.Exists(filepath))
File.Delete(filepath);
//if not exsit ,create it
targetItem = AddinHelper.AddEmptyDoc2Project(parentItem.ProjectItems, filename);
}
return targetItem;
}

private void serializeCodeDOMUnit(System.CodeDom.CodeCompileUnit compileUnit, string domfullname)
{
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream(domfullname, FileMode.Create,
FileAccess.Write, FileShare.None);
try
{
formatter.Serialize(stream, compileUnit);

}
finally
{
stream.Close();
}
}

private System.CodeDom.CodeCompileUnit DeserializeCodeDOMUnit(string domfullname)
{
if (File.Exists(domfullname))
{
CodeCompileUnit ret = null;
IFormatter formatter = new BinaryFormatter();

Stream stream = new FileStream(domfullname, FileMode.Open,
FileAccess.Read, FileShare.None);
try
{
ret = formatter.Deserialize(stream) as CodeCompileUnit;
}
finally
{
stream.Dispose();
}
return ret;
}
else
{
throw new FileNotFoundException(domfullname);
}
}

#endregion
}


AddinHelper

class AddinHelper
{
public static string AddReference(EnvDTE.Project proj,string refPath)
{
string ret = string.Empty;
VSLangProj.VSProject vsProject = (VSLangProj.VSProject)proj.Object;

try
{
vsProject.References.Add(refPath);
}
catch (Exception ex)
{
ret = ex.ToString();
}
return ret;
}

public static ProjectItem checkDocumentExsist(EnvDTE.ProjectItems projItems, string filename)
{
ProjectItem ret = null;
ProjectItem item = null;
int foldercnt = 0;
for (int i = 1; i <= projItems.Count; i++)
{
item = projItems.Item(i);
if (item.Kind == Constants.vsProjectItemKindPhysicalFolder)
{
if (item.ProjectItems.Count > 0)
foldercnt++;
}
else
{
if (item.Kind == Constants.vsProjectItemKindPhysicalFile && item.Name.ToUpper().Equals(filename.ToUpper()))
{
ret = item;
break;
}
}
}
if (ret == null)
{
if (foldercnt > 0)
{
ProjectItem subItem = null;
for (int i = 1; i <= projItems.Count; i++)
{
item = projItems.Item(i);

if (item.Kind == Constants.vsProjectItemKindPhysicalFolder && item.ProjectItems.Count > 0)
{
subItem = checkDocumentExsist(item.ProjectItems, filename);
if (subItem != null)
{
ret = subItem;
break;
}
}
}
}
}
// = findDocument(proj, filename);

return ret;
}

public static ProjectItem AddEmptyDoc2Project(ProjectItems parent, string docname)
{
if ((new FileInfo(parent.ContainingProject.FullName)).IsReadOnly)
{
throw new FileLoadException("The project file '" + parent.ContainingProject.FullName + "' is read only!");
}
ProjectItem ret=null;
string tmpDir = System.IO.Path.GetTempPath();
string tmpCodefile = tmpDir + docname;
if (File.Exists(tmpCodefile))
{
File.Delete(tmpCodefile);
}
FileInfo file = new FileInfo(tmpCodefile);
FileStream stream = file.Create();
stream.Dispose();

ret = parent.AddFromFileCopy(tmpCodefile);

File.Delete(tmpCodefile);
return ret;
}

public delegate object safeDelegate(ProjectItem doc);

public class SafeAccessDocument:IDisposable
{
ProjectItem doc = null;
Window wnd = null;

public SafeAccessDocument(ProjectItem doc)
{
this.doc = doc;
if (doc.Kind == Constants.vsProjectItemKindPhysicalFile)
{
if (!doc.IsOpen)
{
wnd = doc.Open(Constants.vsViewKindPrimary);
}
}
}

#region IDisposable 成员

public void Dispose()
{
if (wnd != null)
wnd.Close(vsSaveChanges.vsSaveChangesNo);

}

#endregion
}

//public static object SafeAccessDocument(ProjectItem doc, safeDelegate func = null)
//{
//    object ret = null;
//    if (doc.Kind == Constants.vsProjectItemKindPhysicalFile)
//    {
//        if (!doc.IsOpen)
//        {
//            /*
//            * the function 'libclsItem.Open' must be invoked
//            * because visual studio initializes the object 'ProjectItem.Document'
//            * asynchronously.So if call the function could force vs initializes
//            * it right away.
//            */
//            Window wnd = doc.Open(Constants.vsViewKindPrimary);
//            if (func != null)
//            {
//                ret = func(doc);
//            }
//            /*
//            * if you open the document in the vs window,
//            * of cource you should close it ,because of avoiding
//            * the problem of opening the document later.
//            */
//            wnd.Close(vsSaveChanges.vsSaveChangesNo);
//        }
//        else
//        {
//            if (func != null)
//            {
//                ret = func(doc);
//            }
//        }

//    }
//    return ret;
//}
}


五、我们在哪

本文中在类CodeDOMSerializer中设计的一个.dom文件来存储CodeCompileUnit实例的二进制序列化结果,从而解决了CodeDomProvider.Parse未实现的将代码文件中的文本动态地解析成CodeCompileUnit的问题。并且接口的设计将对.dom文件的管理完全封装到了该类的内部,对调用方完全不可见,从而避免了调用程序需要做出大量修改才能使用该类功能的情况。
六、后言
由于本文中的例程主要是针对在VS2010 Add-in中实现代码自动生成功能,并且为了确保该类在VS2010种使用更方便,不可避免地在设计时就将该类与VS2010的对象模型绑定。若您需要在自定义环境中使用该类,则根据本文中介绍的原理对该类的实现做些简单调整即可。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐