Handling Unsolicited Messages

This section describes the programming required to allow a client program to handle unsolicited messages sent from the runtime application to the client. Examples of such messages are text messages sent to a session via the SendMessage LDL command or the :SMG colon command, output messages and accepts from Reports and the output from most other colon commands. For example, :LIS, :HEL, etc.

The following needs to be done to implement this functionality:

When the server has a message that is destined for this client session, it is passed to the corresponding IMessages object, where it can be handled by the client e.g. displayed to the user.

An optional step is to implement an ICallBack object to contain the implementation of the message handlers. This allows for a clean object-oriented design, where the IMessages class acts simply as an interface class that receives the messages and then passes them on to an ICallBack object that contains the full implementation for handling the display of messages and user input for Accepts. The example code below includes this structure.

IMessages Object

To be able to receive messages from an AB Suite user application you need to implement the IMessages interface that is defined in the NGSystem.tlb. This type library can be found in the AB Suite runtime Bin or Include folder. When a reference is added to this type library file, Visual Studio generates an interop reference to NGLINC. The Visual Studio Object Browser reveals all the interfaces that are available.

Here is an example C# implementation for this class:

class MessageObject : IMessages
 {
// This message object will construct a callback object that
// will do all the work. The IMessages methods will
// just pass the objects to the callback object.
 //

protected ICallBack m_CallBack;

 public MessageObject( SampleClient pCaller )
{
m_CallBack = new CallBackObject( pCaller );
 }

 #region IMessages Members

 public void PutAccept( IAccept pIAccept, string prompt )
{
m_CallBack.ReceiveAccept( pIAccept, prompt );
}

public void PutCompletion( int Status )
{
 m_CallBack.ReceiveCompletion( Status );
 }

 public void PutMessage( string message, MSGTYPE MSGTYPE )
{
m_CallBack.ReceiveMessage( message, MSGTYPE );
}

public void SetCallBack( ICallBack callBack )
 {
m_CallBack = callBack;
 }

 #endregion
}

ICallBack Object

An ICallBack class should be implemented to handle the received messages. This is where most of the work is done to process the received messages.

Here is an example C# implementation for this class. This is for a simple console client. Received messages are written to the console and Accepts are handled by reading input from the console.

class CallBackObject : ICallBack
{
 //
// This class handles the messages that the SampleClient receives
 // from the Sample system. In the implementation here, it will just write
 // to and read from the command-line.
 //

protected SampleClient m_Caller;

 public CallBackObject( SampleClient pCaller )
{
 m_Caller = pCaller;
 }

 #region ICallBack Members

 //
// This method receives an IAccept object. Here, the method reads the user input
 // from the command-line and sends it back to Sample through the Reply method
 //
 public void ReceiveAccept( IAccept pIAccept, string prompt )
{
Console.WriteLine( "CallBackObject received an Accept request" );
Console.Write( prompt );
string lReply = System.Console.ReadLine();
pIAccept.Reply( lReply );
 }

//
 // This method is called after the End-Of-Job notification from the Report. It is
 // used here to tell the SampleClient we are finished.
//
 public void ReceiveCompletion( int pStatus )
{
Console.WriteLine( "CallBackObject received a Completion indicator \'{0}\'", pStatus );
 m_Caller.OnFinished();
 }

public void ReceiveMessage( string theMsg, MSGTYPE MSGTYPE )
{
Console.WriteLine( "CallBackObject received a {0} message \"{1}\"", MSGTYPE, theMsg );
}

#endregion
}

Connect()

In the client start-up code, an instance of the IMessages class must be created (which in turn creates an instance of the ICallBack class). The IMessages object is then passed to the application as the first parameter in the Connect() call.

Here is an example C# implementation for this code:

// Create a IMessages implementation instance that handles
// the messages and Accept requests from the Sample system
IMessages myMsgObject = new MessageObject( this );
// Finally, connect to Sample, pass MessageObject onto Sample and
// store the SessionId.
ISegmentCycle mySampleInterface = ( ISegmentCycle ) myBusinessObject;
mySessionId = mySampleInterface.Connect( myMsgObject, false, false, null );

General Processing

Messages:

When the application has a message to send to this client, it calls PutMessage() on the corresponding IMessages object passing the message text as a string parameter.

PutMessage() passes the message text on to the ICallBack object via the ReceiveMessage() call where the message string can be handled in any way required, for example, displayed to the user.

Accepts:

When a report run from this client executes an Accept command the application calls PutAccept() on the client s IMessage object. It passes an IAccept object as the first parameter. PutAccept() passes the parameters through to ICallBack.ReceiveAccept(). The implementation here is expected to take input from the user and return it to the report by calling the Reply() method on the IAccept object.

Completion:

When a report that has been run from this client goes to end-of-job the application sends a “completion” message to the client. It does this by calling PutCompletion() on the corresponding IMessages object.

Using Messages COM Object to handle Unsolicited Messages

In addition to the methods that have been described, another way of handling unsolicited messages is using Messages COM object. The Messages COM object implements the IMessages interface required to be passed in the Connect method of the ISegmentCycle. You do not need to implement the IMessages interface as described in the previous sections. You must only implement the ICallBack interface in a class as described previously.

You should instantiate the CallBackObject class in your code and the pointer of the object must be passed to the SetCallBack method of the IMessages object. This ensures that the messages can be passed on to the CallBackObject by Messages COM object to be handled by you.

Following is the code and its description of using the Messages COM object to get user messages from an AB Suite system.

Native VC++ code

...
#import <…\bin\NGSystem.tlb> no_namespace raw_interfaces_only
class CallBackObject : public ICallBack
{

public:
...
HRESULT __stdcall ReceiveMessage(BSTR message, MSGTYPE type)
{
wprintf(_T("%d: %s\n"), type, message);
return S_OK;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
::CoInitializeEx(NULL, COINIT_MULTITHREADED);

HRESULT hr;

CComPtr<IMessages> messenger;
hr = messenger.CoCreateInstance(_T("Messenger.Messages.2.0.1"));
CallBackObject callback;
hr = messenger->SetCallBack(&callback);

 CComPtr<ISegmentCycle> app;
hr = app.CoCreateInstance(_T("Sample.1"));
LONG sessId;

hr = app->Connect(messenger, VARIANT_FALSE, VARIANT_FALSE, _variant_t(NULL), &sessId);
...
}

In this code, the CallBackObject is the class that implements the ICallBack interface as described in the ICallBack Object section previously. The implementation of ReceiveMessage is shown here for clarity.

Note: Unlike >NET Framework for C#, VC++ libraries do not provide a wrapper over COM classes, which implements the IUnknown methods. This requires the IUnknown interface methods to be implemented in the CallBackObject class.

In the main method, the first line initializes the COM library for the main thread. The function also sets the concurrency model of the main thread. The concurrency model set is, Multithreaded. Refer to addendum for more information about the concurrency model of the application. An instance of the Messages COM object and an object of the CallBackObject class is created. The callback object is then passed to the SetCallBack method of the IMessages interface implemented by the Messages COM object. Subsequently, an instance of the AB Suite system is created and the Messages COM object is passed in the Connect method of the ISegmentCycle interface.

When AB Suite system sends a message through the Messages COM object stored in the AB Suite system, the Messages COM object forwards this message to the CallBackObject to be handled by CallBackObject. Similarly when there is an accept request by the system, the request is forwarded to the CallBackObject by the Messages COM object in the system.

Managed C# code

To use Messages COM object, the following assemblies must be added as references in the application: Interop.NGLINC and Interop.Messenger. These assemblies can be found in the bin folder of the AB Suite installation directory.

...
using NGLINC;

class CallBackObject : ICallBack
{
...
 public void ReceiveMessage(string theMsg, MSGTYPE MSGTYPE)
{
Console.WriteLine(MSGTYPE.ToString() + ": " + theMsg);
}
}
[MTAThread]
static void Main(string[] args)
{
IMessages messenger = new Messenger.MessagesClass();
 ICallBack callback = new CallBackObject ();
 messenger.SetCallBack(callback);

ISegmentCycle testApp = (ISegmentCycle)Activator.CreateInstance(Type.GetTypeFromProgID("Sample.1"));
 int sessionId = testApp.Connect(messenger, false, false, null);
...

}

In the above code, the CallBackObject is the class that implements the ICallBack interface as described in the “ICallBack Object” section above. The ReceiveMessage implementation is shown here for clarity.

The MTAThread attribute of the main method sets the COM threading model of the main thread of the application to multithreaded apartment (MTA). Refer to addendum for more information about threading model of an application. In the main method, an instance of the Messages COM object is created by using the MessagesClass class exposed by the Interop.Messenger.dll library. Then an object of CallBackObject class is created and passed in the SetCallBack method of the IMessages interface implemented by the Messages COM object. Finally, an instance of the AB Suite system is created and the Messages COM object is passed to the Connect method of the ISegmentCycle interface.

Whenever AB Suite system sends a message through the Messages COM object stored in the system, the Messages COM object forwards this message to the CallBackObject to be handled by it. Similarly when there is an accept request by the system, the request is forwarded to the CallBackObject by the Messages COM object in the system.

Addendum

The concurrency model or threading model of the thread that creates the Messages COM object must be multithreaded. By making the concurrency model of the thread multithreaded, the Messages COM object is created in a multithreaded apartment (MTA). This is required because Messages COM object is passed to the AB Suite system COM object and accessed by other COM objects such as Report Session Manager, which are created in multithreaded apartments. The thread that creates the Messages COM object should be in multithreaded apartment.

If the concurrency model of the thread is set to single then Messages COM object is created in a single threaded apartment (STA). This can lead to cross-apartment synchronization problems when the object is passed to processes initialized in multithreaded apartments. These can cause the AB Suite system code to stop responding. For example, a report can hang while sending messages using the Messages COM object sent in the Connect method of the ISegmentCycle.

To ensure that the application does not hang, the thread that creates the Messages COM objects need to be set to multithreaded concurrency model or threading model. In native VC++ code, do not use CoInitialize(NULL) function or COINIT_APARTMENTTHREADED concurrency model in CoInitializeEx() function to initialize the COM library in the thread that creates the Messages COM object. Use the CoInitialize(NULL) function as mentioned in the code. In C# code, does not use STAThread attribute in the main method or set the Thread.ApartmentState property to ApartmentState.STA in the thread that creates the Messages COM Object. Use MTAThread attribute in the main method or set the Thread.ApartmentState property to ApartmentState.MTA in any thread other than the main thread of the application. Refer to http://msdn.microsoft.com/en-us/library/system.threading.thread.apartmentstate.aspx for more information.