级别: 中级 Matthew Scarpino (mattscar@yahoo.com), Java 开发人员, Eclipse Engineering, LLC
2006 年 11 月 23 日 Eclipse C/C++ Development Tooling(CDT)项目仍旧是最受欢迎的 Eclipse 可下载项目之一。但由于集成开发环境(IDE)的内容过于丰富,使得其中的代码难于理解和定制,因而,那些想要将 CDT 集成到其应用程序中的组织为此感到非常头疼。这个五部分的 “构建基于 CDT 的编辑器” 系列文章解释了 CDT 编辑器的运行原理,其中的第 1 部分介绍了 CDT 运行的数据结构。
在 CDT 这个问题上,我想我是被我的老板骗了,他说其中只不过是包含了 太多的功能。结果是,他只想要其中的源码编辑器功能 —— 语法着色功能、自动解析功能,特别是代码完成功能。他问我是否能够定制这个编辑器并将其添加到我们的富客户平台(RCP)应用程序中。“没问题”,我回答。“这是 Eclipse!又能有多难呢?”
结果却是非常难。目前,CDT V3.1 由 20 个插件组成,这些插件包含了超过 100 个包以及数千个接口和类。要找出究竟用哪种方法需要长久的努力。但最终我用一个工作编辑器让我的老板满意,我很高兴和大家分享我学到的东西。
 |
什么是 CDT?
CDT 无疑是可获得的最好的开放源码 C/C++ IDE。它除了具有完整的编辑、调试、索引功能之外,还为控制构建过程提供了难以置信的灵活性。如果您不想编写自己的 makefile,它会为您创建一个,并在整个项目过程中对其进行更新。您可以在 CDT 中访问 GNU Compiler Collection(GCC)及其他 GNU C/C++ 应用程序,但您也可以集成任何其他的开放源码或商业工具。
QNX Inc. 公司在 2002 年推出了 CDT,并成为主要的开发者和维护者。许多代码都是基于 Eclipse Java 开发工具(JDT)建立的,但 QNX(特别是 Doug Schaefer)添加了许多功能,包括了高级 C/C++ 解析、标准/托管的 make 项目,以及可配置的构建过程。如果您需要更多的信息,只需查看 Eclipse CDT 页面,或者参考 Doug Schaefer 的写得非常好的博客(参见 参考资料)。
|
|
精简版 CDT
本系列文章的目标是使您能够理解 CDT 编辑,从而构建可定制的 C/C++ 工具。为简化这一过程,我创建了一个完整 CDT 的精简版,我把它称作 Bare Bones CDT(BBCDT)(参见 下载 获得它的代码 )。我移除了绝大多数的 CDT 类,但保留了相同的命名约定 —— 部分原因是为了简化将 CDT 代码添加到 BBCDT 中的过程,另一部分是由于我很懒。
BBCDT 只包含两个插件:org.dworks.bbcdt.core(核心插件)和 org.dworks.bbcdt.ui(用户界面(UI)插件)。第一个插件提供了构成 CDT 模块的类和接口。第二个插件创建了 UI,其中包含了创建新源码文件和项目所需的 CEditor 及相关的类、向导和页面。
在第一篇文章中,BBCDT 并未完成任何值得关注的任务,只展示了基本 CDT 类是如何进行交互的。接下来的文章将解释 CDT 如何执行分块、语法着色、解析及代码完成功能。这些文章也将会详述有关 BBCDT 的内容,并确保这些功能被添加到了代码中。
CDT 模块的元素
通常的 Java™ 代码是通过 File 对象来访问文件和目录的。要在工作空间中对文件执行特定于 Eclipse 的操作,您却需要借助于资源应用编程接口(API)中的适配器:
IFile
IProject
IFolder
IWorkspaceRoot
除了提供对文件的访问外,这些 IResource 还提供了工作空间的结构。最顶层的资源是 IWorkspaceRoot,其子为 IProject。每个 IProject 都包含了一些 IFolder 和 IFile。到目前这止,一切都正常。
CDT 模块代表了一个适用于工作空间资源的不同却相似的适配器集合。图 1 显示了它们的继承层次结构。
图 1. CDT 模块元素的层次结构
图 2 显示了 CDT 是如何使用这些元素来组成其工作空间的。在顶端,ICModel 包含了一些 ICContainer 和 ICProject。这些 ICProject 包含了一些 ISourceRoot,这些 ISourceRoot 像 JDT 包一样运行,但却能够包含处于文件系统中不同位置的代码。为管理这些位置,ISourceRoot 包含了 ISourceEntry 实例,它们中的每个都持有一个到不同源码的 IPath。每个 ICElement 都包含一个用于访问其底层 IResource 的特定名称及方法。
图 2. CDT 工作空间结构
ITranslationUnit
ITranslationUnit 代表了单个 C/C++ 代码文件,是 CDT 模块中最重要的元素。每一个单元都包含了代表源代码文件不同方面的子文件,如 IInclude、IUsing 和 INamespace。(我将在后面讨论 CDT 解析时对 ITranslationUnit 结构进行更为详细的探讨。)
另外,每个 ITranslationUnit 持有单个的 IWorkingCopy,来管理未保存的代码修改。ITranslationUnit 和 IWorkingCopy 都将其内容存储到单独的 IBufferCache 实例中,该实例由一个 BufferManager 来分配,并像最近最少使用(LRU)算法缓存一样运行。文章包含了这个主题,但如果您对此感兴趣,可以浏览 org.dworks.bbcdt.internal.core.util 包。
信息对象
每个 ICModel、ICContainer、ICProject 和 ITranslationUnit 都有一个相应的信息对象(CModelInfo、CContainerInfo 等等)。这些信息对象包含了对其子元素的引用并提供对其子元素的访问。如果您想要一份 CModel 中 ICProject 的列表,需要调用 CModelInfo.getCProjects() 方法,此方法调用了 CModelInfo.getChildren() 方法。CDT 在元素首次打开时创建了一个信息对象,并在其关闭时对其进行资源清理。
CDT 模型的三个阶段
既然您已经了解了该模型的基本元素,那么,您还需要了解这些元素在 CDT 操作时是如何进行交互的。CDT 模型生命周期由三个重要步骤组成:初始创建、创建 CProject 以及在 CDT 编辑器中创建 TranslationUnit。
 |
CDT 模型元素及 IAdapter
正如组成 Eclipse 工作台的资源那样,CDT 模块元素(ICModel、ICProject、ITranslationUnit 等)全都是 IAdaptable 接口的后代。此接口允许 CDT 将其特定功能添加到现有的类中,而不必对这些类进行子类化。从编码的角度看,IAdaptable 类能通过使用其 getAdapter(类适配器)方法将此类转换成另一个类。
对每一个元素来说,CDT 提供了一个定义了完整方法的实现类(CModel、CProject、TranslationUnit 等)。然而,通过使用 IAdaptable 接口,可以在不失去 ICElement 现有功能的情况下创建您自己的 C/C++ 元素。那也就是说,新元素能够像 CDT 元素适应 Eclipse 工作空间中的资源一样适应 CDT 元素。
|
|
第 1 步:创建 CDT 模型
当工作台初始化了 CDT 插件,该核心插件就会创建一个单独的 CoreModel(不是 CModel)。此过程会依次创建两个重要的类: PathEntryManager 和 CModelManager。PathEntryManager 跟踪构建过程所需的目录,如包含目录或宏目录。在 CDT 模块中,每个目录的路径都保存在一个 SourceEntry 对象中。
但 CModelManager 类对于此次探讨来说至关重要。它在 CDT 中完成了诸多重要任务:
- 创建作为元素层次结构中顶层元素的
CModel
- 随着其资源被创建或删除,添加和移除新的模型元素
- 跟踪模型元素及其信息对象
- 持有一份有关
TranslationUnit 和作为其子元素的 WorkingCopy 的映射表。
- 存储一份新近打开元素的缓存
- 对资源内容类型的改变作出响应
- 对资源描述符的改变作出响应
最重要的功能是前两个。CModelManager 作为 CDT 的模型工厂,通过创建 CModel 对象开始运转。随后,它监听工作空间中资源的改变并在 CDT 元素受到影响时更新该模型。
一旦有资源被创建、删除或修改,Eclipse 工作空间就会创建一个存储有新旧层次结构的 IResourceDelta。管理器用 DeltaProcessor 对此进行分析,DeltaProcessor 决定了受影响的资源是否是 CElement。如果是,它会创建一个与此资源相应的新元素,并将其添加到父亲的子列表中。
BBCDT 的 CModelManager 功能执行除最后两项的所有上述功能。资源的内容类型是由在 plugin.xml 文件中列出的 <content-type> 所设置,并创建一个描述符文件用以描述新项目。
步骤 2:创建新的 CProject
CDT 为创建 CDT 资源提供了一个完整的可扩展向导结构。新项目向导 (New Project Wizard) 功能尤其强大,它允许您配置项目构建过程的每一个方面,从环境变量到源码索引。当您完成该向导时,该向导及其核心插件将执行四项主要任务:
- 在给定路径中创建并打开一个
IProject
- 构建一个
IProjectDescription 来保存 IProject 的通用信息。
- 构造一个
CDescriptor 来保存特定于 CDT 的信息。
- 根据内容类型,为
IProject 给出一个 CNature 或 CCNature
第一步和第二步对于创建任何 IProject 来说都是通用的。在第二项任务中,IProjectDescription 存储了工作台用于定义项目的信息。此数据在项目顶层目录的 .project 文件中以 XML 格式存在。两个重要的元素分别是,列出项目构建命令的 <buildSpec></buildSpec> 和标志着该项目具有不同于常规 IProject 特征的 <natures></natures>。
第三步中的 CDescriptor 与 IProjectDescription 类似。主要的区别在于,它包含了特定于 CDT 的数据,并将此信息插入到一个独立的 .cdtproject 文件中,该文件包含了用于构建过程的不同工具的配置文件,并为每个工具指定了配置参数。此文件使用与 IProjectDescription 类似的 XML 格式。清单 1 是一个 .cdtproject 配置文件声明的例子。
清单 1. .cdtproject 配置文件中的信息
<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerFileProfile">
<buildOutputProvider>
<openAction enabled="false" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="makefileGenerator">
<runAction arguments="-f ${project_name}_scd.mk" command="make"
useDefault="true"/>
<parser enabled="false"/>
</scannerInfoProvider>
</profile>
|
最后一步涉及将该项目标记为一个 C 或 C++ 项目。核心插件通过将 CNature 或 CCNature 添加到 IProjectDescription 中来完成这一任务。这些类自己并未完成什么值得关注的任务,但当 DeltaProcessor 意识到新项目的性质时,它会让CModelManager 创建一个与项目资源相对应的 CProject,并将其添加至 CDT 模型层次结构中。
步骤 3:创建新 TranslationUnit 和 WorkingCopy
不同于 CProject,TranslationUnit 并不会在其内含的 IFile 出现后立刻被构建。相反,它会在该文件被激活且编辑器打开后被构建。为解释这项任务是如何运行的,我首先要介绍 CDT 编辑过程的核心类:CEditor。
本质上,CEditor 是一个跟工作台适配,并从 IEditorInput 实例中获取内容的 StyledText 小部件。为了与模型-视图-控制器架构保持一致,Eclipse 文本编辑器 API 只使用此部件来提供视图外观。该编辑器的信息被封装到 IDocument 实例中。SourceViewer 扮演控制器的角色,CSourceViewer 管理对 CDT 文档的访问。
在核心插件中,plugin.xml 保存着不同类型的 C/C++ 文件的扩展名,并将每一个 contentType 和一个文件后缀联合起来。在 UI 插件中,plugin.xml 将 CEditor 和这些 contentType 关联起来。BBCDT 识别具有 .bbc、.bbcpp、.bbh 和 .bbhpp 后缀的文件。当您创建或双击一个这样的文件时,工作台会将 IFile 转换为 IEditorInput 并使用它来初始化 CEditor。
编辑器被初始化后,CDocumentProvider 会执行三项重要任务:
- 使用输入的
IFile 来创建 TranslationUnit 和 TranslationUnitInfo 对象。
- 用
IEditorInput 中的信息为编辑器创建一个 IDocument。
- 为
TranslationUnit 及其缓冲构造 WorkingCopy。
要创建新的 CDT 元素,提供者调用 CoreModel, CoreModel 调用 CModelManager。这些工作空间操作(如CreateWorkingCopyOperation 和 DestroyWorkingCopyOperation)都实现了 IWorkspaceRunnable 接口。它们异步运行,并使工作空间阻止了其他操作对资源修改的妨碍。
像 CProject 一样,每个 TranslationUnit 都具有自己的配置信息,比如注释。但提供者并不会创建一个独立的 CDescriptor。相反,它使数据保持在 IEditorInput 的 FileInfo 对象中。这样,下一次激活输入时,提供者不用从头开始来访问该单元信息。
运行 BBCDT
我将 BBCDT 作为一个插件或插件项目来提供。因为这个工具的主要目的是为以后的工作提供一个基础,所以我建议您引入这个项目,而不是将其功能添加到您的 Eclipse 安装中。
为使 BBCDT 尽可能简单,我省去了 PathEntryManager,这意味着该工具不使用 SourceEntry 对象,甚至是 SourceRoot 对象。因而,您必须将源文件(.bbc、.bbh、.bbcc 和 .bbhh 文件)直接添加到项目中。为创建 BBCDT 资源,我在 org.dworks.bbcdt.ui.wizards 包中创建了一组新的向导/页面类。要创建项目,请单击 File > New > Project 并选择 C 或 C++ 选项。要创建文件,请单击 New > Other 并选择 C 或 C++ 选项。图 3 显示了 BBCDT 项目及编辑器的外观。
图 3. BBCDT
结束语
如果您访问过 Java 代码中的 IFile 和 IProject,CDT 模型中的元素就不是什么问题了。CModel 包含了一些 CProject,这些 CProject 中包含了一些 CSourceRoot。每个 TranslationUnit 对应于单个的 C/C++ 源文件,因而编辑器通过访问并修改这些元素来与 CDT 模型进行交互。
功能越强大,其内容越复杂。这个编辑器也许太详细了,但您会在开始构建自己的 C/C++ 编辑器时发现它的用处。您可以通过研究 BBCDT 源代码来更好地理解 CDT 的运行机制。我建议您用添加、移除及修改来对其操作进行实验。
CDT 模型会在接下来的文章中变得更为清晰,因为我讨论了 TranslationUnit 和 WorkingCopy 与 CEditor 及其 UI 类进行交互的多种方式。第 2 部分会对 Document 如何管理事件、分块如何发生及这些分块如何提供语法着色进行深入介绍。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| Part 1 source code | os-ecl-cdt1.zip | 574KB | HTTP |
|---|
参考资料 学习
获得产品和技术
讨论
关于作者  | |  | Matthew Scarpino 是 Eclipse Engineering LLC 的一名项目经理兼 Java 开发人员。他是 SWT/JFace in Action 的首席作家,并对标准部件工具包(Standard Widget Toolkit,SWT)做出过一次较小的但却非常重要的贡献。他喜爱爱尔兰民间音乐、马拉松赛跑、William Blake 的诗歌以及图形化编辑框架(Graphical Editing Framework,GEF)。 |
对本文的评价
|