views:

405

answers:

9

Hi,

I am engaged in developing a C++ mobile phone application on the Symbian platforms. One of the requirement is it has to work on all the Symbian phones right from 2nd edition phones to 5th edition phones. Now across editions there are differences in the Symbian SDKs. I have to use preprocessor directives to conditionally compile code that are relevant to the SDK for which the application is being built like below:

#ifdef S60_2nd_ED
  Code
#elif S60_3rd_ED
  Code
#else
  Code

Now since the application I am developing is not trivial it will soon grow to tens of thousands of lines of code and preprocessor directives like above would be spread all over. I want to know is there any alternative to this or may be a better way to use these preprocessor directives in this case.

Please help.

+13  A: 

Well ... That depends on the exact nature of the differences. If it's possible to abstract them out and isolate them into particular classes, then you can go that route. This would mean having version-specific implementations of some classes, and switch entire implementations rather than just a few lines here and there.

You'd have

  • MyClass.h
  • MyClass_S60_2nd.cpp
  • MyClass_S60_3rd.cpp

and so on. You can select which CPP file to compile either by wrapping the entire inside using #ifdefs as above, or my controlling at the build-level (through Makefiles or whatever) which files are included when you're building for various targets.

Depending on the nature of the changes, this might be far cleaner.

unwind
My idea exactly. Polymorphism to model behavioral differences. You can implement polymorphism using inheritance (this way was proposed in your answer), but also using aggregation, facilitated by Traits classes.
xtofl
This is the best way. Use the build system for what it's meant for; `#ifdef`s are the path to unmaintainable code.
Steve M
+1  A: 

You can try to define a common interface for all the platforms, if possible. Then, implement the interface for each platform.

Select the right implementation using preprocessor directives.

This way, you will have the platform selection directive in fewer places in your code (ideally, in one place, explicitly in the header file declaring the interface).

This means something like:

commoninterface.h /* declaring the common interface API. Platform identification preprocessor directives might be needed for things like common type definitions */
platform1.c /*specific implementation*/
platform2.c /*specific implementation*/
Cătălin Pitiș
+7  A: 

In our company we write a lot of cross-platform code (gamedevelopment for win32/ps3/xbox/etc).
To avoid platform-related macroses as much as possible we generally use the next few tricks:

  • extract platfrom-related code into platform-abstraction libraries that has the same interface across different platforms, but not the same implementation;
  • split code into different .cpp files for different platforms (ex: "pipe.h", "pipe_common.cpp", "pipe_linux.cpp", "pipe_win32.cpp", ...);
  • use macroses and helper functions to unify platform-specific function calls (ex: "#define usleep(X) Sleep((X)/1000u)");
  • use cross-platform third-party libraries.
Rageous
In a very similar vein to your suggestions, you can also add include search directories specific to each build -- so something like #include "sound_constants.h" will find one of several permutations of that file.
Shmoopty
Nice tip! I'm a little afraid of low-level bugs like mismatching struct declarations in different compile units (#include "" syntax allows to build relative include paths with the highest priority among all additional and standard include directories ~_~`). However guard like this one: http://www.everfall.com/paste/id.php?xxd85sd9c38g -- in headers can solve such problems...
Rageous
A: 

No Idea about alternative, But what you can do is, use different files to include for different version of OS. example

#ifdef S60_2nd_ED

#include "graphics2"

#elif S60_3rd_ED

#include "graphics3"

#else

#include "graphics"

vipsy
+1  A: 

Look at SQLite. They have the same problem. They move the platform-dependent stuff to separate files and effectively compile only needed stuff by having the preprocessor directives that exclude an entire file contents. It's a widely used approach.

sharptooth
A: 

You could something like they do for the assembly definition in the linux kernel. Each architecture has its own directory (asm-x86 for instance). All these folders cluster the same high level header files presenting the same interface. When the kernel is configured, a link named asm is created targeting the appropriate asm-arch directory. This way, all the C files include files like .

LB
+1  A: 

There are several differences between S60 2nd ed and 3rd ed applications that are not limited to code: application resource files differ, graphic formats and tools to pack them are different, mmp-files differ in many ways.

Based on my experience, don't try to automate it too much, but have a separate build scripts for 2nd ed and 3rd ed. In code level, separate differences to own classes that have common abstract API, use flags only in rare cases.

tequilatango
+1. We used to auto-generate the build scripts with python to avoid some of the repetition. Basically substitution and conditionals did most of it - these days maybe Django templates would be worth a try. Of course the build chain would then process them with perl into makefiles. I'm led to believe that eventually there is a compiler involved ;-)
Steve Jessop
A: 

You should try and avoid spreading #ifs through the code.

Rather; use the #if in the header files to define alternative macros and then in the code use the single macro.

This method allows you to keep the code slightly more readable.

Example:

 Plop.h
 ======

 #if V1
 #define    MAKE_CALL(X,Y)    makeCallV1(X,Y)
 #elif V2
 #define    MAKE_CALL(X,Y)    makeCallV2("Plop",X,222,Y)
 ....
 #endif


 Plop.cpp
 ========

 if (pushPlop)
 {
     MAKE_CALL(911,"Help");
 }

To help facilitate this split version specific code into their own functions, then use macros to activiate the functions as shown above. Also you can wrap the changing parts of the SDK in your own class to try and provide a consistent interface then all your differences are managed within the wrapper class leaving your code that does the work more tidy.

Martin York
+2  A: 

I've been exactly where you are.

One trick is, even if you're going to have conditions in code, don't switch on Symbian versions. It makes it difficult to add support for new versions in future, or to customise for handsets which are unusual in some way. Instead, identify what the actual properties are that you're relying on, write the code around those, and then include a header file which does:

#if S60_3rd_ED
    #define CAF_AGENT 1
    #define HTTP_FILE_UPLOAD 1
#elif S60_2nd_ED
    #define CAF_AGENT 0
    #if S60_2nd_ED_FP2
        #define HTTP_FILE_UPLOAD 1
    #else
        #define HTTP_FILE_UPLOAD 0
    #endif
#endif

and so on. Obviously you can group the defines by feature rather than by version if you prefer, have completely different headers per configuration, or whatever scheme suits you.

We had defines for the UI classes you inherit from, too, so that there was some UI code in common between S60 and UIQ. In fact because of what the product was, we didn't have much UI-related code, so a decent proportion of it was common.

As others say, though, it's even better to herd the variable behaviour into classes and functions where possible, and link different versions.

[Edit in response to comment:

We tried quite hard to avoid doing anything dependent on resolution - fortunately the particular app didn't make this too difficult, so our limited UI was pretty generic. The main thing where we switched on screen resolution was for splash/background images and the like. We had a script to preprocess the build files, which substituted the width and height into a file name, splash_240x320.bmp or whatever. We actually hand-generated the images, since there weren't all that many different sizes and the images didn't change often. The same script generated a .h file containing #defines of most of the values used in the build file generation.

This is for per-device builds: we also had more generic SIS files which just resized images on the fly, but we often had requirements on installed size (ROM was sometimes quite limited, which matters if your app is part of the base device image), and resizing images was one way to keep it down a bit. To support screen rotation on N92, Z8, etc, we still needed portrait and landscape versions of some images, since flipping aspect ratio doesn't give as good results as resizing to the same or similar ratio...]

Steve Jessop
Hey, could you also tell me your approach to handle different mobile screen resolutions
ardsrk