MiniAppFrame: A class for calling MDI processes from programs without an APPLICATION frame, for example, from Nettalk Web Services

Tags: #<Tag:0x00007fc0defdd648> #<Tag:0x00007fc0defdd558> #<Tag:0x00007fc0defdd468> #<Tag:0x00007fc0defdd3a0>

Some months ago, I had to publish data generated by our accounting system as Web Services. Nettalk has a very well-designed solution for this, and the existing process and reports already created tabular data that could be easily published.

The problem was that all those processes and reports are implemented as MDI windows, and you can’t call an MDI process from a Nettalk thread without an application frame.

Some solutions would be: convert those processes to use non-MDI windows, have two windows and open one depending on how they are called, have two versions of the process, decouple the data generation part from the UI window; but all those required extensive changes to the processes, or messing with complex user facing UI.

Since potentially hundreds of processes may need to be published, this class was created as an alternative to implement this requiring minimum changes to the existing processes.

It is used like this:

In the caller web service method:

WebServiceRequestGroup GROUP
ClientNumber             LONG
StartDate                DATE
EndDate                  DATE
                       END
...
!ServiceMethod  routine
  MiniAppFrame.InitCaller
  MiniAppFrame.StoreGroupValues(WebServiceRequestGroup)
  MiniAppFrame.Start(SomeMdiProcess) !Calls SomeMdiProcess in a new thread 
                                     !and waits for it to finish
  MiniAppFrame.RetrieveQueue('Result',WebServiceResultQueue)  

In the child SomeMdiProcess:

!EVENT:OpenWindow (or where appropriate)
  IF MiniAppFrame.IsActive()
    MiniAppFrame.InitChild
    LOC:ClientNumber = MiniAppFrame.GetValue('ClientNumber')
    LOC:StartDate    = MiniAppFrame.GetValue('StartDate')
    LOC:EndDate      = MiniAppFrame.GetValue('EndDate')
    POST(EVENT:Accepted,?StartButton)
  END

!ThisWindow.Kill (or where appropriate)
  IF MiniAppFrame.IsActive()
    MiniAppFrame.SetValue('Total',LOC:Total)
    MiniAppFrame.StoreQueue('Result',SomeQueue)
  END

A new thread is started with the MDI process. A value storage object is created by the caller to exchange data, shared only by this pair of caller and child threads. Also, the first time .Start is called, a background thread with an application frame is created.

Developing this class has been a very interesting and teaching exercise. For example, I found by testing that GlobalLong += 1 is not thread safe and it must be executed inside a Critical Section.

The first thing to figure out was how to call the MDI processes. Obviously, just calling it from the Nettalk thread without an app frame doesn’t work. Opening an APPLICATION window before calling also doesn’t work, it must be in another thread. Opening an APPLICATION window for each call doesn’t work, because there can only be one app frame per Clarion exe (error: Unable to open APPLICATION (APPLICATION already active)). The solution for this was to create a background thread with the app frame and keep it open while the exe is running; method Kill must be called when the exe is about to end.

Then, a way for the caller thread to know when the child thread ended was needed. By experimentation I found that INSTANCE(SomeThreadedVariable,ChildThreadNumber) returns 0 when the child thread ends. Curiously SomeThreadedVariable doesn’t have to be declared in the called procedure, it can exist only in the class module.

To exchange data between the caller and child threads, I first did some tests using NOTIFY(,,parameter), but I couldn’t make it work reliably, as I couldn’t find a way for the caller thread to know when the ACCEPT loop of the child thread was ready to process events. The alternative was to use INSTANCE, which allows one thread to write to the other thread’s instance of a threaded variable.

The first version of the class used these two techniques: the child thread waited with a LOOP sleep until the caller thread set the value in the threaded variable. The caller thread checked in an EVENT:Timer until INSTANCE() returned zero.

Then I learned from Mark Golberg that a global threaded class Destruct method can be used to precisely know when a thread ends, and from Bruce Johson got the idea to use a global array of LONGs to keep track of the running threads.

In the current version, the child thread can access the caller thread’s instance of the variable by looking up its caller’s thread number in the array Caller[Thread()], and uses NOTIFY() to inform the caller thread that it has ended.

This combination outperforms the previous techniques, but the class keeps them as fallback, in case something unexpected happens, like a thread number larger than the array size.

The first version of the class could only pass a LONG to the child class using INSTANCE. I tried to pass a STRING but I couldn’t make it work. I used this to pass an address to a jFiles object and it did work, but to continue the learning experience, I implemented a private (module) class ValuesQueueCSClass with a queue of ANYs, a Critical Section, and methods to get and set values in a thread safe way.

Having the value storage object working, it was trivial to add methods to store and retrieve GROUPs and QUEUEs using FlatSerializer, from either the caller or the child threads.

The class, with some documentation and tests, is available at GitHub.

Feedback is welcome.

2 Likes