http://www.developer.com/

Back to article

A Generic Connection Framework for BREW


July 30, 2003

Preamble

The article, structured in two parts, tries to establish a common ground for I/O and network operations on BREW and encapsulate this commonality in a framework. Such a framework is used in J2ME to insulate the developer of protocol implementation details, to offer the needed flexibility in supporting new devices and protocols, as well as to define a uniform way of accessing different resources. These reasons are compelling enough to investigate a similar approach for BREW—with an additional challenge: BREW is asynchronous.

First, we will briefly examine the intricacies of the asynchronous programming model and develop some tools to help in our endeavor. As (unfortunately!) there is nothing for free, we'll easily discover that compilers might cruelly dictate the rules of the game. Finding our way in the maze, putting the pieces together, and creating the framework are the fun of the day.

As the final goal is a framework for I/O and network resources handling, we will introduce two simple BREW-specific encapsulations—one for the file manipulation and another one for HTTP—to be used in conjunction with the framework. This is a good opportunity to review some implementations aspects of these interfaces.

Laying the Foundation

BREW is asynchronous by nature—a necessity derived from its design (single threaded, event based, with a watchdog checking the healthy of the system periodically approximately every 60 seconds). This means that otherwise blocking calls are delayed and executed in a non-blocking manner at a later time, control being relinquished after that to a callback function registered at call time (see Command pattern, BeginXXX/EndXXX idiom in .NET, some Observer pattern variants, and so forth).

One example might be derived from the execution of a long, blocking task.

void Caller::executeLengthyOp()
{
  Resource r;
  r.executeTask();
}

This is strictly forbidden in BREW and the famous watchdog might reset the phone. That's why developers have to split the code exceeding the watchdog interval in small chunks, like this:

void Resource::execute()
{
  doOneTimeOperation();
  giveBackControlAsynchronously (&Resource::execute);
  return;    //to main event loop
}

I purposely emphasized the fact that myTaskExecute() has to return control to the main loop (HandleEvent). The interesting part here is giveBackControlAsynchronously (). This method registers (using BREW specific functionality) a callback (in this example re-registers) for later execution or posts an event and quits—an example of non-blocking, asynchronous execution.

Please note this is perfectly valid, too [3]:

void Resource::execute()
{
  giveBackControlAsynchronously (&Resource::execute);
  doOneTimeOperation();
  return;    //to main event loop
}

The above is a user-driven, non-forcing usage pattern (for a complete discussion see [1]).

There is another, system-driven, idiom involving the handling of system resources. ISOCKET_Connect is a good example of a prepackaged asynchronous call. Behind the scenes, it does lots of work and when it finishes it passes you back the result.

ISOCKET_Read works in pair with ISOCKET_Readable like:

int32 rv = ISOCKET_Read(piSock_, buf_, ds_->bufSize);
if  (rv == AEE_NET_WOULDBLOCK) 
{
  ISOCKET_Readable(piSock_, (PFNNOTIFY)P::callbackHandler, &p_);
  doSometingElse();
  return;
}
else ....

The system informs us that a blocking operation is in effect and the only way to continue the reading operation is to register for later processing. What these two last examples have in common is that the system is in control. All tasks involving possible lengthy system resources manipulation should obey this idiom—but regrettably we don't live in a perfect world and exceptions are always possible.

Now, let's concentrate on the mechanics of an async call (forgetting for one moment the infamous watchdog) and make a brief comparison with a synchronous one:

void Resource::executeAsync()
{
  connect();
  return;    //to main event loop
}
void Resource:: connect ()
{
  String data = connectAndGetData ();
  if (data.isEmpty()
  {
    register (&Resource::connect) ;
  }
  else
  {
    useData(data);
  }
  return;    //to main event loop
}

versus:

void Resource::executeSync()
{
  String data = connectAndGetData ();
  useData(data);
  return;    //to main event loop
}

What we can immediately observe is the fragmentation of the code logic in the async version—scattered now in two functions. The second observation is that useData() has nothing to do with Resource class and it should be injected by the caller in the Resource space. And, finally, the third observation is that conectAndGetData() has to preserve the state between calls—and as in BREW static or global data are absolutely forbidden the only way to do it is to encapsulate the data in MyClass. The RAII—Resource Acquisition Is Initialization [2]—idiom seems to be the natural choice:

typedef void (Caller::*FND)(  );
class ResourceHandle
{
  ResourceHandle (FND f) : f_(f)
  {connect();}
  ~ ResourceHandle ()
  { releaseResources();  }

private:
  ResourceHandle (const ResourceHandle &);
  void operator=(const ResourceHandle &);
  String connectAndGetData();
  FND f_;
  void releaseResources;    //cancels the callback
  //"static" data used by connectAndGetData(), for example:
  String buffer_;
};

Usage:

void Caller::someFunction()
{
  ResourceHandle rh(&Caller::useData);
}

Nice, but pointless—rh is destroyed at the end of the someFunction() body and the callback is never called. A better method would be:

void Caller::someFunction()
{
  ResourceHandle * rh = new ResourceHandle(&Caller::useData);
}

We have now a behavior similar to the synchronous case, but with an extra problem—finding a way to delete rh to avoid a memory leak. There are two options:

  1. Make rh a private member of Caller—with the disadvantage that Caller has to keep track of ResourceHandle (and maybe other handles of other types).
  2. Create a central Handle repository that keeps track of the myriad of resources that might be used in the lifespan of the application. This looks more promising because all we have to keep track of in this case is the repository itself, once per application.

Now that we know how to deal with resources, let's go a little bit deeper—the callback registration mechanism level, namely. Because BREW is a C-based environment, it uses C functions for callbacks, passing the state through an opaque void* pointer. This usually translates into the following C++ idiom:

void Resource:: connectImpl ()
{
  register (&Resource::connect, this) ;
}

static void Resource:: connect (Resource * r)
{
  r->connectImpl();
}

There is an elegant way to get rid of the static method and use C++ callbacks:

void Resource:: connect ()
{
  p_.setCallback((&Resource::connect, this);
  register (&P::callbackHandler, &p_) ;
}

The mysterious P is actually an adapter, like:

template <class Y, class F>
struct pkCBK0
{
  void setCallback(Y* y, F fx)
  {
    y_ = y;
    f_ = fx;
  }
  static void callbackHandler(void* v)
  {
    typedef pkCBK0<Y,F> pk;
    pk* z = static_cast<pk*>(v);
    F fx = z->f_;
    Y* y = z->y_;
    (*y.*fx)();
  }
  Y* y_;
  F f_;
};

The implementation takes advantage of the void* pointer—passing both the real instance (the original "this") as well as the method address in the same void*. There is a downside, of course; p_ has to be a member of Resource as p_ is now the "state keeper." For a resource, this is not a problem—because a resource is essentially an encapsulation. The interesting case is when a P callback has to be used by a Caller. The solution at rescue might be again the central repository, as we'll see shortly.

Putting the Pieces Together

Till now we've collected the following requirements:

  1. A central repository capable of hosting a non-homogeneous collection of different instances has to be in place.
  2. Every resource will be encapsulated as per RAII.
  3. Every resource encapsulation manages its associated callbacks.

As our main goal is to access resources generically. It looks like a good idea to mediate resource maneuvering, meaning that:

  1. Caller will use Resources via a Mediator.
  2. Mediator is responsible for proper resource handling (including creation/destruction).
Note: The basic role of the Mediator is a separation of concerns. Minimal implementations might not implement it at all.

Implementation

Central Repository is implemented in the ResourceRegistry class as a homogeneous collection of polymorphic instances—meaning we need to define a specialized interface to be implemented by all the elements:

class IRelease
{
public:
  virtual void releaseResource() = 0;
  virtual int getUID() = 0;
protected:
  IRelease(){};
  virtual ~IRelease() {};
private:
  IRelease( const IRelease &value );
  const IRelease &operator = ( const IRelease &rhs );
};

Destructor is private because objects implementing this interface cannot be created on the stack. ResourceRegistry has a simple interface:

class ResourceRegistry
{
public:
  void registerResource(IRelease* resource);
  bool unregisterResource(IRelease* resource);
  IRelease* getRegistered(int uid) const;
  ~ResourceRegistry() ;
  ResourceRegistry(int swapLimit):swapLimit_(swapLimit)
  {}
private:
  void unregisterResources();
  void swap();
private:
  BrewVector<IRelease*> resources_;
  int swapLimit_;

private:
  ResourceRegistry( const ResourceRegistry &value );
  const ResourceRegistry &operator =(const ResourceRegistry &rhs);
};

The objects stored inside the repository can be differentiated by a unique, type dependent, ID. That's why getRegistered takes an int parameter. In the meantime, Resources can be reusable or not—and this can be set via a policy class.

The Mediator is implemented in:

template <class P, class T, class U, class W, class R = ResourceRegistry>
class CbkDispatcher : public IRelease, IExecute
{
public:
  virtual void onCbk( ) ;
  virtual void releaseResource( ;
  virtual int getUID( ) ;
  static int getID();
  static int getDispatcher(IShell* shell, R*  r, P& p, W* w);
private:
  CbkDispatcher( IShell* shell, R*  r, P& p;
  ~CbkDispatcher();
  void releaseResources();
  void releasePipe();
  void close() const;
  int initRequest(W* w) ;
private:
  IShell* shell_;
  U* u_;
  P& p_;
  R* r_;
private:
  CbkDispatcher( const CbkDispatcher &Value );
  const CbkDispatcher &operator = ( const CbkDispatcher &Rhs );
  CbkDispatcher(){};
};

The lifetime of a resource can be expressed in the sequence:

  1. initRequest is called by getDispatcher() on behalf of a Caller. As mentioned before, CbkDispatcher is a proxy for the Resource. Caller passes all the needed information, including the output buffer[s] and an asynchronous callback of type P—(p_).
  2. Resource is internally initialized, an internal async method is called and the control returns to Caller.
  3. Resource executes asynchronously.
  4. At the end, p_ is executed asynchronously and CbkDispatcher "commits suicide" or is returned into the ResourceRegistry pool depending on the policy.

CbkDispatcher uses a policy defining whether the associated resources should be treated as reusable or not.

CbkDispatcher accepts a puzzling parameter of type W. This is the data to be passed to the Resource and is an opaque structure implementing IRelease (so it can be hosted in ResourceRegistry as well). W is a different type for every Resource (as it encapsulates specific properties). W has to expose at least two fields—address and parameters—and two methods:

static int getID();
template <class R>
static GDS* initDataStruct(R* rr, char* address,
                           const String& params );

A generic resource is a class exposing three methods:

int     init();
int     initConnection();
void    reset();

There are two initialization methods because a resource is constructed in two steps (more on this when dealing with networking resources). A resource might be reusable. That's why we need a way to reset() it.

How to Use the Framework

The details are a little bit confusing, maybe, but the usage is very simple. Actually, a wrapper was provided:

template <class R, class S>
void CPPApp::initIO( FNC f, char* address, const String& param);

taking care of almost anything.

All we need is a ResourceRegistry, a Resource, and the associated data. A simple GenericResource and data structure (GDS) were created for this purpose. After GenericResource::initConnection() succeeds, an async call is made to asynchronously read some data from a resource.

User has to make one call, like:

initIO<GResource, GDS>(onReadCbk<GDS>,"San Diego",
                       String("Qualcomm"));

and provide a member function as the callback:

template <class S>
void CPPApp::onReadCbk()
{
  S* ds = static_cast<S*>(rr_->getRegistered(S::getID()));
  if (ds->error == SUCCESS)
  {
    if (! ds->data.isEmpty()
    {
      //do something
    }
  }
  else
  {
    String e((long)ds->error);
    //deal with error
  }

}

Playing with Compilers

VC 7.1 is now one of the most standard compliant compilers. At the lower end is VC6—and some additional techniques had to be used to accommodate the framework here. ADS 1.0.1 is somewhere in between and RealView C++ compiler is claiming to be on the high end.

One of the problems of VC6 is in the area of function templates, namely the "explicit template argument specification."

initIO< GenericResource, GDS>(onReadCbk<FDS>, ds->address,
                              String(""));

is not possible. A technique described in [4] is the use of a "type vehicle"—Type2Type:

template <class T>
struct Type2Type
{
  typedef T TYPE;
};

so we can now rewrite the previous initIO call as:

tIO(&CPPApp::onReadFSCbk, ds->address, String(""),
    Type2Type< GenericResource>() , Type2Type<GDS>());

There are problems in using pointers to template member functions, too. One way to solve this is to use functions and another one to "unroll" the calls, like:

initIO(&CPPApp::onReadHttpCbk,....);
void CPPApp::onReadHttpCbk()
{
  onReadCbk(Type2Type<HDS>());
}
instead of simply:
initIO<HTTPpipe, HDS>(onReadCbk<HDS>,....);

Two versions of the code are provided—for VC7.1 and VC6.0, respectively.

In the next installment, we will try to use the framework with some "real-life" resources, such as files and sockets and discuss other useful features of the framework.

Endnotes

[1] Cooperative Multitasking in BREW: A Possible Solution—http://www.developer.com/ws/brew/article.php/1497121

[2] Bjarne Stroustrup—The C++ Programming Language, Addison-Wesley 1997

[3] The exception might be IShell_SetTimer with a too short timer interval

[4] Andrei Alexandrescu—Modern C++ Design, Addison-Wesley 2001

About the Author

Radu Braniste is Director of Technology at Epicad. He can be contacted at rbraniste@epicad.com

# # #

Sitemap | Contact Us

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