针对C/C++代码的工程间依赖关系的层验证

在前面一篇blog中我提到过,最近我们团队刚刚发布了针对Visual Studio 2010的一个功能扩展包:Visualization and Modeling Feature Pack , 其中包含的一个非常cool的功能就是基于C/C++代码的工程间依赖关系的层验证,为广大的C++程序员带来了福音。

层验证是Visual Studio 2010中一个非常有用的功能,在项目的设计阶段,架构师可以借助层图创建Class/Namespace/Method/Project之间的依赖约束关系,在开发阶段中,频繁的代码验证可以帮助项目团队尽快地发现实现与设计的偏离,最终协助 项目团队开发出与设计一致的项目工程。我这篇blog主要侧重于针对C/C++项目的层验证,若对层验证不熟悉的读者,可以参考文章使用VSTS 2010进行层验证 ,里边将会手把手教您怎么创建建模工程,层图以及如何做层验证。

目前我们所支持依赖关系验证仅仅是针对生成目标为.DLL/.EXE的工程间静态调用,静态调用是针对动态调用而言的,是动态链接库调用的一种方式,调用时是由编译系统完成对DLL的加载(Load)和应用程序结束时DLL的卸载(Free)的代码注入。下面我将会通过一个简单案例来介绍如何使用 Visualization and Modeling Feature Pack中C/C++代码的工程之间依赖关系的层验证来帮助项目团队开发出与设计一致的项目工程。案例开始啦。。。

用户需求: 根据源代码结构,绘制一张程序元素Namespace和Class之间包含关系的图。针对目标编程语言可以是C++,C#,和JAVA。
设计: 为了支持将来出现的新型编程语言,增加项目的可扩展性和可维护性。在这里我们将项目实现抽象为3个工程:

  1. Data Provider:解析源代码文件,设别源代码中的程序结构和元素。
  2. Data Format:接受Data Provider产生的数据源,使用XML文件存储Namespace和Class之间的包含关系。
  3. Draw Diagram:以图形化方式显示DataFormat层所产生的xml文件。

根据以上的设计思想,首先创建解决方案ElementPresenter,然后添加建模工程(ModelingProject) ElementPresenter,并在该建模工程下添加如下的层图来表示项目中将会包含的各个工程以及工程之间调用关系的约束。

这个层图规定工程DrawDiagram可以调用工程DataFormat中提供的API,工程DataFormat可以调用 DataProvider提供的API,但是DrawDiagram不能跨越工程DataFormat直接使用DataProvider提供的API。

实现:

  1. 在解决方案ElementPresenter下添加动态链接库工程DataProvider,其生成的目标动态链接库的导出元素有函数ReadElements和类SourceElements,如下所示:

     #ifdef DATAPROVIDER_EXPORTS
    
    #define DATAPROVIDER_API __declspec(dllexport)
    
    #else
    
    #define DATAPROVIDER_API __declspec(dllimport)
    
    #endif
    
    class DATAPROVIDER_API SourceElements 
    
    {
    
    
    
    };
    
    SourceElements DATAPROVIDER_API ReadElements();
    
  2. 添加动态链接库工程DataFormat,其生成的目标动态链接库的导出函数是ReadXMLFormat,如下:

     #ifdef DATAFORMAT_EXPORTS  
    
    #define DATAFORMAT_API __declspec(dllexport)  
    
    #else  
    
    #define DATAFORMAT_API __declspec(dllimport)  
    
    #endif  
    
    string DATAFORMAT_API ReadXMLFormat();  
    
  3. 添加应用程序DrawDigram,如下:

     #include "..\DataFormat\DataFormat.h"
    
    #include "..\DataProvider\DataProvider.h"
    
    void DrawDiagram();
    
    int _tmain(int argc, _TCHAR* argv[])
    
    {   
    
        DrawDiagram();
    
        return 0;
    
    }
    

    其中函数DrawDiagram的实现为:

     #include "stdafx.h"
    
    #include "..\DataFormat\DataFormat.h"
    
    #include "..\DataProvider\DataProvider.h"
    
    string AnalysisDependencyDecorator(SourceElements elements);
    
    void DrawDiagram()
    
    {
    
        string xmlFormat = ReadXMLFormat();
    
        string decorator = AnalysisDependencyDecorator(ReadElements());
    
        /*
    
        *
    
        *Draw namespace, class node in PaintMap
    
        *
    
        */
    
    }
    

    细心的读者可能发现,在DrawDiagram实现中错误地引用了来自动态链接库工程DataProvider的类SourceElements和Api ReadElements,这个是违反我们当初的架构设计的,接下来,我们拭目以待,看看层验证能否帮我们发现这个隐秘的实现错误。

  4. 打开设计阶段创建的层图,从Solution Explorer中拖放工程节点DataFormat到层图中的层DataFormat上,进行工程与层对象之间的绑定,按照如上的方式对其他2个层进行类似的工程绑定,如图:

  5. 在层图上右击鼠标,点击菜单”Validate Architecture”进行层验证,稍等片刻后,发现在Error List中出现了如下的错误信息:

    正如我们所期盼的,这两条错误消息提示工程DrawDiagram中一个名为DrawDiagram函数调用了工程DataProvider中的函数ReadElements,发现了这个违反我们架构设计的实现错误。

  6. 双击错误消息1,转到错误的代码片段

    分析以上代码片段我们发现,应用程序DrawDiagram跨越库DataFormat直接访问了数据源DataProvider,违背了我们之前在设计阶段规划的设计思想。层验证帮助我们轻易地捕捉和定位到这种实现中容易发生,但不易发现的实现错误。

  7. 修正DrawDiagram的实现为:

     void DrawDiagram()
    
    {
    
        string xmlFormat = ReadXMLFormat();
    
        string decorator = AnalysisDependencyDecorator(xmlFormat);
    
    }
    
  8. 回到Layerdiagram,点击菜单”Validate Architecture”再做一次层验证,Error List中的错误消息立即消失。

可能有朋友会埋怨不停地切到层图上去做层验证过于繁琐,幸运的是,我们可以将层验证功能集成到编译过程中去,源代码编译的同时帮我们发现层验证的错误。设置过程非常简单,只需将建模工程的”Validate  Architecture”属性设为”True”即可,如图所示:


除此之外,我们还可以将层验证功能集成到Team Build中去,具体方法请查看文章Team Build与Layer验证
总结  
以上我通过一个案例简单地介绍了如何对C++工程做层验证。这其中包括如何将工程映射到层对象和通过层验证来确定是否代码实现违背了当初设计阶段所设定的工程间调用依赖关系。