devxlogo

Common Challenges to Porting Existing Code in C to BREW

Common Challenges to Porting Existing Code in C to BREW

riting?and maintaining?portable code is as much an art as it is the holy grail of many software companies. If your firm’s success is based on a key algorithm for mobile computing (say, image or audio recognition), why not grow your market share by making your algorithm available for BREW? When you do, though, you’ll face challenges. Often, the challenges are immediate and obvious, and how you solve them is part of how your company is successful, such as moving an application’s user interface from one platform to another. At the heart of most technologies, however, are a few key algorithms nearly always written in mostly-portable C.

Moreover, once you’ve dealt with a few ports of such code to BREW, patterns emerge. Here are the four most common patterns I’ve observed, and how to deal with them.

Referencing Global and Static Variables
If you’ve written any code that actually runs on a BREW-enabled handset, you’ve already learned that BREW doesn’t have support global variables…or does it?

In point of fact, BREW doesn’t support a read-write segment, that chunk of a program image where a compiler shoves the pre-initialized variables that you’ve indicated can change during program execution. The reasons for this are long and sticky, but the upshot is that if you’re building your application using the ARM tool chain (using tools such as the BREW Builder or ARM ADS), you can’t write things like this:

int gCounter = 0;static int thunk( void ){  return ++gCounter;}

Or this, for that matter:

static int thunk( void ){  static int gCounter = 0;  return ++gCounter;}

How much of a problem this really poses depending on your application and what you want to do about it. The lack of a read-write segment (often called, simply, “the global variable problem”) has been an issue to varying degrees at different times for a number of mobile platforms, including Palm OS and Symbian. As a result, many mobile solutions have been designed to accommodate this problem from the get-go, passing any data that would otherwise be global in a pointer to a structure on the heap:

typedef struct SContext{  int counter;} SContext;static int thunk( SContext *p ){  return ( p ? ++p->counter : 0 );}

This use of a context or state variable is still the best way to deal with the problem, because it promotes better organization for your code as well as fewer side effects. However, it’s simply not practical if you’re porting a large body of existing code, especially if you’re going to be maintaining both the origin of the ported code and the resulting port. Fortunately, with the advent of GCC support for BREW, you now have two additional compiler options both based on GCC: Gnude and WinARM. Based on GCC 3.x and GCC 4.x respectively, the GCC tool chain can create “fix-up” code that allocates your global and static variables on the heap at run-time, letting you use your tried-and-true code without changes on BREW. Either of these tools poses some challenges; Qualcomm’s support of GCC solutions for BREW has been more sound and fury, but a growing body of users (in a straw poll at the most recent BREW developer conference, nearly half the developers I asked told me they were using GCC-based tools) is stepping in to provide documentation and help. The binaries generated by the GCC tool chain can be larger than the tools available from ARM, but this is becoming less of a problem as handset capacities improve and open source developers put more effort into refining the WinARM tool chain. See the “Related Resources” box for more information on how to download and install either Gnude or WinARM.

Using Standard Library and Floating Point Interfaces
The lack of a read/write segment, combined with some other platform restrictions forces Qualcomm to offer “helper functions” such as STRCPY for strcpy, the C library string-copy function. Worse, there’s no support for floating point arithmetic on floating-point numbers, because the ARM compiler brings in a floating-point library that requires (you guessed it) a read-write segment. Dealing with this problem can require three different approaches.

For fairly simple, standard C library calls, the easiest thing to do is provide a thin porting layer that re-implements the standard C call using a BREW helper function. (I prefer re-implementing these functions in this manner rather than changing the original implementation source.) While it’s tempting to just start writing

#define strcpy(d, s) STRCPY(d, s) 

I’ve begun advocating the use of inline functions, instead:

__inline char *strcpy(char *d, const char *s) {   return STRCPY( d, s ) }

This has two key advantages:

  • It permits you to control the size of your module by letting you decide which functions should actually be inlined (and in the process, you can also selectively debug a function if you so desire).
  • It avoids the macro-variable problem. Because C macros pass their arguments by reference, not by value, side effects can result if the implementation of the macro uses any operation or function that mutates its value (such as the pre-increment or post-increment operators ++).

Unfortunately, the BREW API doesn’t provide a full replacement of the C standard library; if you’re looking for something like strtok or strtol, you’re out of luck. Over the years, I’ve gradually built a library of the unimplemented standard library functions I’m likely to encounter; experience has shown that I can probably implement and test something like isspace faster than finding one, even with Google, and by keeping a library of these routines around, it’s not that arduous a task.

However, if you’re re-implementing a standard C library function, you should look out for two things. First, be sure to thoroughly unit test your implementation, and if possible, compare your implementation’s behavior with the implementation of the original function on a platform such as UNIX or Windows! In point of fact, the isspace implementation I wrote went for years with a subtle bug (it didn’t treat
as whitespace) that cost me a few hours of pain recently because I’d been too lazy to write exhaustive unit tests. Second, if you’re going to draw from an existing source for your implementation, check license agreements thoroughly. Borrowing code from another library implementation can be very tempting, but can have drastic consequences on the legal ownership of your port? thus causing untold business headaches later when you try to license the results. Take a look at the various licensing terms (GPL, LGPL, and so on) before simply scavenging code from other sources.

The problem with floating-point numbers is similar, but because you want to change what code gets invoked when the C compiler does its magic for operations such as * and /, it’s a little trickier. Fortunately, Qualcomm has an excellent how-to in their Knowledge Base, complete with an object file you can link that replaces the default ARM-compiler provided floating-point arithmetic operators with ones that don’t require a read-write segment. The only other change you need to make to your application is to invoke the function _fp_init() on startup to initialize the new floating-point routines, like this:

extern void _fp_init(void);int AEEClsCreateInstance(AEECLSID cls, IShell *pishell,                          IModule *pimodule, void **ppOut){  if (AEECLSID_MYCLS == cls )   {    _fp_init();    return MyCls_New( piishell, pimodule, ppOut );  }  else  {    return ECLASSNOTFOUND;  }}

Using the Qualcomm-provided library does increase the size of your application slightly, but that’s probably better than refactoring complex mathematical operations to invoke prefix functions like FADD instead of infix operators like +.

A final word of warning when it comes to floating-point variables: use them sparingly. While today’s handsets seem remarkably fast if you’ve been doing mobile development for a number of years, they cannot hope to keep pace with any desktop platform. Floating-point mathematics on a wireless terminal can bring your application to a crawl, and devastate battery life at the same time. In practice, most of the best algorithms for mobile devices are implemented using domain-specific, fixed-point arithmetic operations for the best performance.

Refactoring Synchronous Interfaces
Most of us come from a background where synchronous interfaces are the norm: as a developer, you call a function and wait for the results. If you’re doing something with an external resource (say, the network), your application stops executing and other things happen until the remote resource provides the data I need. Not so in BREW, which is an asynchronous, cooperatively-multitasked environment. In BREW, rather than wait for an external event to occur, you register a callback that the system will invoke when the event has actually happened. For example, to read on a socket, you might write:

Static int SomeSetupFunction (CApp *pMe){// Now that our socket's created, wait for data  ISOCKET_Readble( pMe->pISocket, (PFNNOTIFY)DoRead, pMe );  return SUCCESS;}static void DoRead(CApp *pMe){  int n;  do  {    n  = ISOCKET_Read( pMe->pISocket, pMe->buffer, BSIZE );    if ( n > 0 ) ProcessData( pMe );  } while( n>0);  if ( EWOULDBLOCK == n )  {     // Wait for another round…    ISOCKET_Readable( pMe->pISocket, (PNNOTIFY)DoRead, pMe );  }  else  {    // Handle the error    HandleNetError( pMe );  }} 

This code asks the ISocket interface to notify you when data’s available for reading; once there’s data available, you loop to read the available data until either an error occurs or there’s no more data to be read. If there’s no more data, simply ask the socket to notify you again when more data is available.

Unfortunately, moving from synchronous to asynchronous events is hard. You’re going to need to re-write your code, keeping in mind that it’s your application’s responsibility to share the processor with the rest of the system. One BREW interface that can help you do this is IThread, which lets you set up a cooperative thread of execution in your application. Using IThread, you specify a function to execute, which should relinquish the processor at regular intervals using ITHREAD_Suspend. You must schedule the thread’s resumption by registering a callback and using ISHELL_Resume; the mechanism essentially gives you an easy way to encapsulate a chain of callbacks and an associated heap. While using IThread doesn’t solve the fundamental problem for you, it at least provides a framework in which to work, so that you don’t need to write a state machine from scratch.

Refactoring Threaded Applications
If moving from synchronous to asynchronous events is hard, then untangling a multi-threaded algorithm is harder. Typically, people write code for multithreaded applications for one of two reasons: either to permit the application to manage user events while simultaneously handling outside events such as network I/O, or to give the appearance of a prolonged computation happening in the background without affecting UI performance. These are arguably two different shades of the same underlying problem, but how you address these two problems will differ slightly.

The last code example in the previous case is an example of the first case: on a sufficiently fast network with a server that delivers a lot of data to the application, the processor will spend a great deal of time in the DoRead function. On a multithreaded platform, this wouldn’t be a problem, because the network interface would be synchronous and the read operation would run in another thread (and you’d have to trust that the platform would allocate adequate CPU resources to the rest of your application, including user input/output).

That simply isn’t possible in BREW. Instead, you need to hand tune the behavior of your callbacks. This is where experience plays a big part; be prepared to spend time benchmarking algorithm performance on a few different handsets (not the BREW Simulator on your PC!), tuning things like read or write buffer sizes to meet your needs. Fortunately, once you’ve made your interfaces asynchronous, this is all you need to do?test and tune to be sure that the application executes with the performance you’d like to see balanced between user interface and other operations.

Hopefully, the second case is rare?compute-bound applications on wireless terminals aren’t terribly popular with most consumers anyway. If you have this problem, though, the solution is to break your long computations up into a series of states, and chain the states using ISHELL_Resume or IThread. The process is similar to how you convert synchronous interfaces into asynchronous ones, but it’s often more work, because what may need to be broken up may be inside a loop, and you will be responsible for unrolling the loop into an asynchronous operation.

A common place you may need to do this is in managing a database such as a messaging store; if you need to compact a database or expire old elements (such as in a cache) you should do it like this:

static int Expire( SContext *p, PFNNOTIFY pfnDone, void *pu ){  if ( p->pfnDone ) return EWOULDBLOCK;  p->expireNext = GetFirstElement( p );  CALLBACK_Init( &p->pcbExpire, (PFNNOTIFY)ExpireOne, p );  CALLBACK_Init( &p->pcbExpireDone, pfnDone, pu );  ISHELL_Resume( p->pishell, &p->pcbExpire );  return SUCCESS;}static void ExpireOne( SContext *p ){  if ( !p ) return;  ExpireIfTooOld( p->expireNext );  p->expireNext = GetNextElement( p );  if (p->expireNext )  {    // Process the next one.    ISHELL_Resume( p->pishell, &p->pcbExpire );  }  else  {    // We’re done! Tell the client.    CALLBACK_Cancel( &p->pcbExpire );    ISHELL_Resume( p->pishell, &p->pcbExpireDone );  }}

Whew! While it’s not nearly as pretty as a while loop in its own thread, it’s the only game in town.

Note: To critics of single-threaded platforms, it’s also worth noting that after a late evening debugging a deadlocked application with multiple threads, it starts to look a lot better! Threads aren’t simple either; they just move the complexity somewhere else.)

This code enumerates over the items being expired, and processes each one in its own callback invocation; the system will invoke ExpireOne when the processor is idle. Because it does so only when the system is idle, your application can continue to handle other user events; if a user presses a key while the cycle of chained ISHELL_Resumes is pending, the keystroke is delivered to the event. You can also update the user interface (say, to provide a status notification), or provide additional status from this chain of execution by adding additional callbacks or sending messages to your event handler.

Being Prepared Eliminates Drudgery
Porting isn’t hard, but it is work. Fortunately, like a lot of work, it’s very repetitive, and these patterns appear often. By seeing them now, you are better equipped to make short work of much of the drudgery of bringing your algorithms to Qualcomm BREW.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist