Wednesday, June 1, 2011

Exploratory Project Separation of Framework from Components

Exploratory Project Separation of Framework from Components


A number of years ago when I was working an aircraft project – from which this PC Windows based exploratory project was the inspiration – the project leader wanted a design where the components would be unknown to the framework.  I had been thinking that how to accomplish this had escaped me until now.  While laying in bed awaiting sleep a few weeks ago my mind cast about and thought of this design goal and wondered how it could be accomplished.
I was thinking that I had been stuck with my initial method of incorporating the components with the framework all along.  That is, to have one unique framework procedure, the Install procedure, where the various components to be included for a particular application were "with"ed by the separate Install Ada unit and their Install procedures were invoked to allow each of these components to install/register themselves with the framework.

While casting about for another method of allowing the components to register themselves dynamically without the framework needing any compile time modifications to change a framework unit I thought of a loadable database of the components and a "loader" unit that invoked the Install procedure of each of these components at the address specified by the database.

Then, a day or two later, it occurred to me that I could have the linker do it via a callback (or Import) from the framework Initialize / Install procedure to an application specific procedure that could call the application specific components for the particular application the same as now done for the C components.  That this out-of-framework component Install procedure could either do the installs as using the Ada "with" of the component packages or via the use of the Export pragma. 

However, in looking at the current design, I found that I had forgotten that I had already moved the installs in the exploratory project outside of the framework and into an application specific App package.  However, the App-Launch-Install did mix framework initializations with component installs somewhat. 

Therefore, it would seem, the only thing needed was to have a framework initialize separated from the component installs and to hook in the framework via either a "with" to invoke the framework initialize or the use of an Ada Import.  Perhaps with the later there would be the ability to have a C main that calls an Ada Export of the framework initialize.

Therefore, I adjusted the initialization somewhat.  The following diagram illustrates the general, including the framework, units and the application specific units.  The App Launch procedure is part application specific and part general as illustrated by its position in the diagram. 

The Ada main procedure, Appn, has common contents but a unique name such as App1.adb, App2.adb, App3.adb, etc but could be named to illustrate the purpose of the application.  This allows it to be specified as the main procedure for the GNAT GPS project that specifies the particular folder that contains the application specific source.  These folders are represented by the Appn of the Appn.Launch, etc of the application specific units.  In these folders the App package has a common name for any particular application and does not have a specific identifier – the n notation instead referring to the folder name.  Of course, since there is a different main procedure for each application, the Appn package for each application could have application specific names.  The two App-Initialize methods are named App-InitializeAda.adb and app-InitializeC.c to sort them together while keeping the "App" prefix of the App Ada package.


        <–––––––––general–––––––><–––––Application Specific––––––>
           +––––––+        +––––––––––+
           | Appn  |–––––––>|   Appn.   |
           | main |       /|  Launch  |
           +––––––+      / +––––––––––+
                        /    /       \
                     1 /   3/       2 \   <––– order of calls
        +––––––––––––+/    /       +–––––––––+    +–––––––––––––––+
        |  mC.Itf.   |    /        |  Appn.   |–––>|     Appn.       |
        | Initialize |   /         | Install |    | InitializeAda |
        +––––––––––––+  /          +–––––––––+    +–––––––––––––––+
                       /                |                |
                      /                 v                |
          +–––––––––+/           +–––––––––––––+         |
          | mC.Itf. |            |    appn_     |         |
          | Start   |            | InitializeC |         |
          +–––––––––+            +–––––––––––––+         |
                                        |                |
     rest of mC, Exec, etc              v                v
                                  +–––––––––––+    +–––––––––––+
                                  |  C com m  |    | Ada com m |
                                  |  Install  |    |  Install  |
                                  +–––––––––––+    +–––––––––––+

                                     application components

The above diagram is for InitializeAda as an Ada procedure that has "with" statements for the various Ada component package Install procedures.  To use Import and Export instead of "with" statements, it is easier to link the component packages into the build if there is a call to a C app_InitializeAda that invokes the Ada Install procedures as below.  That is, Install imports the C method to install the Ada components the same as it does the app_InitializeC method that initializes the C topics and installs the C components.


        <–––––––––general–––––––><–––––Application Specific––––––>
           +––––––+        +––––––––––+
           | Appn  |–––––––>|   Appn.   |
           | main |       /|  Launch  |
           +––––––+      / +––––––––––+
                        /    /       \
                     1 /   3/       2 \   <––– order of calls
        +––––––––––––+/    /       +–––––––––+    +–––––––––––––––+
        |  mC.Itf.   |    /        |  Appn.   |–––>|     appn_       |
        | Initialize |   /         | Install |    | InitializeAda |
        +––––––––––––+  /          +–––––––––+    +–––––––––––––––+
                       /                |                |
                      /                 v                |
          +–––––––––+/           +–––––––––––––+         |
          | mC.Itf. |            |    appn_     |         |
          | Start   |            | InitializeC |         |
          +–––––––––+            +–––––––––––––+         |
                                        |                |
     rest of mC, Exec, etc              v                v
                                  +–––––––––––+    +–––––––––––+
                                  |  C com m  |    | Ada com m |
                                  |  Install  |    |  Install  |
                                  +–––––––––––+    +–––––––––––+

                                     application components

The above design seems more symmetric that what I previously had although the first diagram still is representative of how the application began running.

App-Launch first does some common non framework related initialization and then calls mC-Itf-Initialize to initialize the framework.  It next calls App-Install to do the application specific initialize and install of the various components of the application.  Finally, App-Launch calls mC-Itf-Start that completes – the finalize of – the initialization phase and then starts the framework and component processes/threads.

Source Code samples:

Ada main:

 with App;

 procedure App3 is
 -- ++
 --| Overview:
 --|   This is the Ada main procedure for the "App 3" / AppC application of the
 --|   configuration of hosted applications.  It is the entry point of the
 --|   application and calls the application specific Launch procedure that
 --|   controls the running of the application and installs the application.
 --|   specific components.
 -- --

 begin -- App3

   --| Logic_Step:
   --|   Invoke the application specific procedure to launch the application.

   App.Launch;

  end App3;

App package spec and body:

 package App is
 --| Top of the Application Specific Code
 -- ++
 --| Overview:
 --|   This package is the interface to be called by the main procedure to
 --|   incorporate the application specific components into the build as
 --|   well as the common support procedures and the framework.
 -- --

   procedure Launch;
   --| Main application specific procedure
   -- ++
   --| Overview:
   --|   This procedure is the main procedure to include the application
   --|   specific components into the application.  It is to be called
   --|   directly by the main procedure that is launched by the operating
   --|   system.  It makes calls in a sequence that first initializes the
   --|   framework and launches the components of the framework, then
   --|   launches the application specific components, and finally
   --|   completes the framework initialization and starts the threads.
   -- --

 end App;

 package body App is
 --| Top of the Application Specific Code
 -- ++
 --| Overview:
 --|   This package is the interface to be called by the main procedure to
 --|   incorporate the application specific components into the build.
 -- --

   procedure Install;
   --| Initialize application
   -- ++
   --| Overview:
   --|   This procedure installs the application specific components.
   -- --

   procedure Install is separate;

   procedure Launch is separate;

 end App;

App-Launch separate:

 with Apps;
 with Console;
 with Console_Terminate;
 with Exception_Treatment;
 with Exec.Text_Log;
 with GNAT.Exception_Actions;
 with mC.Itf;
 with Text_IO;
 with Time_Reporting;
 with Unchecked_Conversion;
 with Win32;
 with Win32.WinCon;

 separate( App )

 procedure Launch is
 -- ++
 --| Overview:
 --|   This is the application main procedure for the "App 3"/AppC application
 --|   of the configuration of hosted applications to control the running of
 --|   the application.  It is to be called directly by the Ada main procedure
 --|   to act in its place; that is, the Ada main procedure is to be a very
 --|   thin layer that only calls this procedure to get the application
 --|   launched.
 --| Notes:
 --|   Part of this procedure is common code for any application and part of
 --|   it is specific to the particular application.
 -- --

   Application_Name
   --| Name of application
   : Apps.Application_Name_Type := ( others => ' ' );

   type PHandler_Routine
   --| Notes:
   --|   This special control handler access type because the attempt to use
   --|   that of Win32.Wincon directly fails to compile when attempt to assign
   --|   the console control handler to the object.
   is access function
             ( CtrlType : Win32.DWord
             ) return Win32.Bool;

   Handler
   --| Console Control Handler
   : PHandler_Routine := Console_Terminate.ConsoleCtrHandler'access;

   Success
   --| Whether SetConsoleCtrHandler succeeded
   : Win32.Bool;

   Win_Handler
   --| Console Control Handler to pass to SetConsoleCtrlHandler
   : Win32.Wincon.PHandler_Routine;

   function to_Access -- convert one access type to the other
   is new Unchecked_Conversion( Source => PHandler_Routine,
                                Target => Win32.Wincon.PHandler_Routine );

 begin -- Launch

   --| Logic_Step:
   --|   Enable default console output.

   Exec.Text_Log.Initialize;
   Console.Initialize;

   --| Logic_Step:
   --|   Register callback to output exception traceback information.

   GNAT.Exception_Actions.Register_Global_Action
   ( Action => Exception_Treatment.Exception_Callback'access );

   Console.Write("Ada App3/AppC Launch/Main", Console.Enable3);

   --| Logic_Step:
   --|   Register callback to be executed when the close button of the DOS
   --|   window is clicked in order to do cleanup.

   Win_Handler := to_Access( Handler );
   Success := Win32.WinCon.SetConsoleCtrlHandler
              ( HandlerRoutine => Win_Handler,
                Add            => 1 ); -- TRUE

   --| Logic_Step:
   --|   Initiate time reporting.

   Time_Reporting.Initialize;

   --| Logic_Step:
   --|   Initialize framework.

   Application_Name(1..5) := "App 3";

   mC.Itf.Initialize
   ( User_Name => Application_Name,
     User_Id   => 3 );

   --| Logic_Step:
   --|   Install component application components.

   Install;

   --| Logic_Step:
   --|   Complete framework startup and transfer control to the application.

   mC.Itf.Start;

   declare
     Terminate_Char : String(1..1);
   begin
     Text_IO.Put("Enter Character to Terminate: ");
     Text_IO.Get(Terminate_Char);
   end;

 exception

   when others =>
     Console.Write_Error("App3/AppC last chance exception handler");

 end Launch;

App-Install separate:

 separate( App )

 procedure Install is
 --| Install Application Components
 -- ++
 --| Overview:
 --|   This procedure performs the application specific initialization and
 --|   invokes the initialization/install procedures for the different kinds
 --|   of components (both Ada language components and foreign language
 --|   components).  These procedures will invoke the Install interface of
 --|   each component to allow them to install themselves as the components
 --|   to be executed by the current application.
 -- --

   procedure InitializeAda;                              -- Ada common
   pragma Import(C, InitializeAda, "app_InitializeAda"); --  initialize

   procedure InitializeC;                            -- C common
   pragma Import(C, InitializeC, "app_InitializeC"); --  initialize

 begin -- Install

   --| Logic_Step:
   --|   Install the Ada language components.

   InitializeAda;

   --| Logic_Step:
   --|   Install the C language components.

   InitializeC;

 end Install;

app-InitializeAda.c method:

 extern void app_InitializeAda()
 { // Install all Ada language topics and components.

   // declare Ada component Install procedures
   extern void com_Periodic_Install(); // Install component

   // Install each Ada component.
   com_Periodic_Install();

 } // end method app_InitializeAda

app-InitializeC.c method:

 #include <comC1.h>            // "spec"s for
 #include <comC2.h>            //   components
 #include <mT-Topic1ofN.h>     // "spec"s
 #include <mT-TopicNto1.h>     //   for
 #include <mT-TopicPeriodic.h> //   topics
 #include <mT-TopicContentFiltered.h>

 extern void app_InitializeC()
 { // Initialize all C language topics and components.

   // Initialize each topic's internal data.
   mT_Topic1ofN_Init();
   mT_TopicNto1_Init();
   mT_TopicPeriodic_Init();
   mT_TopicContentFiltered_Init();

   // Install each C component.
   comC1_Install();
   comC2_Install();

 } // end method app_InitializeC

Com_Periodic spec:

 package Com_Periodic is

   procedure Install;
   --| Interface to Install the Component
   -- ++
   --| Overview:
   --|   Interface procedure to install the component at power up.
   --|   It will identify itself along with its requirements to the
   --|   Message Controller.
   --|
   --|   This component is a debug component to run periodically and
   --|   issue a periodic topic to cause other to run.  Also, as one
   --|   the producers of various kinds of topics to check that they
   --|   are delivered successfully.
   -- --
   pragma Export(C, Install, "com_Periodic_Install" );

 end Com_Periodic;

The pragma Export allows the linking of the component to the application without a "with" using the following batch file.  ComC1-Install.c and ComC2-Install.c are similar to past posts.

Batch file:

I used the second method for the App3 exploratory example application that has the use of C components.  When there isn't an Ada InitializeAda unit that "with"s the components, the build of the Ada main (that is App3.adb) doesn't, of course, cause the components and the topic packages that they reference to be compiled.  Therefore, these units must be compiled separately.  This can be done by selecting them individually in GPS to be compiled or done in the batch file as below.  (Note:  So far I have failed to determine how to add the compiled units into a library as happens when the C code is compiled so the gnatlink line names the individual units.)

cd C:\Source\EP\ObjectC
c:\gnatpro\5.04a1\bin\gcc -c -IC:\Source\EP\UserApp\Try7
  -IC:\Source\EP\UserApp\Try7\AppC -IC:\Source\EP\Util
  -IC:\Win32Ada\src -aLC:\Source\EP\ObjectC
  C:\Source\EP\UserApp\Try7\AppC\com_periodic.adb
c:\gnatpro\5.04a1\bin\gcc -c -IC:\Source\EP\UserApp\Try7
  -IC:\Source\EP\Util -IC:\Win32Ada\src -aLC:\Source\EP\ObjectC
  C:\Source\EP\UserApp\Try7\mT-Topic_Periodic.adb
c:\gnatpro\5.04a1\bin\gcc -c -IC:\Source\EP\UserApp\Try7
  -IC:\Source\EP\Util -IC:\Win32Ada\src -aLC:\Source\EP\ObjectC
  C:\Source\EP\UserApp\Try7\mT-Topic_N_to_One_1.adb
cd C:\Source\EP\UserApp\Try7
c:\gnatpro\5.04a1\bin\gnatmake -c -aLC:\Source\EP\ObjectC -PApp3.gpr
cd C:\Source\EP\ObjectC
c:\gnatpro\5.04a1\bin\gnatbind -x App3.ali
c:\gnatpro\5.04a1\bin\gnatlink App3.ali com_periodic.o
  mT-Topic_Periodic.o mT-Topic_N_to_One_1.o libapp3.a -o App3.exe
pause


The indented lines above are really extensions of the previous line.

No comments: