3.2. Implementation of the CXXDriver in C++

  1. The next step is to get Babel to generate the skeleton code that we will fill in with the component's implementation. In the $STUDENT_SRC/components directory, type make .drivers.CXXDriver-cxx. The output should look something like this:

    ### Generating a cxx implementation for the drivers.CXXDriver component.
    /san/cca/cca-tools_gcc_intelF90_PIC/bin/babel -s cxx -R../xml_repository \
       -R/san/cca/cca-tools_gcc_intelF90_PIC/share/cca-spec-babel-0_7_8-babel-0.10.10/xml \
        -g -u -E -l -m drivers.CXXDriver. --suppress-timestamp drivers.CXXDriver
    Babel: Resolved symbol "drivers.CXXDriver"...
    touch .drivers.CXXDriver-cxx
    

    and in the $STUDENT_SRC/components/drivers/cxx directory, you should see the following files:

    drivers.CXXDriver.babel.make
    drivers_CXXDriver_Impl.cxx
    drivers_CXXDriver_Impl.hxx
    glue
    

    all of which were generated by Babel. (glue is actually a directory that contains a large number of generated files that Babel needs to do its job, but which you never need to modify.) The source code files that you will need to modify in order to implement the component are the so-called Impl files. For C++, both a source file (.cxx) and the corresponding header file (.hxx) are generated.

  2. In your editor, take a look through both $STUDENT_SRC/components/drivers/cxx/drivers_CXXDriver_Impl.cxx and $STUDENT_SRC/components/drivers/cxx/drivers_CXXDriver_Impl.hxx to familiarize yourself with their structure before you make any changes.

    1. Near the top of drivers_CXXDriver_Impl.hxx, you will see a group of include directives:

      ...
      //
      // Includes for all method dependencies.
      //
      #ifndef included_drivers_CXXDriver_hxx
      #include "drivers_CXXDriver.hxx"
      #endif
      ...
      

      Babel generates include directives for header files that are necessary to resolve the types used in the SIDL definition of the class you're implementing (in this case, in the $STUDENT_SRC/components/sidl/drivers.sidl file). It does not automatically generate include directives for interfaces you implement. You will have to add those and any other header files your implementation requires as part of the implementation process.

      When an automatically generated file is manually modified, there is always a danger that the modifications will be overwritten the next time the file is generated. Babel solves this with a concept called splicer blocks. These structured comments that appear to the compiler as regular comments, but are interpreted by Babel as having a special meaning. Babel will preserve code within a splicer block when the file is regenerated. Code outside splicer blocks will be overwritten. Most Babel-generated files contain numerous splicer blocks -- everywhere you might need to add something to the generated skeleton. Here is an example:

      ...
      // DO-NOT-DELETE splicer.begin(drivers.CXXDriver._includes)
      // Put additional includes or other arbitrary code here...
      // DO-NOT-DELETE splicer.end(drivers.CXXDriver._includes)
      ...
      

      Note that each splicer block has a name that is unique within the file, and has explicit beginning and end markers. In this case, the leading comment syntax is appropriate to C++, but of course files generated for other languages will have different ways of denoting comments.

    2. In the drivers_CXXDriver_Impl.cxx, You will see that Babel has already generated the signatures for all of the methods you need to implement, giving them appropriate C++-ized names, and has provided splicer blocks ready for you to fill in (with a default method body that throws a "method not implemented" exception). This includes both the go method inherited from the gov.cca.ports.GoPort definition, and the setServices method inherited from the gov.cca.Component definition. You will obviously need to delete the babel-generated code that throws the exception (or comment it out), and replace it with the code that actually implements the method under consideration.

3.2.1. The setServices Implementation

  1. We'll begin by implementing the setServices method in drivers_CXXDriver_Impl.cxx. Here is what the routine should look like (you'll need to type in the stuff marked up like this), along with some comments about different sections.

    ...
    /**
     * Method:  setServices[]
     */
    void
    drivers::CXXDriver_impl::setServices (
      /*in*/ ::gov::cca::Services services )
    throw (
      ::gov::cca::CCAException
    ){
      // DO-NOT-DELETE splicer.begin(drivers.CXXDriver.setServices)
      // insert implementation here
    
      frameworkServices = services; 1
    
      // Provide a Go port
      gov::cca::ports::GoPort gp = (*this); 2
            
      frameworkServices.addProvidesPort(gp, 2
                                        "GoPort",
                                        "gov.cca.ports.GoPort",
                                        frameworkServices.createTypeMap());
    
      // Use an IntegratorPort port
      frameworkServices.registerUsesPort ("IntegratorPort", 3
                                          "integrator.IntegratorPort", 
                                          frameworkServices.createTypeMap());
    
      // DO-NOT-DELETE splicer.end(drivers.CXXDriver.setServices)
    }
    ...
    

    1

    When the framework calls setServices, it passes in a gov.cca.Services object (in C++ gov::cca::Services) that we need to keep a copy of. Note that frameworkServices is not declared here. We will add a declaration for it to the .hxx file in the next step.

    2

    In order to register the ports that our component will provide with the framework, we use the addProvidesPort method of the gov.cca.Services interface. You can find this interface in the cca.sidl file (where you previously looked up gov.cca.Component and gov.cca.ports.GoPort) in order to check its signature, which is:

    ...
    void addProvidesPort(in gov.cca.Port inPort,
                         in string portName,
                         in string type,
                         in gov.cca.TypeMap properties )
           throws gov.cca.CCAException ;
    ...
    

    (Of course we're actually calling the C++ version of the interface.)

    The first argument is the object that actually provides the port. The way we wrote the SIDL, the drivers.CXXDriver class provides the port, and since we're writing a method within this class, the babel C++ binding allows the enclosing object to be accessed through the standard C++ *this mechanism (cast to the appropriate type).

    The second and third arguments are a local name for the port, which must be unique within the component, and a type, which should be globally unique. If the actual types of the ports don't match between user and provider, it will cause a failed cast or possibly a segmentation fault. The string type here is a convenience to the user, giving a human-readable way to identify the type of the port that can be presented in the framework's user interface. By convention, the SIDL interface name for the port is used for the type.

    The final argument is a gov.cca.TypeMap. This is a CCA-defined type that provides a simple hash table that can be used to associate properties with a provides port. In practice, it is rarely used, but must be present.

    3

    We must also tell the framework which ports we expect to use from other components. Looking in cca.sidl, we find that the method's signature is:

    ...
    void registerUsesPort(in string portName,
                          in string type,
                          in gov.cca.TypeMap properties ) 
         throws gov.cca.CCAException ;
    ...
    

    The first and second arguments are a local name for the port, following the same rules and conventions as in the addProvidesPort invocation above. The final argument is, once again, a gov.cca.TypeMap, like addProvidesPort.

  2. The header file also requires a couple of additions. First, let's take care of declaring frameworkServices as a private variable belonging to the drivers::CXXDriver class.

    Edit $STUDENT_SRC/components/drivers/cxx/drivers_CXXDriver_Impl.hxx and add the following:

    ...
      /**
       * Symbol "drivers.CXXDriver" (version 1.0)
       */
      class CXXDriver_impl : public virtual ::drivers::CXXDriver
      // DO-NOT-DELETE splicer.begin(drivers.CXXDriver._inherits)
      // Insert-Code-Here {drivers.CXXDriver._inherits} (optional inheritance here)
      // DO-NOT-DELETE splicer.end(drivers.CXXDriver._inherits)
      {
      // All data marked protected will be accessable by
      // descendant Impl classes
      protected:
    
        bool _wrapped;
    
    
        // DO-NOT-DELETE splicer.begin(drivers.CXXDriver._implementation)
        // Insert-Code-Here {drivers.CXXDriver._implementation} (additional details)
    
        ::gov::cca::Services frameworkServices;
    
        // DO-NOT-DELETE splicer.end(drivers.CXXDriver._implementation)
    ...
    

  3. We also need to add the include directives for the header files for the classes we inherit from. (For technical reasons, Babel does not insert these automatically when it generates the file.)

    ...
    // DO-NOT-DELETE splicer.begin(drivers.CXXDriver._includes)
    // Put additional includes or other arbitrary code here...
    
    #include "integrator_IntegratorPort.hxx"
    #include "gov_cca_ports_GoPort.hxx"
    
    // DO-NOT-DELETE splicer.end(drivers.CXXDriver._includes)
    ...
    

    Note that in naming files, Babel translates periods (“.”) in the SIDL to underscores (“_”).

  4. Now, although the component is not complete, it is a good idea to check that it compiles correctly with the code you've added so far.

    First, change directories to $STUDENT_SRC/components and run make drivers. This will install Makefile and MakeIncl.user files in $STUDENT_SRC/components/drivers/cxx.

    Then, change directories to $STUDENT_SRC/components/drivers/cxx and run make. If you get any compiler errors, you should fix them before going on.

3.2.2. The go Implementation

  1. Once again, edit $STUDENT_SRC/components/drivers/cxx/drivers_CXXDriver_Impl.cxx and add the implementation of the go method:

    ...
    /**
     * Method:  go[]
     */
    int32_t
    drivers::CXXDriver_impl::go ()
    throw ()
    
    {
      // DO-NOT-DELETE splicer.begin(drivers.CXXDriver.go)
      // insert implementation here
    
      double value; 1
      int count = 100000;
      double lowerBound = 0.0, upperBound = 1.0;
    
      ::integrator::IntegratorPort integrator; 2
    
      // get the port ...
      gov::cca::Port port = frameworkServices.getPort("IntegratorPort");
      integrator = babel_cast< ::integrator::IntegratorPort >(port); 2
    
      if(integrator._is_nil()) { 3
        fprintf(stdout, "drivers.CXXDriver not connected\n");
        frameworkServices.releasePort("IntegratorPort");
        return -1;
      }
        // operate on the port
        value = integrator.integrate (lowerBound, upperBound, count); 4
    
        fprintf(stdout,"Value = %lf\n", value);
        fflush(stdout);
    
      // release the port.
      frameworkServices.releasePort("IntegratorPort"); 5
      return 0; 6
    
      // DO-NOT-DELETE splicer.end(drivers.CXXDriver.go)
    }
    ...
    

    1

    Setup the parameters with which to call the integrator.

    2

    In this section we get a handle to the particular integrator.IntegratorPort that the driver's uses port has been connected to. First, we have to declare a variable of the appropriate type (::integrator::IntegratorPort is the C++ translation of the SIDL integrator.IntegratorPort, defined in $STUDENT_SRC/ports/sidl/integrator.sidl. Then, we invoke the getPort on our frameworkServices object. The argument to this method is the local name we used in the registerUsesPort invocation. Note the use of the babel_cast method to cast from the gov::cca::Port to its sub-type ::integrator::IntegratorPort.

    3

    This code checks that the getPort worked, and returned a valid port. If the getPort fails, or if the driver's uses port has not been connected to an appropriate provider, then getPort will return a nil port object. The _is_nil method is automatically available on all SIDL objects. Because the driver can't do anything without being properly connected to an integrator, the response to getPort failing is to abort by returning a non-zero value.

    [Note] Note

    getPort returning nil need not be treated as a fatal error in all cases. For example, a component may be designed so that certain ports are optional -- to be used if present, but to be ignored if not. Another possibility is that the component may be able to accomplish the same thing through several different ports, so that only one of a given group needs to be connected.

    4

    Here we actually call the integrate method on the integrator port we just got a handle for. The signature of the integrate method is defined in $STUDENT_SRC/ports/sidl/integrator.sidl.

    5

    Finally, once we're done using the port, we call releasePort.

    6

    It is considered impolite for a component to call exit because it will bring down the entire application, and possibly crash the framework. Instead, components should simply return.

  2. Congratulations, you have completed the implementation of the CXXDriver! To check your work, run make in $STUDENT_SRC/components/drivers/cxx. If you get any compiler errors, you should fix them before going on.

  3. At this point, it is a good idea to go up to $STUDENT_SRC and run make to insure that anything else which might depend on the existence of the new drivers.CXXDriver component gets built too.

The next step is to test your new driver component, in Section 3.5, “Using Your New Component”.