Restructuring of
Framework Remote Component
Now that the treatment of the message protocol for
communications between the user applications and the display application had
been incorporated into the mC framework it became time to restructure the
Remote Component to treat the interface to the Display application using the
same Ada packages and methods as those for other User applications.
This took me longer than expected and is not yet
finished. Mostly however because some
features were also added that are not strictly related to employing the user
application Remote methods in common with the display interface to avoid the
duplication that occurred when the cloned user display components were moved
into the framework Remote component.
Four of these were
1) to get
application App3 that contains C and C++ user components up-to-date after
having been ignored for quite some time while a number of framework changes
were being made.
2) to use Windows
interfaces to check which of the other applications of the configuration of the
current PC are running,3) to use Windows to do the same for a different PC in the configuration, and
4) to send a Remote Register Complete topic between running user applications to indicate to a connected application that the sending application has completed its Remote Registration with the receiving application. Sometime in the future this can be used to avoid transmitting other topics until the sending application is able to determine that the connected application has completed its remote registration with the sending application and hence is ready to treat remote topics.
Another feature to be added will be to monitor the
communications between the user application and the display application to
check that the two applications remain connected or, if become reconnected, to
continue as best as possible from where communications was lost. Yet another (structure only change) will be
to package the remote registration procedures into a Registration unit for
easier recognition of their common rationale.
An unrelated additional feature will be to have the first
running application of the configuration launch the others.
As part of this change, the configuration file was changed
to have a user applications portion and a display applications portion so that
one such file can be used both by the user application framework and by the
display application. The user
application framework needed this change as part of incorporating the treatment
of the display interface via the same methods as the user applications.
While making these changes an error was detected where the
application identifier was treated (in various places) as an index into arrays
of data concerning applications of the configuration and connected applications. This previously had not become apparent
since the debug cases had been for user applications 1, 2, and 3. When I added the display application for
common treatment, I separated the possible values for the identifiers into one
range values for user applications and another range for display
applications. Thus the display
application came to be assigned an identifier of 15 while the arrays were sized
as 1 to 5 so that a direct index of the identifier could no longer be
used. Further, I could find this sort
of thing better if I switched app 2, for instance, to app 5 to have gaps in the
user app identifiers which I will need to do.
Above is a figure showing how the user Display components
were previously moved into a common framework Display component.
The following second figure illustrates how the Display
interface now uses the same Methods as the user components and how, in the
future, it will use the same communications Monitor package and package the
remote registration into its own unit.
The new diagram is meant to show that the Remote Component
with its thread (the top middle oval to indicate the thread – enclosed icon to
indicate the Ada package) includes inputs from and outputs to the tables and
reads the Transmit, Receive and Watch Queues upon receiving a wakeup event
(that is, the notify of a dataless topic that is published as part of adding
the entry to the queue).
The Transmit Queue is written via the publish of a topic
from various components while the Receive Queue is written to contain messages
received from other applications including the Display App via the receive
ports of the MS_Pipe and WinSock communication methods that each have a thread
that blocks to wait for a new message from its associated application.
Received display messages are passed to the Display
subpackage that converts them to instances of the Display Event Request topic
message and then publishes them for distribution. In this case the Remote component also queues to the Transmit
Queue.
A received Display Event Response topic (dequeued from the
Receive Queue) as well as an unsolicited Display Command topic message are
treated via Display as received by the framework (while running under the
publishing component's thread). The
Display package supplies the Display protocol header in place of that of the
Topic protocol and transmits to the display app via the selected Remote Method.
Hence the Display package is the Display Protocol to Topic
Protocol converter and vice versa. This
design, as mentioned in the previous post, allows for the use of other
protocols to be added; such as an A661 Protocol. Only another protocol converter need be added, just as can be done
for communication methods, by adding another supported method to be selected by
the Method package. When this becomes
necessary a protocol selector package should be added to invoke callback
procedures of the particular converter package as is done by Method to select
those of MS_Pipe or WinSock. Other
communication drivers can, of course, be added as well where Method would then
also contain callback procedure entries to the new method.
Monitor / Registration
The Monitor package will be discussed after monitoring of the user-display application traffic has been implemented. The Registration package will just be grouping the remote register procedures within such a package.
Windows Interface to Detect Running Applications
There are supposed to be newer Windows interfaces to return whether a particular process (that is, application) is running on the current PC or on a named one. Others to start a process on the current PC.
Since these interfaces don't seem to be available via the GNAT libraries (and certainly not via Win32Ada that contains interfaces to much older versions of Windows), I attempted to access them through a Visual C++ function and link it via gnatlink with the rest of exploratory project. After various attempts I put that aside to return to later.
I can, however, detect if a specific application is currently running via the Windows interfaces provided with GNAT and Win32Ada.
BOOL WINAPI
EnumProcessModules
( __in HANDLE hProcess,
__out HMODULE *lphModule,
__in DWORD cb,
__out LPDWORD lpcbNeeded
);
can be used. To do
this, the appRunningC.c function was created and imported to Ada via
pragma
Import(C, App_Running, "appRunningC");
The Ada declaration used was
function
App_Running
(
Application : in Interfaces.C.Strings.Chars_Ptr
) return
Interfaces.C.char;
where the Chars_Ptr is used to point to a null terminated
character array containing the name of the application along with its path as
obtained from the Apps-Configuration.dat file.
The returned character is typecast to a byte value to indicate whether
the application is running or not.
The code of appRunningC is
#include <mC-ItfC.h>
#include <ctype.h>
#include <psapi.h>
#include <stdio.h>
#include <string.h>
#include <tchar.h>
#include <windows.h>
// Microsoft Help comment:
// To ensure correct resolution of symbols,
add Psapi.lib to TARGETLIBS
// and compile with -DPSAPI_VERSION=1
// My comment:
// For GNAT, include
C:\GNAT\2011\lib\gcc\i686-pc-mingw32\4.5.3\libpsapi.a
// in the gnatlink command.
void strConvert( char * inStr, char * outStr )
{ // Convert string to all
lower case while switching any forward slash
// in pathname to a backward slash
int i = 0;
while (inStr[i] != 0)
{
if (inStr[i] == '/')
{ outStr[i] = '\\';
}
else
{ outStr[i] = tolower(inStr[i]);
}
i++;
} // end loop
outStr[i] = 0; // attach trailing null
} // end method strConvert
int MatchModulePathname( DWORD processID, char * Application )
{ // Check application
name at beginning of module list for process for match.
HMODULE hMods[1024];
HANDLE hProcess;
DWORD cbNeeded;
unsigned int i;
// Get a handle to the process (that is, application).
hProcess = OpenProcess( PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ,
FALSE, processID );
if (NULL == hProcess)
return 0; // false
// Get a list of all the modules in this process.
if ( EnumProcessModules(hProcess, hMods, sizeof(hMods),
&cbNeeded) )
{
// Only the first name will contain the application name of the process.
TCHAR szModName[MAX_PATH], ModName[MAX_PATH];
// Get the full path to the module's file.
if ( GetModuleFileNameEx( hProcess, hMods[0], szModName,
sizeof(szModName) / sizeof(TCHAR)) )
{
strConvert( szModName, ModName );
// Check if module name with path matches that of the Application.
if (strcmp(Application,ModName) == 0)
{
// Release the handle to the process and return
found.
CloseHandle(
hProcess );
return 1; // true
}
}
} // end if ( EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded) )
// Release the handle to the process.
CloseHandle( hProcess );
return 0; // false
} // end method MatchModulePathname
extern "C" char appRunningC(char * Application )
{ // Determine if
Application is running.
DWORD aProcesses[1024];
DWORD cbNeeded;
DWORD cProcesses;
unsigned int i;
// Get the list of process identifiers.
if ( !EnumProcesses( aProcesses, sizeof(aProcesses),
&cbNeeded ) )
return 2; // Application cannot be found due to problem
// Calculate how many process identifiers were returned.
cProcesses = cbNeeded / sizeof(DWORD);
// Examine module names for each process for match to input.
char AppPath[MAX_PATH];
strConvert(Application, AppPath);
for ( i = 0; i < cProcesses; i++ )
{
if (MatchModulePathname( aProcesses[i], AppPath ) == 1)
{
return 1; // Application found
}
}
return 0; // Application not found
} // end method appRunningC
This routine is currently being used to check whether each application of the configuration (other than the application that is doing the checking) is running. This can be changed to pass a list of all of the other applications in the configuration to appRunningC and return an array of those that are running to avoid multiple calls to the OpenProcess and EnumProcessModules functions.
GNAT Compiler
While making the various changes it seemed that GNAT GPS no longer rebuilt changed separates. This seemed to be the case after the addition of the C function for use by the Ada framework Remote-Method-MS_Pipe package to interface to Windows to check what applications of the configuration were running. Therefore I had to start compiling the packages that I changed with statements such as
c:\gnat\2011\bin\g++ -c -g
-IC:\Source\EP\UserApp\Try10 -IC:\Source\EP\Util
-IC:\Win32Ada\src -aLC:\Source\EP\ObjectC
C:\Source\EP\UserApp\Try10\mc-message-remote.adb -o mc-message-remote.o
in a batch file (where the indented lines are actually part
of the first line) as well as the .c file.
The -g switch includes debug symbols in the .o object file to be
included in the eventual .exe file by gnatlink.
Remote Register Complete topic
The implementation of the Remote Register Complete topic took longer than expected. This topic is both produced and consumed by the Remote framework component. However, the instance produced by the Remote component of one application is meant for that of another application.
Therefore the topic used the recently added Requested Delivery method of message exchange so each application could register to consume a delivery identifier that equaled its application identifier. The Remote component of the publishing application supplies the delivery identifier of the application to receive the topic as it publishes the topic. Therefore there are as many possible publishers of the topic as there are applications and the same number of consumers.
This is as the message exchange delivery method was intended to treat. The difference in this case was that the same component was both a publisher and a consumer so its Role was Either. Since this Role hadn't been previously used, this caused a few problems that had to be tracked down and corrected. However, another problem caused confusion in identifying what problems were caused by this new role.
That was due to one application of a pair seeming to transmit its instance of the topic to the other application and that application seeming to create its instance but not doing the transmit. Finally (I must be getting old) I identified the reason.
The registering of a delivery identifier to be consumed involves, as has been done in the past, two variables where the second one is referenced in the Register call. The first of these is an array of identifier pairs indicating five possible pairs of delivery identifiers that indicate the identifiers the component is to treat. Each pair indicates a range of values where the second entry can be 0 to indicate that there is no range; only the particular identifier of the first entry. The second variable contains the count of the number of pairs to examine and the doubly indexed array of values. Normally, of course, the count will be one.
Registering to consume the Remote Register Complete topic, the Remote Install procedure declared
Delivery_Id_List
--| Treat
all "Remote Register Complete Topic" messages with ids of the app
:
mC.Itf_Types.Delivery_Id_Array_Type
:= ( ( 1,
1 ), ( 0, 0 ), ( 0, 0 ), ( 0, 0 ), ( 0, 0 ) );
Delivery_Id
--|
Deliver topic when identifier is that of local app
:
mC.Itf_Types.Delivery_List_Type
:= (
Count => 1,
List => Delivery_Id_List );
mimicking what had been done in the past for other Requested
Delivery and Delivery Identifier delivery methods. Except in those cases, the Delivery_Id_List object was declared as
constant since the identifiers were known at compile-time.
In this case, since the pair of delivery ids depends upon the application's own identifier – that isn't known until initialization-time, the value was initialized as shown above and then modified just prior to the Register call to contain the running application's id as shown below.
Delivery_Id_List := ( ( Integer(Local_App.Id), Integer(Local_App.Id) ),
( 0, 0 ), ( 0, 0 ), ( 0, 0 ), ( 0, 0 ) );
mT.Remote_Register_Complete_Topic.Request.Data.Register
(
Participant => Component_Main_Key,
Detection =>
mC.Itf_Types.Event_Driven,
Role => mC.Itf_Types.Either,
Delivery_Id => Delivery_Id,
Callback =>
Monitor.Treat_Register_Complete'access,
Access_Key
=> Remote_Register_Complete_Topic_Access_Key,
Status => Data_Status );
where the Count in the Delivery_Id variable remains as 1.
Since this kind of Register (other than the use of Either for the Role instead of Consumer) had been working on the order of a year or more it didn't occur to me that this late resetting of the Delivery_Id_List could be causing any problems.
However, I finally determined that the compiler was ignoring the resetting of the value to be used by Delivery_Id in the object code that it generated. So both applications of the pair were specifying that they would consume delivery id 1. I changed the source code to
declare
Delivery_Id_List
--| Treat
all "Remote Register Complete Topic" messages with ids of the app
:
mC.Itf_Types.Delivery_Id_Array_Type
:= ( (
Integer(Local_App.Id), Integer(Local_App.Id) ),
( 0,
0 ), ( 0, 0 ), ( 0, 0 ), ( 0, 0 ) );
Delivery_Id
--|
Deliver topic when identifier is that of local app
: mC.Itf_Types.Delivery_List_Type
:= (
Count => 1,
List => Delivery_Id_List );
begin
mT.Remote_Register_Complete_Topic.Request.Data.Register
(
Participant => Component_Main_Key,
Detection =>
mC.Itf_Types.Event_Driven,
Role => mC.Itf_Types.Either,
Delivery_Id => Delivery_Id,
Callback =>
Monitor.Treat_Register_Complete'access,
Access_Key =>
Remote_Register_Complete_Topic_Access_Key,
Status => Data_Status );
end;
This fixed the problem with Remote of each application transmitting the instance of the topic that was published by it to the other application of the pair where it was then delivered to the
Treat_Register_Complete procedure of the Monitor subpackage.
This might be a case that could have been fixed by pragma Volatile if it had been recognized. That is, an example of the same thing that can happen when an object is changed by one thread and read by another without the new value being used by the code that was generated by the compiler due to its not recognizing that it could be changed. (Or, as happens when an address is passed to a procedure that then uses it to change a value used later by the calling routine.)
The remaining gotchas were then immediately found and fixed.
No comments:
Post a Comment