Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Writing Plug-Ins for the Eclipse C/C++ Development Tools Project


September, 2004: Writing Plug-Ins for the Eclipse C/C++ Development Tools Project

A platform for building specialized C/C++ tooling

Doug is a senior software developer for the Rational division of IBM and Sebastien is an operating systems manager for QNX Software Systems. Sebastien is also the project leader for the Eclipse CDT. They can be contacted at dschaefeca.ibm.com and sebastienqnx.com, respectively.


Like a growing number of developers, we've come to appreciate the many useful features found in the Eclipse Java development environment (JDT). These features include a content-assist facility that offers highly accurate code completion, a structured search that uses semantic information about a program to accurately locate definitions and references, and a refactoring engine that can improve the structure of code without changing its behavior.

Together, these features have raised our Java productivity. That said, we also long for an environment that can bring the same benefits to our C++ chores. As it turns out, the Eclipse CDT Project (http://www .eclipse.org/cdt/) was established to do just that—provide a fully functional C/C++ IDE for the Eclipse platform. The CDT Project got started when a team of developers at QNX Software Systems realized the power and extensibility of the JDT, and decided to bring similar functionality to the C/C++ community. As with other Eclipse projects, the CDT is written mostly in Java and runs on a range of development hosts, including Windows, Linux, Solaris, and QNX Neutrino platforms. It also supports a variety of target processors and operating systems.

The CDT is, in essence, a platform for building specialized C/C++ tooling (see Figure 1). It adds various extensions to Eclipse's many XML-based extension points, letting tool creators adapt the CDT's edit/build/debug/launch capabilities to their particular toolchains, code-generation tools, and code-analysis products. As a result, users can choose from a basket of tool offerings that can be customized to address particular development needs.

How, exactly do you go about customizing the CDT? To illustrate, we create an Eclipse plug-in that takes advantage of the CDT's built-in parser and code model to provide a qualitative assessment of the developer's coding style.

Enforcing Encapsulation

Writing object-oriented applications has become second nature to many developers, thanks, in large part, to C++. Nonetheless, an early advantage of C++ continues to haunt the object-oriented purist—its compatibility with C. While this compatibility has eased the adoption of object-oriented techniques in the C world, it also lets programmers get away with sloppy object-oriented style. For instance, many developers let data members of a class be accessible, even though this violates the OO principle of encapsulation. To help you avoid such stylistic improprieties, our plug-in marks declarations of public data members as errors.

To illustrate several concepts, we implement our public data checker as an Eclipse builder. A builder is the object provided by any plug-in that participates in the Eclipse build framework, and is invoked when files are saved (provided that automatic builds are turned on) or when users select either Build or Rebuild from the Project menu. At first, a builder may not seem the obvious choice to implement our checker. However, we want to emulate compile-time checking, and by creating a plug-in invoked at build time, we can achieve that goal.

Creating the Plug-In

To create the plug-in, we use the Eclipse Plug-in Development Environment (PDE), which provides a number of wizards and editors (http://www.eclipse.org/pde/). First, launch the PDE's new project wizard and select the new Plug-in Project template in the Plug-in Development section. The wizard asks you for the name of the plug-in—call it "org.eclipse.cdt.stylist"—and lets you modify options that configure code generated for the plug-in. To keep things simple, click Finish to accept the defaults for the remaining options.

The wizard generates several files including plugin.xml, the manifest file that tells Eclipse about the new plug-in (see Listing One). Using this file, you declare where components of your plug-in plug into the Eclipse platform. Again, the Eclipse interconnection mechanism is based on extension points (well-known interfaces defined in XML) to which the plug-in contributes extensions. This non-Java declaration offers a significant benefit: The plug-in is loaded only when it's needed. Given that an Eclipse installation can have hundreds of plug-ins, this feature can greatly improve startup times. To allow access to features in the Eclipse resource plug-in and CDT core, ensure the plug-ins org.eclipse.core.resources and or.eclipse.cdt.core are in the dependencies list.

Creating Menu Items

Because plug-ins are loaded on demand, you need a way to get the plug-in rolling. In the plugin.xml editor (on the Extensions pane), you see an Add button. This button launches a wizard that allows you to create Eclipse components. Select the Extension Wizards pane, then select the "Popup Menu" template. Specify where you want the menu item to appear and what names to show on it (see Figure 2).

To have the menu item appear on CDT projects only, declare org.eclipse.cdt.core .model.ICProject as the target object class. That way, the item only shows up in views where objects of this type are shown; that is, projects displayed in the C/C++ Projects view of the C/C++ Projects Perspective. For the class name, use EnablePublicDataChecking (see Listing Two).

Once you've defined the menu item, feel free to test it out. The PDE provides a test environment called the "Runtime Workbench" that launches a new copy of Eclipse with the plug-ins in your workspace enabled. You can do this now to create a CDT project and see the menu item active on that project. The PDE generates a skeletal implementation of the menu item that, when selected, pops up a message dialog, proving that things are hooked up.

Creating the Builder

Next, create the builder. Again, this is done by creating an extension point. Since the PDE does not provide a template for creating builders, you need to use a schema-based generator. Click the Add button again and, in the Extension Points pane, select the org.eclipse.core.resource.builders extension and click Finish. In the Element Details Section of the editor, set the ID and name as in Figure 3. Use the Extensions editor to add a new builder and run element to the extension. In the Details section for the run element, you see a property named class. Click the class hyperlink and you see another wizard that lets you generate a new Java class. This class provides a skeletal implementation for the builder, so let's call it PublicDataChecker (see Listing Three).

Once you've defined much of the plug-in's structure, you can start hooking things together. In the PublicDataChecker class, add a static method, addBuilder, which adds the builder to a project. To do this, take the ID string you assigned to the builder extension and add it to the list of commands that uses the project. The ID string allows Eclipse to refrain from loading the plug-in until you load a project associated with the plug-in.

At this point, you need to implement the menu item in the EnablePublicDataChecking class. Start by implementing the selectionChanged method to capture the list of selected items. Then, implement the run method to call the PublicDataChecker's addBuilder method to add our builder to the selected project.

To test whether the menu item works correctly, you can use the Eclipse debugger to step through it. But if you're in a hurry, simply check the list of builders applied to the project, as shown in the project properties. You should see the builder in the list of builders, bearing the name you gave to the extension.

Implementing Checking Behavior

Now that the builder is hooked up, you can think about how its behavior should be implemented. Essentially, you want the builder to walk through each file that has changed since you last checked, or through all files on a project rebuild, and look for classes with public data members. When the builder finds one, it alerts users by adding a line to the Problems view. By double-clicking on that line, users can jump to where the definition is located in the file.

To add information to the Problems view, you must define a marker. This is a tag that records a file offset that can be used for bookmarks or reporting errors. To define the marker, create another extension, this time to the org.eclipse.core.resources.marker extension point. Eclipse markers are arranged in a somewhat complex inheritance hierarchy. To have the marker placed in the Problems list and to show in the C/C++ Project View, subclass the marker from org.eclipse.cdt.core.problem. Doing this also lets the marker persist across Eclipse sessions.

With the marker defined, you can focus on implementing the builder. The Eclipse build mechanism provides the builder with a handle to the project you need to build and, for incremental builds, a list of files that have changed since the last build. To implement the logic for checking member visibility, however, you need information about code contained in the files. To access this information, you can take advantage of the parser provided by the CDT and of the CDT's core interface—the C code model. Given a file, the CDT creates an instance of the code model for that file and returns an ITranslationUnit element. From that point, you can walk the code model, find the classes and member variables, and do your checking.

To implement checking in the builder class, start by implementing the checkClass method. This method takes both the IFile object, which represents a file, and the IStructure object, which represents a class in that file. In the checkClass method, create an ICElementVisitor that visits the class and its children, looking for data members that have public visibility. If the ICElementVisitor finds such a member, it creates an instance of the marker and attaches it to the file object.

Next, add the checkFile method, which finds all of the classes in a file and calls checkClass. This method calls the CDT to create an ITranslationUnit object that contains the results of the parse on that file. Any existing markers you've put on the file are removed, and another ICElementVisitor is created to walk the ICElement tree, looking for the classes declared in the file.

Finally, you need to implement the build method that provides the main entry point to the builder. You can choose from two main types of build, full or incremental. On full builds, we walk the entire project resource hierarchy and call checkFile on all header files; that is, files ending in .h. On incremental builds, Eclipse provides a resource delta that describes the changes since the last build. In this case, we call checkFile on all header files that appear as new or changed in the delta list.

Testing and Running

That's all there is to it (see Figure 4). You can now test the builder by creating a C++ project, creating a class and a method, and playing with the visibility on that method. Just one potential problem: To run the checker, you must first build after each change. If that becomes annoying, you could always implement an alternative design. For instance, you could implement a "listener" to the Eclipse resource management system and automatically run the checking each time a file is saved. Or, you could create a menu item and force the user to manually invoke the checking.

Conclusion

This example just scratches the surface of what you could do with the APIs offered by the CDT, including APIs that let tool vendors seamlessly integrate their tools. Over time, the CDT's code model will become more complete and offer the ability to make changes to the code. In addition, the CDT team is developing a refactoring engine that will let you automate complex code changes across many files in a project—much like the JDT refactoring engine does today.

With the dual-headed thrust of being both a multiplatform C/C++ IDE and a platform for C/C++ integrating tools, the CDT is building momentum in the industry. C and C++ development is still going strong, and the CDT is becoming an invaluable tool in the C/C++ developer's tool belt.

DDJ



Listing One

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.0"?>
<plugin
   id="org.eclipse.cdt.stylist"
   name="Stylist Plug-in"
   version="1.0.0"
   provider-name=""
   class="org.eclipse.cdt.stylist.StylistPlugin">

   <runtime>
      <library name="stylist.jar">
         <export name="*"/>
      </library>
   </runtime>
   <requires>
      <import plugin="org.eclipse.ui"/>
      <import plugin="org.eclipse.core.resources"/>
      <import plugin="org.eclipse.core.runtime.compatibility"/>
      <import plugin="org.eclipse.cdt.core"/>
   </requires>

   <extension
         id="publicDataChecker"
         name="Public Data Checker"
         point="org.eclipse.core.resources.builders">
      <builder>
         <run
               class="org.eclipse.cdt.stylist.PublicDataChecker">
         </run>
      </builder>
   </extension>
   <extension
         point="org.eclipse.ui.popupMenus">
      <objectContribution
            objectClass="org.eclipse.cdt.core.model.ICProject"
            id="org.eclipse.cdt.stylist.contribution1">
         <menu
               label="CDT Stylist"
               path="additions"
               id="org.eclipse.cdt.stylist.menu1">
            <separator
                  name="group1">
            </separator>
         </menu>
         <action
               label="Enable Public Data Checking"
               class="org.eclipse.cdt.stylist.popup.actions.
                                                    EnablePublicDataChecking"
               menubarPath="org.eclipse.cdt.stylist.menu1/group1"
               enablesFor="+"
               id="org.eclipse.cdt.stylist.newAction">
         </action>
      </objectContribution>
   </extension>
   <extension
         id="publicDataProblem"
         name="Illegal public data"
         point="org.eclipse.core.resources.markers">
      <super
            type="org.eclipse.core.resources.problemmarker">
      </super>
   </extension>
</plugin>
Back to article


Listing Two
package org.eclipse.cdt.stylist.popup.actions;
import ...
public class EnablePublicDataChecking implements IObjectActionDelegate {

    /** Constructor for Action1. */
    public EnablePublicDataChecking() {
        super();
    }
    /** see IObjectActionDelegate#setActivePart(IAction,IWorkbenchPart) */
    public void setActivePart(IAction action, IWorkbenchPart targetPart) {
    }
    IStructuredSelection selection;
    /** see IActionDelegate#run(IAction) */
    public void run(IAction action) {
        if (selection == null)
            return;
        Iterator iter = selection.iterator();
        while (iter.hasNext())
            try {
                PublicDataChecker.addBuilder(((ICProject)iter.next()).
                                                              getProject());
                } catch (CoreException e) {
                // Report the error
            }
    }

    /**
     * see IActionDelegate#selectionChanged(IAction, ISelection)
     */
    public void selectionChanged(IAction action, ISelection selection) {
        if (selection instanceof IStructuredSelection)
            this.selection = (IStructuredSelection)selection;
        else
            this.selection = null;
   }

}
Back to article


Listing Three
package org.eclipse.cdt.stylist;
import ... 

/** see IncrementalProjectBuilder  */
public class PublicDataChecker extends IncrementalProjectBuilder {
    /**
     *
     */
    public PublicDataChecker() {
    }
    // These ids must match the ids in their respective extensions
    private static final String ID = StylistPlugin.ID + ".publicDataChecker";
    private static final String PROBLEM_ID = StylistPlugin.ID + 
                                                       ".publicDataProblem";
    private static final String PROBLEM_MSG = "publicDataProblem";
    
    public static void addBuilder(IProject project) throws CoreException {
        IProjectDescription desc = project.getDescription();
        ICommand[] builders = desc.getBuildSpec();
        
        for (int i = 0; i <builders.length; ++i)
            if (ID.equals(builders[i].getBuilderName()))
                return;
        ICommand[] newBuilders = new ICommand[builders.length + 1];
        ICommand builder = desc.newCommand();
        builder.setBuilderName(ID);
        
        System.arraycopy(builders, 0, newBuilders, 0, builders.length);
        newBuilders[builders.length] = builder;
        desc.setBuildSpec(newBuilders);
        project.setDescription(desc, null);
    }
    /** see IncrementalProjectBuilder#build */
    protected IProject [] build(int kind, Map args, 
                          IProgressMonitor monitor) throws CoreException {
        IProject project = getProject();
        if (kind == FULL_BUILD) {
            project.accept(new IResourceProxyVisitor() {
                public boolean visit(IResourceProxy proxy) 
                                                 throws CoreException {
                    if (proxy.getType() == 
                            IResource.FILE && proxy.getName().endsWith(".h"))
                        checkFile((IFile)proxy.requestResource());
                    return true;
                }
            }, 0);
        } else {
            IResourceDelta delta = getDelta(project);
            delta.accept(new IResourceDeltaVisitor() {
                public boolean visit(IResourceDelta delta) 
                                             throws CoreException {
                    if (delta.getFlags() == IResourceDelta.CONTENT
                            && delta.getKind() != IResourceDelta.REMOVED)
                   {
                        IResource resource = delta.getResource();
                        if (resource.getType() ==
                                IResource.FILE && 
                                           resource.getName().endsWith(".h"))
                            checkFile((IFile)resource);
                    }
                    return true;
                }
            } );
        }
        return null;
    }
    private void checkFile(final IFile file) throws CoreException {
        ICElement cfile = CModelManager.getDefault().create(file, null);
        if (!(cfile instanceof ITranslationUnit))
            return;
        // Remove existing markers
        file.deleteMarkers(PROBLEM_ID, false, IResource.DEPTH_ZERO);
        
        // Look for the classes in this file
        ITranslationUnit unit = (ITranslationUnit)cfile;
        unit.accept(new ICElementVisitor(){
            public boolean visit(ICElement element) throws CoreException {
                if (element.getElementType() == ICElement.C_CLASS) {
                    checkClass(file, (IStructure)element);
                    // return false since checkClass already visited the kids
                    return false;
                }
                return true;
            }
        });
    }
    private void checkClass(final IFile file, IStructure cls) 
                                                  throws CoreException {
        cls.accept(new ICElementVisitor() {
            public boolean visit(ICElement element) throws CoreException {
                if (element instanceof IField) {
                    IField field = (IField)element;
                    if (field.getVisibility() == ASTAccessVisibility.PUBLIC) {
                        IMarker marker = file.createMarker(PROBLEM_ID);
                        marker.setAttribute(IMarker.SEVERITY, 
                                                   IMarker.SEVERITY_ERROR);
                        marker.setAttribute(IMarker.MESSAGE,
                            StylistPlugin.getResourceString(PROBLEM_MSG));
                        ISourceRange range = field.getSourceRange();
                        int start = range.getIdStartPos();
                        marker.setAttribute(IMarker.CHAR_START, start);
                        marker.setAttribute(IMarker.CHAR_END, start + 
                                                       range.getIdLength());
                        marker.setAttribute(IMarker.LINE_NUMBER, 
                                                       range.getStartLine());
                    }
                }
                return true;
            }
        });
    }
    
}
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.