ardsrk
ardsrk

Reputation: 2447

Alternatives to preprocessor directives

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.

Upvotes: 6

Views: 3701

Answers (9)

Steve Jessop
Steve Jessop

Reputation: 279445

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...]

Upvotes: 6

Loki Astari
Loki Astari

Reputation: 264729

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.

Upvotes: 1

Teemu Kurppa
Teemu Kurppa

Reputation: 4849

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.

Upvotes: 1

LB40
LB40

Reputation: 12341

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 .

Upvotes: 1

sharptooth
sharptooth

Reputation: 170559

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.

Upvotes: 2

vipsy
vipsy

Reputation: 11

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"

Upvotes: 1

user106014
user106014

Reputation:

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.

Upvotes: 6

Cătălin Pitiș
Cătălin Pitiș

Reputation: 14327

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*/

Upvotes: 2

unwind
unwind

Reputation: 400139

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.

Upvotes: 17

Related Questions