http://www.developer.com/

Back to article

Inter-Application Communication in BREW


July 23, 2004

This time, we will discuss inter-application communication—a rather strange topic taking into account the BREW environment, where small, standalone applications prevail. Once BREW becomes more mainstream and handhelds more powerful, applications tend to be more complex, leaving behind the old monolithic approach and making use of more sophisticated design choices.

That's why this article will try to answer questions such as:

  • How can two or more processes exchange information in BREW?
  • When should you use IAC? What are the design considerations?
  • How does one create and use services in BREW?

Why Do We Need Inter-Application Communication?

There are various reasons, usually implying design decisions.

The simplest case is when you need to pass/extract data to/from other applications (a suite of applications is a good example).

A more complex situation is a service; for example, an XML parser. There is little need for a parser to share its state (if any) with other processes. An extension fills the bill here, no doubt—an extension used by all the interested parties in this service.

Extensions in BREW are like "in-process" components in COM, being loaded in the space (context) of the caller. This means that our extension will have the same life span as the caller and every call to

IExtensionCls* e = 0;
ISHELL_CreateInstance(shell, AEECLSID_EXTENSION_CLS, (void **)&e);

should generate a new instance of IExtensionCls. There is no inter-application communication in this case, but rather a shared "execution model."

What happens if our service has to share state (a database, for example)? An extension doesn't fill the bill any more because there are obvious limitations, for example:

  • The extension should be a singleton in this case and COM singletons have well-known drawbacks [1]
  • All the memory allocated in an extension is tied to the caller context. Destroying the context (calling application) implicitly destroys the callee.
  • Raw pointers have to be passed over process boundaries—a technique allowed today but forbidden in future versions of BREW implementing memory protection between processes (the same happened in the transition fro Win16 to Win32, for example)

The best design in this case is to encapsulate the data in a process of its own, accessible via a well-known interface that can be modeled as an extension. Shortly, we will present such an example.

Background Applications

BREW 2.0 introduced a new kind of state for an application: background, in addition to active and suspended states. Background applications are discussed in detail in a Qualcomm document [2]—what follows is just a short recap:

An applicationm once startedm is in the active (running) state—capable of receiving all events, including key events (the active application owns the display). At any time, the application can be interrupted and be put in suspended state. A suspended application can become active again (resume).

Background applications are different; they have no way to interact directly with the user but they are in a running state. This makes them extremely attractive to be used as global services processing data in the background.

An application can be in the background for a limited period of time too—when receiving an event from another application. Global data repositories can use this feature to avoid some of the inherent limitations of background applications, which are otherwise notable. Other than not being accessible directly by the user, [2] mentions: " ... handsets running background applications will experience increased power drain due to the inability to enter standby mode; consequently, prolonged background execution should be avoided for its deleterious effects on handset battery lifetime. Some OEMs may limit the capabilities available to a background application; for instance, while running in the background, applications may be unable to open sockets or play ringers."

Exchanging Information

INotifier is an interface that might be used for initiating communication between two or more applications. Because INotifier will be discussed in more detail in a future article, we will concentrate our presentation on what IShell provides.

We can roughly qualify all the IShell calls of type

ISHELL_X(AEECLSID ....)

as inter-application communication. Once you know the clsid of another applet you can start exchanging information—;almost. Let's analyze for a moment the most useful methods (please note that some of these methods were enhanced—carefully read the documentation for the most suitable version and availability).

ISHELL_StartApplet() allows you to asynchronously start a different process from inside another process. The running process receives the EVT_APP_SUSPEND event immediately before the new application is started.

ISHELL_PostEvent() posts an asynchronous event to a specific applet, ISHELL_SendEvent being its synchronous counterpart. Because BREW is an event-based system, sending and posting events should be seen like the main vehicle for inter-application communication.

But, now comes the problem: An event cannot accommodate too much additional data to be passed through. Some developers might use the last parameter (an unsigned long) to pass a raw pointer from a process A to process B:

char* address = (char*)MALLOC(100);
ISHELL_PostEvent(shell, clsIdB, EVT_USER, command , address);

Unfortunately, this technique exhibits all the dangers presented before: "address" lives in A's context. Once A is destroyed, "address" is no longer valid.

What we need is a clean way to marshal data from process A to be consumed in process B. One way is to use a file in the SHARED directory as a pipe. PostEvent is simply used to signal the availability of data (it can describe meantime the size of the data chunk). For example, we might have in process A:

writeData2Pipe(&size);
ISHELL_PostEvent(shell, clsIdB, EVT_USER, command , size);
Process B consumes data in:
boolean CPPApp::OnEvent(AEEEvent eCode, uint16 wParam, uint32 dwParam)
{
   switch (eCode)
   {
      case EVT_USER:
         switch(wParam)
         {
         case command:
            readFromPipe(dwParam );    //dwParam is the size of data
            return true;
         ... ....
         }
... ..
   }
}

Of course, a more comprehensive protocol might be used, but this is the general idea.

Furthermore, A and B don't access the protocol directly, but via an extension hide all the implementation details.

Other things to have in mind:

  • Suspend/resume the caller or simply start/stop it? Take into account that launching any application (in the background or not) consumes memory; releasing the caller might free precious space.
  • Should the callee be a background application, become the active process, or simply be resurrected and put in the background for the duration of the call? All these behaviors are possible (see above for more details) and choosing one over the other is simply a matter of design and requirements.

A Short Example

The example consists of two applications (<cppapp> and <cppapp1>) and one extension (<extension>). <cppapp> contains the same code as [3] and is practically a client for <cppapp1>. <extension> is the component used by both application, hosting the pipe. The interface <extension> exposes is minimal:

QINTERFACE(IExtensionCls)
{
   DECLARE_IBASE(IExtensionCls)
   int  (*PipeWrite)(IExtensionCls * po, const char* data);
   int  (*PipeRead)(IExtensionCls * po, char* data);
};

and the implementation trivial:

static int ExtensionCls_PipeWrite(IExtensionCls * po, const char* v)
{
   IFileMgr* fmgr = 0;
   IShell* s = ((AEEApplet*)GETAPPINSTANCE())->m_pIShell;
   int err = ISHELL_CreateInstance(s, AEECLSID_FILEMGR, (void **)&fmgr);
   if(err != SUCCESS) return err;
   const char fn[]=AEE_SHARED_DIR"/pipe";
   IFILEMGR_Remove(fmgr, fn);
   IFile* fl( IFILEMGR_OpenFile(fmgr, fn , _OFM_CREATE));
   err = fl ? SUCCESS : EFAILED;
   if (fl)
   {
      IFILE_Write(fl, v, 32);
      IFILE_Release(fl);
   }
   IFILEMGR_Release(fmgr);
   return err;
}
static int ExtensionCls_PipeRead(IExtensionCls * po, char* v)
{
   IFileMgr* fmgr = 0;
   IShell* s = ((AEEApplet*)GETAPPINSTANCE())->m_pIShell;
   int err = ISHELL_CreateInstance(s, AEECLSID_FILEMGR, (void **)&fmgr);
   if(err != SUCCESS)  return err;
   const char fn[]=AEE_SHARED_DIR"/pipe";
   IFile* fl( IFILEMGR_OpenFile(fmgr, fn , _OFM_READ));
   err = fl ? SUCCESS : EFAILED;
   if (fl)
   {
      IFILE_Read(fl, v, 32);
      IFILE_Release(fl);
   }
   IFILEMGR_Release(fmgr);
   return err;
}

Obvious improvements would be the use of smart pointers to access BREW interfaces (see BrewIPtr in [4]) and refactoring the two methods, but this is beyond the scope of this article. This code is based on a fixed-size pipe, but the variable size can be easily implemented as discussed previously.

<cppapp1> has two methods to initiate communication:

static void placeCommand(AEECLSID cls, int command, uint32 params=0)
{
   IShell* s = ((AEEApplet*)GETAPPINSTANCE())->m_pIShell;
      ISHELL_StartApplet(s, cls);
      ISHELL_PostEvent(s, cls, EVT_USER, command , params);
}
   static void shareData()
   {
      IExtensionCls* e = 0;
      IShell* s = ((AEEApplet*)GETAPPINSTANCE())->m_pIShell;
      if(ISHELL_CreateInstance(s, AEECLSID_EXTENSION_CLS,
                               (void **)&e) != SUCCESS)
            return ;
      const char v[] = "TEST";
      IEXTCLS_PipeWrite(e, v);
      IEXTCLS_Release(e);
   }

Commands are decoded in the EVT_USER section of the HandleEvent method of <cppapp>. <cppapp> has only one relevant method:

static void readSharedData()
   {
      IExtensionCls* e = 0;
      IShell* s = ((AEEApplet*)GETAPPINSTANCE())->m_pIShell;
      if(ISHELL_CreateInstance(s, AEECLSID_EXTENSION_CLS,
                               (void **)&e) != SUCCESS)
            return ;
      char v[32];
      IEXTCLS_PipeRead(e, v);
      IEXTCLS_Release(e);
   }

How it works:

<cppapp1> asks <cppapp> to execute a command like:

placeCommand(AEECLSID_CPPAPP, 1, 700);

This is a very simple protocol using only one unsigned long paramter. <cppapp> will consequently execute:

test1(dwParam);
where dwParam = 700.

More complexes cases need more data being transferred:

placeCommand(AEECLSID_CPPAPP, 2, (uint32)address);
shareData();

<cppapp> simply reads the data passed to it and responds in a similar manner:

readSharedData();
   ISHELL_PostEvent(((AEEApplet*)GETAPPINSTANCE())->m_pIShell,
                      AEECLSID_CPPAPP_1, EVT_USER, 1 , 0);
   ISHELL_CloseApplet(((AEEApplet*)GETAPPINSTANCE())->
                        m_pIShell, false); 

Please note that <cppap1> is a minimal example and provides no support for suspend/resume.

Downloads

Download the example files here.

References

  1. Don Box, Keith Brown, Tim Ewald, Chris Sells: Effective COM: 50 Ways to Improve Your COM and MTS-based Applications, Addison-Wesley, 1998
  2. Background Applications: https://brewx.qualcomm.com/bws/.../docs/bg_apps.pdf
  3. C++ Idioms in BREW, Part 2: http://www.developer.com/ws/brew/article.php/3370971
  4. Small Memory Allocation, Part 3 http://www.developer.com/ws/brew/article.php/3340611

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date