Tuesday, January 8, 2019

Pseudo Visual Compiler using Interface to Ada as the OFP



Trying to think of what to do next I thought how it would be nice to invoke another language, such as Ada, from C# to avoid the C# problems of not being able to call the action routines of the pseudo OFP from a table.  Such as a table with callback addresses as can be done from Ada.

Then it occurred to me that I should be able to do it already.  With a post of a little while back – C# Displays Follow On for ExpenseIt Using the Framework – I was able to use the C# message delivery framework along with Windows Forms.  Therefore, instead of sending messages to a database component of the same application as done in that post, I could send messages from the C# visual compiler application to a remote Ada application where the Ada application could mimic the very highly reduced OFP.  And the actions could then be invoked from a table of callbacks.

Problem solved.  Except, of course, it can only be done between C# and Ada unless the message delivery framework is extended into yet another language.

However, then I thought how the years ago message delivery framework of the EP and IP experimental projects is much too complicated.  One reason being that they were test beds for trying out new interfaces and methods of sending and receiving message topics.  So those versions of the framework, as written in Ada, have much that is superfluous to what the much simpler C# framework does.  So I used this as an excuse to start creating a simpler Ada version that will result in an Ada message delivery framework that mimics the C# version.

As I have now started, I can see that this will definitely keep me busy for some time so I won't need to worry about having something to do.  For instance, in following the C# code, I found that I needed some Ada procedures and functions to implement C# system functions.  This post will provide those that I came across in just two of the C# methods and present the corresponding C# code with the new Ada equivalent.

The C# system methods are the string append represented by
  String1 += String2;
and, from my C# Format class DecodeHeartbeatMessage method
  int j = String.Compare(subString1, "Heartbeat", false);
  i = subString1.IndexOf('|');
  subString2 = subString1.Substring(i + 1, subString1.Length - i - 1);
  bool result = Int32.TryParse(numeric, out field);

So I implemented these methods to start with.  As I proceeded implementing the pair of C# methods (EncodeHeartbeatMessage as the second method) I found that I didn't need the append routine but did need additional C# method replacements. 

As will be seen, the usage of the Ada versions will be more difficult than the C#. 

Part (most?) of the reason is that Ada requires that the size of the strings be specified when they are declared.  Therefore, since I didn't want the implementation of these methods to need to declare large strings in order to be sure to have enough space for a wide variety of strings to be passed to the Ada functions/procedures I supplied the address of the string to the Ada routine so its size could be whatever was needed.

These C# methods are implemented in a new CStrings package.

CStrings Ada package

The Ada CStrings package specification is as follows.

with System;

package CStrings is

  subtype CompareResultType is Integer range -1..+1;
  subtype IntegerSizeType is Natural range 0..64;
  subtype StringOffsetType is Integer;

  subtype StringType is System.Address;
  -- Provides location of a string / character array

  type SubStringType is
  record
    Length : Natural;
    -- Number of characters in the substring
    Value  : System.Address;
    -- Location of the string / character array
  end record;

  function AddToAddress
  ( Location : in System.Address;
    Amount   : in Integer
  ) return System.Address;

  procedure Append
  ( First  : in StringType;
    -- First string to append Second to
    Second : in StringType;
    -- String to append to First
    Result : in out SubStringType
    -- Available length upon input; concatenated string with length upon output
  );
  -- Append strings; C# +=

  function Compare
  ( Left       : in StringType;
    -- First substring to be compared
    Right      : in StringType;
    -- Other substring to be compared with first
    IgnoreCase : in Boolean
    -- True if case is to be considered
  ) return CompareResultType;
  -- Return whether Left is alpha sort before Right, equal, or after
  -- The compare can go until the trailing nul characters

  function IndexOf
  ( From : in StringType;
    -- String to be searched
    Find : in StringType
    -- String to search for
  ) return StringOffsetType;
  -- Return location in From at which Find was located

  function IndexOf1
  ( From : in StringType;
    -- String to be searched
    Find : in Character
    -- Character to search for
  ) return StringOffsetType;
  -- Return location in From at which Find was located

  procedure IntegerToString
  ( From    : in Integer;
    -- Integer to convert to a string
    Size    : in IntegerSizeType;
    -- Number of characters into which the converted value must fit
    Result  : out String;
    -- Converted value with trailing NUL
    Success : out Boolean
    -- Whether From fits in Size (plus 1 for the NUL)
  );
  -- Convert Integer to string that will fit in string of Size

  function Substring
  ( From  : in StringType;
    -- String from which to extract a substring
    Start : in StringOffsetType;
    -- Start index into From
    Stop  : in StringOffsetType
    -- End index into From
  ) return String;
  -- Extract substring from From and return from Start to Stop - 1

  procedure TryParse
  ( From    : in StringType;
    -- String from which to extract a substring and convert
    Size    : in IntegerSizeType;
    -- Number of bits into which the converted value must fit
    Result  : out Integer;
    -- Converted value as normal sized integer
    Success : out Boolean
    -- Whether From string is numeric and fits in Size
  );
  -- Convert String to integer that will fit in one of Size

end CStrings;

I didn't finish the implementation of IndexOf since I was only looking for single characters.  Therefore, IndexOf1 is the function that is used.

In most every instance the implementation uses the trailing NUL character of C and C# as the string terminator.  This is because the Ada message framework will be receiving messages from C# where the message data will have a terminating NUL.  Therefore, the CStrings methods will expect a terminating NUL except for IndexOf where the input string can be much longer.

I could have chosen to use an array of bytes as I did before to create the data for a message and then convert to a string when the message is published.  But I chose to follow the C# code and create a message data string.

Thus, the Ada body of CStrings is
with Interfaces.C;
with System.Storage_Elements;
with Text_IO;

package body CStrings is

  package C renames Interfaces.C;
 
  Adjustment
  : constant := Character'Pos('a') - Character'Pos('A');

  function AddToAddress
  ( Location : in System.Address;
    Amount   : in Integer
  ) return System.Address is
    use System.Storage_Elements;
  begin -- AddToAddress
    return Location+System.Storage_Elements.Storage_Offset(Amount);
  end AddToAddress;

  procedure Append
  ( First  : in StringType;
    Second : in StringType;
    Result : in out SubStringType
  ) is

    Available : Integer := Result.Length;

    Last : Integer := 0;

    FirstAddr  : System.Address := First;
    SecondAddr : System.Address := Second;
    OutAddr    : System.Address := Result.Value;

  begin -- Append

    -- Copy First string to Result
    while (True) loop
      declare
        Location : System.Address := FirstAddr;
        OutLoc   : System.Address := OutAddr;
      begin
        declare
          FirstChar : Character;
          for FirstChar use at Location;
          OutChar : Character;
          for OutChar use at OutLoc;
        begin
          if FirstChar = ASCII.NUL then -- end of first string
            exit; -- loop; don't copy the trailing NUL
          else
            if Last < Available then
              Last := Last + 1;     -- copy character into
              OutChar := FirstChar; --  output string
            else
              Text_IO.Put_Line("First string too long for output");
              raise Constraint_Error;
            end if;
          end if;
        end;
        FirstAddr := AddToAddress(FirstAddr,1);
        OutAddr   := AddToAddress(OutAddr,1);
     end;
    end loop;

    -- Copy Second string to Result
    while (True) loop
      declare
        Location : System.Address := SecondAddr;
        OutLoc   : System.Address := OutAddr;
      begin
        declare
          SecondChar : Character;
          for SecondChar use at Location;
          OutChar : Character;
          for OutChar use at OutLoc;
        begin
          if Last < Available then
            Last := Last + 1;      -- copy character to
            OutChar := SecondChar; --  output Result string
            if SecondChar = ASCII.NUL then -- end of second string
              Result.Length := Last; -- Set output Result Length
              exit; -- loop to return with new string
            end if;
          else
            Text_IO.Put_Line("Second string too long for output");
            raise Constraint_Error;
          end if;
        end;
        SecondAddr := AddToAddress(SecondAddr,1);
        OutAddr    := AddToAddress(OutAddr,1);
      end;
    end loop;

  end Append;

  function ToLower
  ( Char : in Character
  ) return Character is
  begin -- ToLower
    if Char in 'A'..'Z' then
      return Character'Val( Character'Pos(Char) + Adjustment );
    else
      return Char;
    end if;
  end ToLower;

  function Compare
  ( Left       : in StringType;
    Right      : in StringType;
    IgnoreCase : in Boolean
  ) return CompareResultType is

    Index : Integer := 0;

    LeftAddr  : System.Address := Left;
    RightAddr : System.Address := Right;

  begin -- Compare

    while (True) loop
      Index := Index + 1;
      declare
        LeftLoc  : System.Address := LeftAddr;
        RightLoc : System.Address := RightAddr;
      begin
        declare
          LeftChar : Character;
          for LeftChar use at LeftLoc;
          RightChar : Character;
          for RightChar use at RightLoc;
        begin
          if IgnoreCase then
            LeftChar  := ToLower(LeftChar);
            RightChar := ToLower(RightChar);
          end if;
          if LeftChar = ASCII.NUL and then RightChar = ASCII.NUL then
            return 0; -- match
          elsif LeftChar = ASCII.NUL then
            return 1; -- treating blank fill etc as mismatch
          elsif RightChar = ASCII.NUL then
            return -1; -- treating blank fill etc as mismatch
          elsif LeftChar < RightChar then
            return 1; -- right string greater
          elsif LeftChar > RightChar then
            return -1; -- left string greater
          end if;
        end;
        LeftAddr  := AddToAddress(LeftAddr,1);
        RightAddr := AddToAddress(RightAddr,1);
      end;
    end loop;
    return -1;
  end Compare;

  function IndexOf
  ( From : in StringType;
    Find : in StringType
  ) return StringOffsetType is

    Done  : Boolean;
    Index : Integer := 1;

    FromAddr : System.Address := From;
    FindAddr : System.Address := Find;

    TempFind : array(0..120) of Character;

  begin -- IndexOf

    -- Obtain array of characters to search for in From.  Limit the number
    -- of characters in Find.
    Done := False;
    declare
      Location : System.Address := FindAddr;
    begin
      declare
        FindChar : Character;
        for FindChar use at Location;
      begin
        if FindChar /= ASCII.NUL then
          TempFind(Index) := FindChar;
          Index := Index + 1;
        else
          Done := True;
        end if;
      end;
      if not Done and then Index < 120 then
        Location := AddToAddress(Location,1);
      end if;
    end;

    –- Returning 0 for not found since this function isn't implemented
    return 0;

  end IndexOf;

  function IndexOf1
  ( From : in StringType;
    Find : in Character
  ) return StringOffsetType is

    Found : Boolean := False;
    Index : StringOffsetType := 1;

    FromAddr : System.Address := From;

  begin -- IndexOf1

    while (not Found) loop
      declare
        Location : System.Address := FromAddr;
      begin
        declare
          FromChar : Character;
          for FromChar use at Location;
        begin
          if FromChar = Find then
            Found := True;
            return Index;
          elsif FromChar = ASCII.NUL then -- at end of C string
            Found := True;
            return 0;
          end if;
        end;
      end;
      Index := Index + 1;
      FromAddr := AddToAddress(FromAddr,1);
    end loop;

    return 0;

  end IndexOf1;

  procedure IntegerToString
  ( From    : in Integer;
    Size    : in IntegerSizeType;
    Result  : out String;
    Success : out Boolean
  ) is

    ResultData : String(1..Size+1); -- sufficient to contain the converted integer

    Index : Integer := 0; -- location at which to insert '-' sign
    Sign  : Character := ' ';

    TempFrom : Integer;
    -- From as positive

  begin -- IntegerToString

    if From < 0 then
      Sign := '-';
    end if;

    TempFrom := Abs(From);

    ResultData := ( others => '0' );
    for I in reverse 1..Size loop
      ResultData(I) := Character'Val( Character'Pos('0') + ( TempFrom mod 10) );
      TempFrom := TempFrom / 10;
      exit when TempFrom = 0;
    end loop;

    if TempFrom /= 0 then -- From cannot be converted into Size characters
      Success := False;
      Result := (others => ASCII.NUL);
      return;
    end if;

    Index := 0;
    for I in 1..Size-1 loop -- replace leading 0s with spaces
      exit when ResultData(I) /= '0';
      Index := I; -- index of last space
      ResultData(Index) := ' ';
    end loop;

    if Sign /= ' ' then -- negative sign
      if Index > 0 then -- available position for negative sign
        ResultData(Index) := Sign;
      else -- From cannot be converted into Size characters
        Success := False;
        Result := (others => ASCII.NUL);
        return;
      end if;
    end if;

    ResultData(Size+1) := ASCII.NUL; -- append trailing NUL

    Success := True;
    Result := ResultData;

  end IntegerToString;

  function Substring
  ( From  : in StringType;
    Start : in StringOffsetType;
    Stop  : in StringOffsetType
  ) return String is

    -- Create C char_array at location of From
    Size : C.size_t := C.size_t(Stop-Start+2);
    FromData : C.char_array(1..Size);
    for FromData use at From;

    Index : Integer;

    -- Ada string to be returned
    Temp : String(1..Stop-Start+2);

  begin -- Substring

    -- Copy substring characters from location at From to Ada string.
    Index := 0;
    for I in Start..Stop loop
      Index := Index + 1;
      Temp(Index) := Character(FromData(C.size_t(I)));
      if Temp(Index) = ASCII.NUL then -- quit early if trailing NUL found
        exit; -- loop
      end if;
    end loop;

    -- Insert trailing NUL if needed
    if Temp(Index) /= ASCII.NUL then
      Index := Index + 1;
      Temp(Index) := ASCII.NUL;
    end if;

    -- Return NUL terminated Ada string.
    return Temp(1..Index);

  end Substring;

  procedure TryParse
  ( From    : in StringType;
    Size    : in IntegerSizeType;
    Result  : out Integer;
    Success : out Boolean
  ) is

    Digit : Integer;
    Sign  : Integer := 1;
    Start : Integer := 1;

    Number : Integer := 0;

    FromData : String(1..Size);
    for FromData use at From;

  begin -- TryParse

    Result := 0;
    Success := False;

    -- Check for leading spaces
    for I in 1..Size loop
      if FromData(I) /= ' ' then
        Start := I;
        exit;
      end if;
    end loop;

    -- Check for a leading non-space sign
    if FromData(Start) = '+' then
      Start := Start + 1; -- bypass sign
    elsif FromData(Start) = '-' then
      Start := Start + 1;
      Sign  := -1; -- to make number negative
    end if;

    -- Check if rest of From array contains numeric digits and convert
    for I in Start..Size loop
      if FromData(I) not in '0'..'9' then
        if I = Size and then FromData(I) = ASCII.NUL then
          null; -- ignore trailing NUL
        else
          return; -- with failure
        end if;
      else -- valid digit
        Digit := Character'Pos(FromData(I)) - Character'Pos('0');
        Number := Number * 10 + Digit;
      end if;
    end loop;

    Result := Sign*Number; -- take any leading sign into account
    Success := True;

  end TryParse;

end CStrings;

These routines take advantage of Ada's ability to overlay a variable with one of a different format (but the same size) by setting its address to that of the other variable.  For instance, in a declare block
        declare
          SecondChar : Character;
          for SecondChar use at Location;
          OutChar : Character;
          for OutChar use at OutLoc;
        begin
Also, they use the AddToAddress function declared at the beginning to be able to increment through the strings.  For instance,
        SecondAddr := AddToAddress(SecondAddr,1);
        OutAddr    := AddToAddress(OutAddr,1);
where the AddToAddress function allows the value to be added to be other integers besides the use of 1 illustrated.  This is made use of in the Format DecodeHeartbeatMessage method.

The Append function copies the First string to the Result Value string except for its terminating NUL and then copies the Second string to follow it in Result Value.  The available size allocated for the concatenated string is input in the Result Length and then replaced for output by the length of the Result Value string.

Compare assumes NUL terminated strings and returns 0 if the strings are the same length and match.  If the strings are of different lengths than this implementation ignores whether the extra characters are spaces and returns either plus or minus 1 to indicate a mismatch.

IndexOf1 scans for the supplied Find character and returns its location in the From string or 0 if the Find character can't be found.

IntegerToString converts the supplied From integer to a string and then determines if leading string positions can be ignored to return a string of the specified Size where the Size assumes that the invoking routine has left space for the terminating NUL.

Substring returns the characters between Start and Stop in the From string or those up to the terminating NUL.  The returned string will have a terminating NUL.

IntegerToString and TryParse assume the Integer conversion version of the C# method.  That is, unlike C#, the type of the conversion cannot be supplied.  Well, in the case of IntegerToString the conversion from an Integer is not assumed since a generalized C# ToString isn't used. 

C# Format DecodeHeartbeatMessage method

In the C# version of the message framework there is a Format class as provided in Format.cs.  This class contains methods to decode and encode a few messages and can be extended as desirable.  One of these methods is to decode a "heartbeat" message that is transmitted from a remote application (that is, not the application that is currently running) to the currently running application to detect if the connection between the two application has become broken by not receiving the message in a timely manner or receiving a garbaged version of the message.

The C# code for decoding the message is as follows.

    static public bool DecodeHeartbeatMessage
        (Delivery.MessageType message, int remoteAppId)
    {
        if ((message.header.id.topic == Topic.Id.HEARTBEAT) &&
            (message.header.id.ext == Topic.Extender.FRAMEWORK))
        {
            // assuming rest of header is ok
            if (message.header.size != 15)
            {
                Console.WriteLine("Heartbeat message has a size other than 15 {0}",
                       message.header.size);
                return false;
            }

            string numeric = "";
            string subString1 = "";
            string subString2 = "";
            // Find first delimiter, if any.
            int i = message.data.IndexOf('|');
            int l = 0;
            if (i > 0)
            { // Is substring prior to delimiter the message id?
                subString1 = message.data.Substring(0, i);
                int j = String.Compare(subString1, "Heartbeat", false);
                if (j == 0)
                { // Yes - Heartbeat message
                    l = message.data.Length - i - 1;
                    subString1 = message.data.Substring(i + 1, l);
                    i = subString1.IndexOf('|');
                    numeric = subString1.Substring(0, i);
                    subString2 = subString1.Substring(
                                      i + 1, subString1.Length - i - 1);
                    int field;
                    bool result = Int32.TryParse(numeric, out field);
                    if (result)
                    {
                        if (field == remoteAppId)
                        { // 1st field is as expected
                            i = subString2.IndexOf('|');
                            if (i > 0)
                            {
                                subString1 = subString2.Substring(
                                                  i + 1, subString2.Length - i - 1);
                                numeric = subString2.Substring(0, i);
                                result = Int32.TryParse(numeric, out field);
                                if (result)
                                {
                                    if (field == App.applicationId)
                                    { // 2nd field is as expected; finished checking
                                        //      consecutiveValid++;
                                        return true; //heartbeatMessage = true;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return false;

    } // end DecodeHeartbeatMessage

Note:  When doing the Ada version I noted that the last field is not extracted and converted to numeric.

Ada Format DecodeHeartbeatMessage function

The Ada version of this same routine required the use of the CStrings functions (except for Append which the Encode routine will need).  Even with the use of the CStrings package, the code is not as straight forward as can be seen below.

  function DecodeHeartbeatMessage
  ( Message     : in Delivery.MessageType;
    RemoteAppId : in Integer
  ) return Boolean is

    I : CStrings.StringOffsetType;
    -- Index into string
    J : CStrings.CompareResultType;
    -- 0 if two strings compare
    L : CStrings.StringOffsetType;
    -- Length of substring

    Msg
    : CStrings.StringType := Message.Data'Address;

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

  begin -- DecodeHeartbeatMessage

    if Message.Header.Id.Topic /= Topic.HEARTBEAT) or else
       Message.Header.Id.Ext /= Topic.FRAMEWORK
    then
      return False;
    end if;

    -- assuming rest of header is ok
    if Message.Header.Size /= 15 then
      Text_IO.Put("Heartbeat message has a size other than 15 ");
      return False;
    end if;

    -- Find first delimiter, if any.
    I := CStrings.IndexOf1(Msg, '|');
    L := 0;
    if I > 0 then -- delimiter found
      -- Is substring prior to delimiter the message id?
      declare
        SubString1
        -- String where NUL will be located at position corresponding to '|'
        : String(1..I);
        Heartbeat : String(1..10);
      begin
        SubString1 := CStrings.Substring(Msg, 1, I-1); -- string prior to '|'

        Heartbeat(1..9) := "Heartbeat";
        Heartbeat(10) := ASCII.NUL;
        J := CStrings.Compare(Substring1'Address, Heartbeat'Address, False);
        if J /= 0 then
          -- miscompare
          return False; -- not Heartbeat message
        end if;
      end;
    else
      return False; -- not Heartbeat message
    end if;

    -- Heartbeat message
    L := Message.Header.Size - I + 1; -- where I is location of the '|'
    declare
      SubString1 : String(1..L);
      -- String where NUL will be located at position corresponding to '|'
      Msg1 : CStrings.StringType := CStrings.AddToAddress(Msg,I);
    begin

      SubString1 := CStrings.Substring(Msg1, 1, L-1);
      I := CStrings.IndexOf1(SubString1'Address,'|');

      declare
        Numeric : String(1..I);
        SubString2 : String(1..SubString1'Length-I);--width with up to | removed
        Field  : Integer;
        Result : Boolean;
        Msg2 : CStrings.StringType := CStrings.AddToAddress(Msg1,I);
      begin

        Numeric := CStrings.Substring(SubString1'Address, 1, I-1);
        L := SubString1'Length - I -1;
        CStrings.TryParse(Numeric'Address, Numeric'Length, Field, Result);
        if not Result then
          return False;
        end if;
        if Field /= remoteAppId then -- 1st field not as expected
          return False;
        end if;

        -- Get "to" app id
        SubString2 := CStrings.SubString(Msg2, 1, L+1);
        I := CStrings.IndexOf1(SubString2'Address,'|');
        Numeric := CStrings.Substring(SubString2'Address, 1, I-1);
        L := SubString1'Length - I -1;
        CStrings.TryParse(Numeric'Address, Numeric'Length, Field, Result);
        if not Result then
          return False;
        end if;
        if Field /= Itf.ApplicationId.Id then -- 2nd field not as expected
          return False;
        end if;

        -- Get Heartbeat Iteration.  Otherwise ignore it for now.
        declare
          SubString3 : String(1..SubString2'Length-I);--width with up to | removed
        begin
          SubString3 := CStrings.SubString(Msg3, 1, L+1);
        end;
      end;
    end;

    return True;

  end DecodeHeartbeatMessage;

In this implementation declare blocks are used to declare the strings of the correct sizes depending upon where the delimiter (that is, the | character) was found.  And the Data Message, first declared as Msg and located at the address of the Message.Data portion of the Message, moved across the message data via declarations such as
      Msg1 : CStrings.StringType := CStrings.AddToAddress(Msg,I);
and
        Msg2 : CStrings.StringType := CStrings.AddToAddress(Msg1,I);
and
          Msg3 : CStrings.StringType := CStrings.AddToAddress(Msg2,I);
where Msg1, Msg2, and Msg3 are addresses since they are of CStrings.String_Type and AddToAddress is used to move the location of the message data to be used to the right.

C# Format EncodeHeartbeatMessage method

    static public Delivery.MessageType EncodeHeartbeatMessage(int appId)
    {
        string msg = "Heartbeat|" + App.applicationId + "|" +
                      appId + "|" + heartbeatIteration;
        Delivery.MessageType message;
        message.header.CRC = 0;
        message.header.id.topic = Topic.Id.HEARTBEAT;
        message.header.id.ext = Topic.Extender.FRAMEWORK;
        Component.ParticipantKey key;
        key.appId = App.applicationId;
        key.comId = 0; // for Framework
        key.subId = 0;
        message.header.from = key;
        key.appId = appId;
        message.header.to = key;
        message.header.referenceNumber = 0;
        message.header.size = (short)msg.Length;
        message.data = msg;
        return message;
    } // end EncodeHeartbeatMessage
Note that the msg string at the top just concatenates the portions of the message data together with the | delimiters.  It is then set to message.data towards the end. 

Ada Format EncodeHeartbeatMessage function

  ( RemoteAppId : in Integer
  ) return MessageType is

    Success : Boolean;
    Id      : String(1..2);

    Message : MessageType; -- message to be returned

    Msg : String(1..25) := (others => ASCII.NUL); -- enough space for Data

    for Msg use at Message.Data'address;

  begin -- EncodeHeartbeatMessage

    Msg(1..10) := "Heartbeat|";
    CStrings.IntegerToString( Itf.ApplicationId.Id, 1, Id, Success );
    Msg(11) := Id(1);
    Msg(12..12) := "|";
    CStrings.IntegerToString( RemoteAppId, 1, Id, Success );
    Msg(13) := Id(1);
    Msg(14..14) := "|";
    CStrings.IntegerToString( HeartbeatIteration, 1, Id, Success );
    Msg(15) := Id(1);

    Message.Header.CRC := 0;
    Message.Header.Id.Topic := Topic.HEARTBEAT;
    Message.Header.Id.Ext := Topic.FRAMEWORK;
    Message.Header.From(1) := Machine.Unsigned_Byte(Itf.ApplicationId.Id);
    Message.Header.From(2) := 0;
    Message.Header.From(3) := 0;
    Message.Header.To(1) := Machine.Unsigned_Byte(RemoteAppId);
    Message.Header.To(2) := 0;
    Message.Header.To(3) := 0;
    Message.Header.ReferenceNumber := 0;
    Message.Header.Size := 15;

    return Message;

  end EncodeHeartbeatMessage;
Note that this Ada version doesn't need the CStrings Append procedure.  The Data string (as overlaid with Msg) just builds up Msg which will have a terminating NUL.  Thus the Ada version of the routine is not really any more complicated than the C# version except for needing the IntegerToString procedure to convert the three integers to a string before copying into Msg.

The function depends upon the three integers never exceeding a single digit (that is, 0 to 9) so that IntegerToString would return a conversion failure.  In the interface application being created between C# and Ada the two Application Ids will only be 1 and 2.

No comments: