Thursday, March 14, 2019

Pseudo Visual Compiler using Interface to Ada as the OFP - Part 6



This the last post of this sequence.  A C# application and an Ada application now communicate over Microsoft Named Pipes which was the original goal.  (See Pseudo Visual Compiler using Interface to Ada as the OFP of January 8, 2019 as well as Pseudo Visual Compiler of Decades Ago of Dec 2018).

General Discussion

The C# application (App1) has a Windows Form to display a partial CDU/MCDU of an aircraft.  When a key is clicked, imitating a key push on an aircraft MCDU, the key is sent to the Ada application (App2) imitating a tiny portion of an aircraft OFP.  Rather than actually pretending to imitate the selection of new MCDU pages or performing tasks associated with Line Select Keys (LSKs) for a particular displayed page, the Ada application just echoes the particular keys back to App1 and other messages for LSKs and other keys (such as PREV, UP, etc) that would require actually code to decipher. 

 App1 then changes the MCDU page title in response to the returned message for keys such as DIR, INIT, etc that refer to an actual OFP page.  In either case, the returned message is displayed in a text box beside LSKL2.  Therefore, there is feedback to the "operator".


Before this can happen the two applications must be fully connected.  That is, before App2 can treat keys it has to know that there is a consumer of its response message which just happens to be located in App1.  (That is, with the message delivery framework, the topic consumer components can be located in any of the applications of the configuration.  In this case there are only the two applications.)  Therefore, that the Register Request message that contains the components of App1 that have registered to consume messages has to have been received by App2 and its Register Response message returned to App1 informing App1 that its topic consumer components have been treated.  Consequently OFP CHANGEPAGE topics will be sent by App2 to App1 since it has a component that will consume the message.  (Of course, App2 could have had such a component as well.  In which case the topic would have been delivered to the App2 component as well as the App1 component.)

To inform the operator that the initialization has completed, a Windows MessageBox is displayed.  The operator can then indicate that it has been seen (selecting the OK button) and then proceed to click a key on the MCDU.  Depending upon the speed of the PC and the like, the feedback will appear in the text box and, if applicable, the page title will change.

For debugging purposes, the Ada App2 application outputs Text_IO messages that show particular progress.  If the GNAT debugger is used, this will scroll past in its console window.  Otherwise, the application can be run in a DOS window and the console output piped to a file to be reviewed upon termination.  Likewise, for the C# App1 application console output can be captured if desired.  Since App1 uses a Windows form, the C# Console output is worthless (that is, no console window is produced) so a ConsoleOut class has been implemented to output the text to a particular file where it can be reviewed upon termination.  Therefore, at startup, the operator is prompted as to whether this output should be retained along with notification of where it will be located.  (This is dependent upon the Windows folder structure being the same as that used to create the application.)


Figure CDU-1

 Figure Ok-Popup

App1 initially displays the partial CDU/MCDU as in Figure CDU-1.  When the two applications are fully connected the popup MessageBox in Figure Ok-Popup is displayed to inform the user that the key buttons can now be clicked.  Figure CDU-2 then displays what the happens after the F-PLAN key is clicked.  For such a key click the OFP KEYPUSH topic message is sent to App2 with data corresponding to the key and App2 receives the message and responds with the OFP CHANGEPAGE topic.  It either echoes the key or if not DIR, PROG, PERF, INIT, DATA, or F-PLAN (FLIGHTPLAN) informational text to be displayed in the text box as shown in Figure CDU-3.



When the key is DIR, PROG, PERF, INIT, DATA, or F-PLAN the page title is changed to reflect the selected page.  When a different key is clicked, the returned informational text of the Data portion of the CHANGEPAGE topic message is displayed in the text box as shown in Figure CDU-3 where LSK R5 (the fifth key down on the right hand side of MCDU screen) had been clicked.


Figure CDU-2



The new code of the C# application 1 and the Ada application 2 will be included with this post.  The current code of the entire project will be stored on GitHub in sw-engr/rpi.




Figure CDU-3

Problems Encountered

The major problem encountered was getting the C# and Ada applications to connect via the named pipes.  [Note that although the pipes are created as full-duplex (bi-directional) and have been in each of my experimental projects, I have always used them as if there were separate server and client pipes.  And named them for the direction of data flow.  Such as 1to2 for one pipe and 2to1 for the other along with any additional naming required as is the case for Ada where \\.\pipe\ precedes the 1to2 or 2to1 of this two application configuration.]

When communicating between C# applications no fussing was required – I just had to follow Microsoft instructions.  When communicating between Ada applications, the two variations of the server and client pipe open methods were necessary.  For the server
    Handle :=
      ExecItf.CreateFile
      ( FileName            => Addr_to_LPSCSTR(Comm.Pipe(Index)(Transmit).Name'address),
        DesiredAccess       => ExecItf.GENERIC_READ or ExecItf.GENERIC_WRITE,
        ShareMode           => 0,    -- no sharing
        SecurityAttributes  => null, -- default security attributes
        CreationDisposition => ExecItf.OPEN_EXISTING,
        FlagsAndAttributes  => 0,    -- default attributes
        TemplateFile        => System.Null_Address ); -- no template file
and for the client
      Handle :=
        ExecItf.CreateNamedPipe
        ( Name               => Addr_to_LPSCSTR(Comm.Pipe(Index)(Receive).Name'address),
          OpenMode           => ExecItf.PIPE_ACCESS_DUPLEX,        --16#00000003#,
          PipeMode           => ExecItf.PIPE_TYPE_MESSAGE     or   --16#00000004#
                                ExecItf.PIPE_READMODE_MESSAGE or   --2
                                ExecItf.PIPE_WAIT,                 --0
          MaxInstances       => ExecItf.PIPE_UNLIMITED_INSTANCES,  --255
          OutBufferSize      => Message_Size, -- output buffer size
          InBufferSize       => Message_Size, -- input buffer size
          DefaultTimeOut     => 0,            -- client timeout in msec
          SecurityAttributes => null );       -- default priority attributes
Where the \\.\pipe\MtoN and NtoM names were located in the Comm.Pipe structures of the initial project.  (Index allowed for multiple different pairs of applications.)

However, for communication between Ada (via its C interface in ExecItf) and C# this didn't work and I was off wild goose hunting.  At the beginning either the C# application would give an exception such as "UnauthorizedAccessException was unhandled" with hints such as
  Make sure you have sufficient privileges to access this resource
  If you are attempting to access a file, make sure it is not ReadOnly
Another exception was "Access to the path is denied."  Or else the Ada application would return null for the handle.  Due to the not sufficient privileges etc I got off on pursuits such as adding security attributes and such to the C# pipe open and this led to losing sight of the goal with changes to both the C# and Ada pipe opens.  I was going about it blind since who knows what Microsoft was doing in their System calls that involved the named pipes and how they were creating the pipe path names.

After chasing my tail for a few days it occurred to me that the Ada to C# interface would be similar to C to C# (since I had been using the C interface of GNAT Ada to interface with Windows) or C++ to C#.  So I did internet searches for named pipes connecting between C++ and C# applications.  This led to finding Microsoft "documents" for "How to: Use Named Pipes for Network Interprocess Communication" (C#); "C++ named-pipe server for IPC"; "Named Pipe Client"; and "Named Pipe Server Using Overlapped I/O" (the later three for C++). 

The C++ documents reversed the server and client CreateFile and CreateNamedPipe opens that I had been using in Ada to Ada communications.  That is, CreateNamedPipe for the server and CreateFile for the client.  Once I made that change and remembered to add the trailing ASCII NUL to the Ada pipe name, which early on had occurred to me as going to be needed to have a pipe name that would look like a C nul terminated string (and which sometime during the chase after lack of privilege etc had gotten removed), I was able to get a connection between the two applications.

Then it was just a matter of locating problems where one or the other of the applications lacked a needed fix (of which there weren't too many).  After enough problems had been fixed that the KEYPUSH message was getting sent (if I delayed long enough for the full connection to occur) and the CHANGEPAGE message was being returned, it took about a day to figure out why the text wasn't appearing for the page title or in the text box.  That is, in the ComOFP component I would invoke a method in Windows CDUForm to write the text to the controls.  But, although the debugger would show that the text was changed, it wouldn't appear on the screen.

Then it occurred to me that back in September of last year (shown as October in the blog posts) (C# Displays Follow On for ExpenseIt Using Access Database) had to wait in the form to await the response and then update the controls.  That is, the form would invoke the framework interface component via calls such as
            string response = EmployeeDatabase.ModifyEmployeeData
                              (firstName, lastName, department);


            if (response == "success")
where the form couldn't continue until, in the example, the ModifyEmployeeData method of the EmployeeDatabase class returned the response.  The form controls could then be updated.  Meanwhile the EmployeeDatabase framework interface class built the message, sent it, and waited for the response.  Upon receiving the response, a wait and Sleep loop of the ModifyEmployeeData method detected this and returned the response.  In this way the Windows form could update the controls in such a way that the update would become visible after the framework had been doing other threads.

A piece of cake after that.  I just had CDUForm and ComOFP interface the same way.  That is,
        // This method is to react to a key push
        private void React(Key key)
        {
           if (ComOFP.connected) // fully connected to remote app
           {
              // Send key to OFP application
              Result result = new Result();
              result = comOFP.TreatKey(key);
              if (result.success)
              {
                  DisplayPageTitle(result.text);
              }
              else
              {
                  textBoxL2.Text = "key ignored";
              }
           }

        } // end React
in CDUForm where result returned whether the send of the key had resulted in a successful response, and if so, the text contained in the response.  While ComOFP has
       public CDUForm.Result TreatKey(CDUForm.Key key)
        {
            responseReceived = false;

            Delivery.MessageType keypush;
            keypush.data = key.ToString();
            Delivery.Publish(keyPushTopic, componentKey, keypush.data);

            // Wait until response received
            while (true)
            {
                if (!responseReceived)
                {
                    Thread.Sleep(100); // millisecs
                }
                else
                {
                    break; // exit loop
                }
            }
            // Return the response
            responseReceived = false;
            response.success = true;
            response.text = receivedChangePage;
            return response;

        } // end TreatKey
to send the KEYPUSH topic and
        static void AnyMessage(Delivery.MessageType message)
        {
            // Treat received message
            ConsoleOut.WriteLine("ComOFP AnyMessage " + message.data);
            receivedChangePage = message.data;
            responseReceived = true;

        } // end AnyMessage
is entered from its Disburse queue when a message is received and saves the data where it can be visible to TreatKey and sets that the responseReceived flag to true to terminate the while loop in TreatKey.  These two changes get around the Windows refresh problem so that DisplayPageTitle is able to not only change the text of the controls (as happened before the change) but have the new text become visible.

I then added the popup MessageBox (Figure Ok-Popup) so that the end of setup would be known.  Thus the pair of applications to communicate between C# and Ada is complete.  And I now have two versions of the message delivery framework that are compatible.

New Code Classes and Packages

Corrections to previously published code will not be provided here.  See GitHub for the entire set of C# classes and Ada packages.

C# App1 classes

Program.cs to start the framework and run the CDUForm.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows.Forms;

namespace VisualCompiler
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            if (System.Threading.Thread.CurrentThread.Name == null)
            {
                System.Threading.Thread.CurrentThread.Name = "MainThread";
            }
            currentProcess = Process.GetCurrentProcess();

            // Open ConsoleOut text file
            ConsoleOut.Install();

            Component.ApplicationId appId; // the Program.cs version
            appId.name = "App 1";          //   of the Program class
            appId.id = 1;                  //   ids the first application
            App.Launch(appId);

            // Install the components of this application.
            App1.Install();

            // Now, after all the components have been installed, create the
            // threads for the components to run in.
            //  o In addition, the TimingScheduler thread will be created.
            //  o There will be a return from Create and then the created threads
            //    will begin to execute.  And the main procedure application
            //    thread will no longer be executed -- instead, the created
            //    threads will be the only threads running.
            Threads.Create();

            // Except, that with this framework plus Windows Forms application,
            // this main procedure application thread will continue to be
            // executed in the CDUForm.
            CDUForm cduForm;
            cduForm = new CDUForm();
            Application.Run(cduForm);
        } // end Main

        static private Process currentProcess;

    } // end class Program
} // end namespace

In the above, ConsoleOut class is installed and then general App class is launched indicating that the application is App1.  As provided before, this class is a common class used with C# other applications.  After the common classes of any application are Installed, the App1 class Install is done for the classes that are unique to this application.  Finally, the framework threads are created and then the secondary CDUForm is run. 

I had previously found integrating the message delivery framework with Windows forms that the initial Form1 created by C# for a Windows Forms Application can be ignored and new forms created by Add Windows Forms | Windows Form used instead.  This is the case with CDUForm as renamed from the created Form2.

App1.cs

The unique App1.cs file installs the components that only exist in this particular application.  As such it only installs the ComOFP class – the only framework user (that is, non-framework) component of the application.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace VisualCompiler
{
    static class App1
    {
        // Install the components of Application 1
        static public void Install()
        {
            // Install into the framework each of the components
            // of the application.
            ComOFP.Install();
        } // end Install

    } // end class App1
} // end namespace

CDUForm.cs

This is the renamed Form2 created by Project | Add Windows Forms | Windows Form.  It displays a pretend aircraft CDU/MCDU as illustrated by the CDU figures and created using CDUForm.cs [Design].  The event actions are created normally by selecting the button and renaming it as desired (LSKL1, etc) and double clicking the buttons to get C# to insert the entry point into the code. 
        private void LSKL1_Click(object sender, EventArgs e)
        {
            React(Key.LSKL1);
        }
etc where I added the invocation of React to have a common method to treat all the buttons (referenced as Keys as per a physical MCDU).

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace VisualCompiler
{
    public partial class CDUForm : Form
    {
        static private ComOFP comOFP;
        static private CDUForm cduForm = new CDUForm();

        // The type to be returned by ComOFP
        public struct Result
        {
            public bool success;
            public string text;
        }

        public enum Key
        { // the line select keys and a portion of the other non-alphanumeric keys
            LSKL1,
            LSKL2,
            LSKL3,
            LSKL4,
            LSKL5,
            LSKL6,
            LSKR1,
            LSKR2,
            LSKR3,
            LSKR4,
            LSKR5,
            LSKR6,
            DATA,
            DIR,
            FLIGHTPLAN,
            INIT,
            PERF,
            PROG,
            PREV,
            NEXT,
            UP,
            DOWN
        } // end enum Key
       
        public CDUForm()
        {
            InitializeComponent();
            comOFP = new ComOFP();
        } // end constructor

        public void DisplayPageTitle(string text)
        {
            // Display new Title if key push was to known page
            if ((text == "DIR") || (text == "PROG") || (text == "PERF") ||
                (text == "INIT") || (text == "DATA") || (text == "FLIGHTPLAN"))
            {
                DisplayLabel.Text = text;
            }
            // Display result in text box no matter what
            textBoxL2.Text = text;
        } // end DisplayPageTitle

        // This method is to react to a key push
        private void React(Key key)
        {
           ConsoleOut.WriteLine("React " + key);
           if (ComOFP.connected) // fully connected to remote app
           {
              // Send key to OFP application
              Result result = new Result();
              result = comOFP.TreatKey(key);
              if (result.success)
              {
                  DisplayPageTitle(result.text);
              }
              else
              {
                  textBoxL2.Text = "key ignored";
              }
           }

        } // end React

        //**********************************************************************
        // Beginning of event handlers

        // These event handlers all invoke the React method to allow a common
        // method to determine whether in the build table mode or the OFP mode.
        // This allows a common form to be used to interpret the button push
        // as from the visual compiler / table builder or from the OFP using
        // the created table.
        private void LSKL1_Click(object sender, EventArgs e)
        {
            React(Key.LSKL1);
        }

        private void LSKL2_Click(object sender, EventArgs e)
        {
            React(Key.LSKL2);
        }

        private void LSKL3_Click(object sender, EventArgs e)
        {
            React(Key.LSKL3);
        }

        private void LSKL4_Click(object sender, EventArgs e)
        {
            React(Key.LSKL4);
        }

        private void LSKL5_Click(object sender, EventArgs e)
        {
            React(Key.LSKL5);
        }

        private void LSKL6_Click(object sender, EventArgs e)
        {
            React(Key.LSKL6);
        }

        private void LSKR1_Click(object sender, EventArgs e)
        {
            React(Key.LSKR1);
        }

        private void LSKR2_Click(object sender, EventArgs e)
        {
            React(Key.LSKR2);
        }

        private void LSKR3_Click(object sender, EventArgs e)
        {
            React(Key.LSKR3);
        }

        private void LSKR4_Click(object sender, EventArgs e)
        {
            React(Key.LSKR4);
        }

        private void LSKR5_Click(object sender, EventArgs e)
        {
            React(Key.LSKR5);
        }

        private void LSKR6_Click(object sender, EventArgs e)
        {
            React(Key.LSKR6);
        }

        private void DIR_Click(object sender, EventArgs e)
        {
            React(Key.DIR);
        }

        private void PROG_Click(object sender, EventArgs e)
        {
            React(Key.PROG);
        }

        private void PERF_Click(object sender, EventArgs e)
        {
            React(Key.PERF);
        }

        private void INIT_Click(object sender, EventArgs e)
        {
            React(Key.INIT);
        }

        private void DATA_Click(object sender, EventArgs e)
        {
            React(Key.DATA);
        }

        private void FPLAN_Click(object sender, EventArgs e)
        {
            React(Key.FLIGHTPLAN);
        }

        private void PREV_Click(object sender, EventArgs e)
        {
            React(Key.PREV);
        }

        private void NEXT_Click(object sender, EventArgs e)
        {
            React(Key.NEXT);
        }

        private void UP_Click(object sender, EventArgs e)
        {
            React(Key.UP);
        }

        private void DOWN_Click(object sender, EventArgs e)
        {
            React(Key.DOWN);
        }

        private void DisplayLabel_Click(object sender, EventArgs e)
        {

        }

    } // end class

} // end namespace

When a button/key is clicked the corresponding event handler is entered.  Since all the widgets are treated the same, the React method is immediately invoked passing the button/key's enumerated literal.

React then checks whether the two applications have completed their initial messages to exchange Register Request messages to add any non-framework topics that have been registered to be consumed (that is, received and delivered to their component).  In the situation of this application, that is the OFP CHANGEPAGE topic which gets added to the App2 Library.  App2 adds the OFP KEYPUSH topic to its Library.  If the two applications are fully connected, the key is passed to ComOFP via the TreatKey method to be transmitted to App2. 

The invocation of TreatKey is not  completed until there is a response available as explained above.  When there is a return from ComOFP the result is checked for success.  If true, the result.text is passed to DisplayPageTitle to update the title if the text indicates a new page and, in any case to update the text box.  Which completes the LSKL1, etc event handler.

ComOFP.cs

This message delivery framework interface class is invoked by CDUForm to treat a keypush event.  It publishes the OFP KEYPUSH topic which the framework will deliver to the consumer component and then waits for the signal (responseReceived) that the response can be returned to the CDUForm for display.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace VisualCompiler
{
    class ComOFP
    {
        // This component contains only two topics.  To send the selected CDU
        // key to the remote OFP app and to receive the response.

        static private Component.ParticipantKey componentKey;

        static private Topic.TopicIdType changePageTopic;
        static private Topic.TopicIdType keyPushTopic;

        static private Disburse queue;

        static private CDUForm cduForm = new CDUForm();

        static public bool connected = false; // connected to remote app 2

        static private bool responseReceived = false;
        static private CDUForm.Result response = new CDUForm.Result();
        static private string receivedChangePage = "";

        static public void Install()
        {
            // Create Disburse queue
            queue = new Disburse("ComOFP", true,     // use Timer event wakeup
                                 false, AnyMessage); // not periodic

            // Register this component
            Component.RegisterResult result;
            result = Component.Register
                     ("ComOFP", 0, Threads.ComponentThreadPriority.NORMAL,
                      MainEntry, queue);
            componentKey = result.key;

            if (result.status == Component.ComponentStatus.VALID)
            {
                Library.AddStatus status;

                changePageTopic.topic = Topic.Id.OFP;
                changePageTopic.ext = Topic.Extender.CHANGEPAGE;
                keyPushTopic.topic = Topic.Id.OFP;
                keyPushTopic.ext = Topic.Extender.KEYPUSH;

                // Register to consume the OFP CHANGEPAGE topic
                status = Library.RegisterTopic
                         (changePageTopic, result.key,
                          Delivery.Distribution.CONSUMER, MainEntry);

                // Register to produce the OFP KEYPUSH topic
                status = Library.RegisterTopic
                         (keyPushTopic, result.key,
                          Delivery.Distribution.PRODUCER, null);
            }

        } // end Install

        // Entry point
        static void MainEntry()
        {
            while (true) // loop forever
            {
                if (connected)
                {
                    // Wait for event.
                    string xxx = queue.queueName;
                    queue.EventWait();
                }
                // wait for remote app (remoteAppId of 2) before wait for event -
                else if ((!connected) && (Remote.RegisterAcknowledged(2)))
                {
                    var result = MessageBox.Show("Ok to use keys", "TreatKey",
                                                 MessageBoxButtons.OK);

                    connected = true;
                }
                else
                {
                    Thread.Sleep(100); // wait and check for connected
                }

            } // end forever loop

        } // end MainEntry

        static void AnyMessage(Delivery.MessageType message)
        {
            // Treat received message - notify TreatKey that response received
            ConsoleOut.WriteLine("ComOFP AnyMessage " + message.data);
            receivedChangePage = message.data;
            responseReceived = true;

        } // end AnyMessage

        public CDUForm.Result TreatKey(CDUForm.Key key)
        {
            responseReceived = false;

            Delivery.MessageType keypush;
            keypush.data = key.ToString();
            Delivery.Publish(keyPushTopic, componentKey, keypush.data);

            // Wait until response received
            while (true)
            {
                if (!responseReceived)
                {
                    Thread.Sleep(100); // millisecs
                }
                else
                {
                    break; // exit loop
                }
            }
            // Return the response to CDUForm
            responseReceived = false;
            response.success = true;
            response.text = receivedChangePage;
            return response;

        } // end TreatKey

    } // end ComOFP class
} // end namespace

ConsoleOut.cs

ConsoleOut is a replacement for the C++ Console.  It is necessary in order to see activity during execution since the normal Console in only available for Console Applications.  It uses a popup MessageBox at startup to ask the user if text output is to be saved and to inform the user where the disk file containing the output will be located if the response is Yes.

For Write statements (vs WriteLine) the text is saved awaiting a WriteLine in order to output the text all at once.

Since various threads can request text output, the output of one thread (via various Write statements) can be superceded by that of another thread.  Therefore, provision has been made for this possibility.

Note: The output file can't be used if already open, such as to examine it, therefore before selecting this option other users have to be terminated.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace VisualCompiler
{
    public class ConsoleOut
    {

        private static string path = "C:\\Source\\XP3\\ConsoleOut.txt";

        private static bool finishedWrite = true;

        private static string partialText = "";

        private static bool doOutput = false;

        // Install by creating the txt file
        static public void Install()
        {
            MessageBoxButtons buttons = MessageBoxButtons.YesNo;
            DialogResult result;
            string message = "String output to replace Console is in " + path;
            string caption = "Info";
            result = MessageBox.Show(message, caption, buttons);
            if (result == DialogResult.Yes)
            {
            doOutput = true;
            }
            else
            {
            doOutput = false;
            }
            if (doOutput)
            {
                if (System.IO.File.Exists(path))
                {
                    try
                    {
                        System.IO.File.Delete(@path);
                    }
                    catch (System.IO.IOException e)
                    {
                        Console.WriteLine(e.Message);
                        return;
                    }
                }
                using (System.IO.FileStream textfile = System.IO.File.Create(path))
                {
                }
            }

        } // end Install

        static public void Write(string text)
        {
            partialText += text;
        } // Write

        static public void WriteLine(String text)
        {
            if (!doOutput)
            {
                return;
            }
            if ((text == "") || (text == " "))
            {
                if (!finishedWrite)
                {
                    while (!finishedWrite) // wait while another thread finishes write
                    {
                        Thread.Sleep(10); // 10 milliseconds
                    }
                }
                finishedWrite = false;
                using (System.IO.StreamWriter textFile =
                    new System.IO.StreamWriter(@path, true))
                {
                    textFile.WriteLine(partialText);
                    partialText = ""; // clear for the next time
                }
                finishedWrite = true;
            }
            else
            {
                if (!finishedWrite)
                {
                    while (!finishedWrite) // wait while another thread finishes write
                    {
                        Thread.Sleep(10); // 10 milliseconds
                    }
                }
                finishedWrite = false;
                while (true)
                {
                    try
                    {
                        using (System.IO.StreamWriter textFile =
                               new System.IO.StreamWriter(@path, true))
                        {
                            textFile.WriteLine(text);
                        }
                        break; // exit loop
                    }
                    catch (IOException)
                   {
                   }
                }
                finishedWrite = true;
            }
        } // end WriteLine


    } // end ConsoleOut class

} // end namespace


Ada App2 packages

Main is the procedure that is entered from the operating system at startup.  As with C# Program, the common App procedure is launched identifying the application – in this instance App2 – then the unique App2 application specific code in installed, followed by the creation of the necessary threads to support the message delivery framework components and the user components.

Main.adb

with App;
with App2;
with Itf;
with Threads;

procedure Main is

  AppId : Itf.ApplicationIdType;

begin -- Main

  -- Launch the general packages of this application
  AppId.Name := "App 2     ";
  AppId.Id   := 2;
  App.Launch(AppId);

  -- Install the components of this application
  App2.Install;

  -- Now, after all the components have been installed, create their threads
  Threads.Create;

end Main;

The application specific components are installed via, for this application, the App2 package.  Similarly with the C# App1, there is only one non-framework (i.e., user) component to be installed.

App2.ads

package App2 is

  procedure Install;

end App2;

App2.adb

with ComOFP;

package body App2 is

  procedure Install is

  begin -- Install

    ComOFP.Install;

  end Install;

end App2;

The ComOFP package is the user component that treats the received OFP KEYPUSH message topic and responds by requesting delivery of the OFP CHANGEPAGE response. 

The visible Install procedure, registers the component with the Component package while naming the Main callback procedure to be entered via its thread, the Disburse package to receive wakeup events when messages are delivered and the address of the Write function to be used by Delivery when a message is to be queued to the instance of the Disburse package.  Component will then obtain a wait handle for the queue and return it in Result and Install will then pass it to its instance of the Disburse package.  Then the two topics (OFP KEYPUSH and OFP CHANGEPAGE) are registered with the Library; the first specifying that ComOFP will be the producer of the messages and that it will be a consumer of the second topic.

When, via Delivery, DisburseWrite is invoked to write to the particular queue, Disburse will add the message to the queue and signal that the wait should end.  This processing will cause the thread of ComOFP to execute (note, the write took place in the thread of the writer) and the message will be forwarded to the AnyMessage Universal message handler of the component as specified in the declaration of the queue.  Upon entry to the AnyMessage procedure the message data is checked and the appropriate response data created and published.  (Note: No check for what the received topic might be is necessary since only the one consumer topic was registered.)  The framework will then look up the consumers of the topic in the Library and, since only the one remote consumer, cause it to be transmitted to App1 for delivery to its ComOFP consumer.

ComOFP.ads

package ComOFP is

  procedure Install;

end ComOFP;

ComOFP.adb

with CStrings;
with Component;
with Delivery;
with Disburse;
with ExecItf;
with Itf;
with Library;
with System;
with Text_IO;
with Threads;
with Topic;
with Unchecked_Conversion;

package body ComOFP is

  package Int_IO is new Text_IO.Integer_IO( Integer );

  QueueOFP : Itf.V_Short_String_Type
             := ( Count => 7,
                  Data  => "QComOFP             " );

  ComponentKey : Itf.ParticipantKeyType := Component.NullKey;
  -- Component's key returned from Register

  KeyPushTopic    : Topic.TopicIdType;
  ChangePageTopic : Topic.TopicIdType;

  procedure AnyMessage
  ( Message : in Itf.MessageType );

  package DisburseQueue
  -- Instantiate disburse queue for component
  is new Disburse( QueueName => QueueOFP'Address,
                   Periodic  => False,
                   Universal => AnyMessage'Address,
                   Forward   => System.Null_Address );

  function DisburseWrite
  -- Callback to write message to the DisburseQueue
  ( Message : in Itf.MessageType
  ) return Boolean;

  procedure Main -- callback
  ( Topic : in Boolean := False
  );

  ComOFPName : Itf.V_Medium_String_Type
  := ( Count => 6,
       Data  => "ComOFP                                            " );

  Result : Component.RegisterResult;


  procedure Install is

    Status : Library.AddStatus;

    use type Component.ComponentStatus;
    use type Library.AddStatus;

    function to_Callback is new Unchecked_Conversion
                                ( Source => System.Address,
                                  Target => Topic.CallbackType );

  begin -- Install

    Result :=
      Component.Register
      ( Name       => ComOFPName,
        Period     => 0, -- not periodic
        Priority   => Threads.NORMAL, -- Requested priority of thread
        Callback   => to_Callback(Main'Address), -- Callback function of component
        Queue      => DisburseQueue.Location,
        QueueWrite => DisburseWrite'Address );
    if Result.Status = Component.VALID then
      DisburseQueue.ProvideWaitEvent( Event => Result.Event );
      ComponentKey := Result.Key;

      KeyPushTopic.Topic := Topic.OFP;
      KeyPushTopic.Ext := Topic.KEYPUSH;
      Status := Library.RegisterTopic( KeyPushTopic, Result.Key,
                                       Delivery.CONSUMER,
                                       to_Callback(Main'Address) );
      if Status /= Library.SUCCESS then
        Text_IO.Put_Line( "ERROR: Register of KEYPUSH Topic failed" );
      end if;

      ChangePageTopic.Topic := Topic.OFP;
      ChangePageTopic.Ext := Topic.CHANGEPAGE;
      Status := Library.RegisterTopic( ChangePageTopic, Result.Key,
                                       Delivery.PRODUCER,
                                       to_Callback(Main'Address) );
      if Status /= Library.SUCCESS then
        Text_IO.Put_Line( "ERROR: Register of CHANGEPAGE Topic failed" );
      end if;
    end if;

  end Install;

  -- Write to queue
  function DisburseWrite
  ( Message : in Itf.MessageType
  ) return Boolean is
  begin -- DisburseWrite
    return DisburseQueue.Write(Message => Message);
  end DisburseWrite;
 
  -- Forever loop as initiated by Threads
  procedure Main -- callback
  ( Topic : in Boolean := False
  ) is

    Success : Boolean;

    Timer_Sec
    -- System time seconds as ASCII
    : String(1..2);
    System_Time
    -- System time
    : ExecItf.System_Time_Type;

  begin -- Main

    Text_IO.Put_Line("in ComOFP callback");

    loop -- forever
      Text_IO.Put_Line("ComOFP wait for event");

      DisburseQueue.EventWait; -- wait for event

      Text_IO.Put_Line("ComOFP after end of wait");

      System_Time := ExecItf.SystemTime;
      CStrings.IntegerToString(System_Time.Second, 2, False, Timer_Sec, Success);
      Text_IO.Put("ComOFP ");
      Text_IO.Put_Line(Timer_Sec(1..2));

    end loop;

  end Main;

  -- Treat any message of the component that doesn't have its own procedure
  procedure AnyMessage
  ( Message : in Itf.MessageType
  ) is

    MessageOut : Itf.MessageType;

    use type Topic.Extender_Type;
    use type Topic.Id_Type;

  begin -- AnyMessage

    Text_IO.Put("Entered ComOFP AnyMessage ");
    Int_IO.Put(Topic.Id_Type'pos(Message.Header.Id.Topic));
    Int_IO.Put(Topic.Extender_Type'pos(Message.Header.Id.Ext));
    Text_IO.Put_Line(" ");

    if Message.Header.Id.Topic = Topic.OFP and then
       Message.Header.Id.Ext   = Topic.KEYPUSH
    then

      -- Treat the message by selecting a new page and publishing the
      -- OFP CHANGEPAGE back to App1 to change the displayed page on
      -- the pseudo CDU.
     
      Text_IO.Put_Line(Message.Data(1..3));
     
      MessageOut.Data := ( others => ASCII.NUL );
     
      if Message.Data(1..3) = "DIR" then
        MessageOut.Data(1..3) := "DIR";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..4) = "PROG" then
        MessageOut.Data(1..4) := "PROG";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..4) = "PERF" then
        MessageOut.Data(1..4) := "PERF";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..4) = "INIT" then
        MessageOut.Data(1..4) := "INIT";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..4) = "DATA" then
        MessageOut.Data(1..4) := "DATA";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..10) = "FLIGHTPLAN" then
        MessageOut.Data(1..10) := "FLIGHTPLAN";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..4) = "PREV" then
        MessageOut.Data(1..13) := "Previous Page";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..4) = "NEXT" then
        MessageOut.Data(1..9) := "Next Page";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..2) = "UP" then
        MessageOut.Data(1..11) := "Up Selected";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..4) = "DOWN" then
        MessageOut.Data(1..13) := "Down Selected";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..5) = "LSKL1" then
        MessageOut.Data(1..13) := "LineSelect L1";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..5) = "LSKL2" then
        MessageOut.Data(1..13) := "LineSelect L2";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..5) = "LSKL3" then
        MessageOut.Data(1..13) := "LineSelect L3";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..5) = "LSKL4" then
        MessageOut.Data(1..13) := "LineSelect L4";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..5) = "LSKL5" then
        MessageOut.Data(1..13) := "LineSelect L5";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..5) = "LSKL6" then
        MessageOut.Data(1..13) := "LineSelect L6";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..5) = "LSKR1" then
        MessageOut.Data(1..13) := "LineSelect R1";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..5) = "LSKR2" then
        MessageOut.Data(1..13) := "LineSelect R2";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..5) = "LSKR3" then
        MessageOut.Data(1..13) := "LineSelect R3";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..5) = "LSKR4" then
        MessageOut.Data(1..13) := "LineSelect R4";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..5) = "LSKR5" then
        MessageOut.Data(1..13) := "LineSelect R5";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      elsif Message.Data(1..5) = "LSKR6" then
        MessageOut.Data(1..13) := "LineSelect R6";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      else
        Text_IO.Put_Line("ERROR: Invalid Key data");
        MessageOut.Data(1..16) := "Invalid Key data";
        Delivery.Publish(ChangePageTopic, ComponentKey, MessageOut.Data);
      end if; 
     
    else

      Text_IO.Put_Line("ERROR: Invalid topic received by ComOFP");

    end if;

    Text_IO.Put_Line("Exiting ComOFP AnyMessage");
   
  end AnyMessage;

end ComOFP;

Sample Text Output (of App2)

Receive of Register Request message where 3 followed by 4 are the numeric values of REGISTER and REQUEST.  5 in the 16th byte is the data Size and 8 6 1 4 0 is the data where 8 and 6 are the numeric values of OFP and CHANGEPAGE while 1,4,0 is the component key of ComOFP of application 1 meaning that it is the 4th component (of any kind) registered in Application 1.
Bytes read          29Received CRC        247        188
        247         188           3           4           1           0           0           2           0           0           0           0           0           6           0           5           8           6           1           4           0
DisburseBytes Write to be done         21
Disburse Bytes Set_Event True
Disburse Bytes after Wait
Disburse Bytes after Reset Event
ReceiveInterface wait satisfied
Receive1 returned with Success
ReceiveMessage fromServer ReceiveInterface RecdMessage          21
ParseRecdMessages
message data size           5
CopyMessage
CopyMessage          6          5
exit from CopyMessage
ForwardMessage to be called
          1 True
Register Message
do ReadFile        250        250        240
RegisterRequest topic          6          8          6           6          8          6TopicTable after Decode
in Write TransmitQueue        172
ReceiveInterface wait for event
T1
Entered Transmit AnyMessage T1           3REGISTER  RESPONSE      5  #
The above shows the REGISTER RESPONSE being transmitted to notify App1 of the receipt of the REGISTER REQUEST.
. . .
Below is the receive of the OFP KEYPUSH (8 3 in the 3rd and 4th bytes) with 3 bytes of data (68, 73, 82) which, as ASCII characters are D I R.
Bytes read          27Received CRC        237        191
        237         191           8           3           1           4           0           2           5           0           0           0           0          11           0           3          68          73          82
DisburseBytes Write to be done         19
Disburse Bytes Set_Event True
Disburse Bytes after Wait
Disburse Bytes after Reset Event
ReceiveInterface wait satisfied
Receive1 returned with Success
ReceiveMessage fromServer ReceiveInterface RecdMessage          19
ParseRecdMessages
message data size           1 True
          3
do ReadFile        250        250        240
CopyMessage
CopyMessage         11          3
exit from CopyMessage
ForwardMessage to be called
Forward Delivery Publish           8
Delivery of topic           8          1
          8
Deliver the topic           0          0          2
in Write QComOFP        184
ReceiveInterface wait for event
ComOFP after end of wait
Disburse Bytes in EventWait
ComOFP 38
ComOFP wait for event
Entered ComOFP AnyMessage           8          3
DIR   
in Write TransmitQueue        172
Exiting ComOFP AnyMessage
T1
after ForwardMessage QComOFP        184
Entered Transmit AnyMessage T1           8OFP       CHANGEPAGE   12 DI
TransmitMessage sending          28 bytes
Immediately above is ComOFP AnyMessage receiving the OFP KEYPUSH (8 3) topic followed by the results of publishing the OFP CHANGEPAGE response with the message delivered to Transmit via its queue and Transmit sending the 28 bytes of the message.

The results in App1 can be seen in the figures (although for the F-PLAN key).