Best (cleanest) way for writing platform specific code - c++

Say you have a piece of code that must be different depending on the operating system your program is running on.
There's the old school way of doing it:
#ifdef WIN32
// code for Windows systems
#else
// code for other systems
#endif
But there must be cleaner solutions that this one, right?

The typical approach I've seen first hand at a half-dozen companies over my career is the use of a Hardware Abstraction Layer (HAL).
The idea is that you put the lowest level stuff into a dedicated header plus statically linked library, which includes things like:
Fixed width integers (int64_t on Linux, __int64 on Windows, etc).
Common library functions (strtok_r() vs strtok_s() on Linux vs Windows).
A common data type setup (ie: typedefs for all data types, such as xInt, xFloat etc, used throughout the code so that if the underlying type changes for a platform, or a new platform is suddenly supported, no need to re-write and re-test code that depends on it, which can be extremely expensive in terms of labor).
The HAL itself is usually riddled with preprocessor directives like in your example, and that's just the reality of the matter. If you wrap it with run-time if/else statements, you comilation will fail due to unresolved symbols. Or worse, you could have extra symbols included which will increase the size of your output, and likely slow down your program if that code is executed frequently.
So long as the HAL has been well-written, the header and library for the HAL give you a common interface and set of data types to use in the rest of your code with minimal hassle.
The most beautiful aspect of this, from a professional standpoint, is that all of your other code doesn't have to ever concern itself with architecture or operating system specifics. You'll have the same code-flow on various systems, which will by extension, allow you to test the same code in a variety of different manners, and find bugs you wouldn't normally expect or test for. From a company's perspective, this saves a ton of money in terms of labor, and not losing clients due to them being angry with bugs in production software.

I've had to do a lot of this sort of stuff in my career, supporting code that buils and runs on an embedded device, plus in windows, and then also have it run on different ASICS and/or revisions of ASICS.
I tend to do what you suggest and then when things really diverge, move on to defining the interface I desire to be fixed between platforms and then having separate implementation files or even libraries. It can get really messy as the codebase gets older and more exceptions need to be added.
Sometimes you can hide this stuff in header files, so your code looks 'clean', but a lot of times that's just obfuscating what's going on behind a bunch of macro magic.
The only other thing I'd add is I tend to make the #ifdef/#else/#endif chain fail if none of the options are defined. This forces me to revisit the issue when a new revision comes along. Some folks prefer it to have a default, but I find that just hides potential failures.
Granted, I'm working in the embedded world where code space is paramount (since memory is small and fixed), and code cleanliness unfortunately has to take a back seat.

An adopted practice for non-trivial projects is to write platform-specific code in separate files (and in separate directories, where applicable), avoiding "localized" #ifdefs to the fullest possible extent.
Say you are developing a library called "Example" and example.hpp will be your library header:
example.hpp
#include "platform.hpp"
//
// here: platform-independent declarations, includes etc
//
// below: platform-specific includes
#if defined(WINDOWS)
#include "windows\win32_specific_code.hpp"
// other win32 headers
#elif defined(POSIX)
#include "posix/linux_specific_code.hpp"
// other linux headers
#endif
platform.hpp (simplified)
#if defined(WIN32) && !defined(UNIX)
#define WINDOWS
#elif defined(UNIX) && !defined(WIN32)
#define POSIX
#endif
win32_specific_code.hpp
void Function1();
win32_specific_code.cpp
#include "../platform.hpp"
#ifdef WINDOWS // We should not violate the One Definition Rule
#include "win32_specific_code.hpp"
#include <iostream>
void Function1()
{
std::cout << "You are on WINDOWS" << std::endl;
}
//...
#endif /* WINDOWS */
Of course, declare Function1() in your linux_specific_code.hpp file as well.
Then, when implementing it for Linux (in the linux_specific_code.cpp file), be sure to surround everything for conditional compilation as well, similar to I did above (eg. using #ifdef POSIX). Otherwise, the compiler will generate multiple definitions and you'll get a linker error.
Now, everything an user of your library must do is #include <example.hpp> in his code, and place either #define WINDOWS or #define POSIX in his compiler's preprocessor definitions. In fact, the second step might not be necessary at all, assuming his environment already defines either one of the WIN32 or UNIX macros. This way, Function1() can already be used from the code in a cross-platform manner.
This approach is pretty much the one used by the Boost C++ Libraries. I personally find it clean and sensible. If, however, you don't like it, you can have a read at Chromium's conventions for multi-platform development for a somewhat different strategy.

Related

How exactly cross platform libraries works for different machines

Recently, I was learning about networking in C++ and I found boost.asio which is a cross platform library, and then I got a thought about how this library is a cross platform since Windows provides different library for networking and even mac also,
so how its function works on different machines, does cross platform libraries create their own functions for this purpose or they contain different private functions of different machine's logics and provides public functions which then during compiling time check for on which machine that codes are compiling and change our written functions with machines defined libraries.
For example
//Operations for windows
Private void WindowsFunc
{ code }
//Operations for mac
Private void MacFunc
{ code }
//library's functions
Public void Do
{
//Performs different operations
//for different machines
If (windows)
WindowsFunc
else if (Mac)
MacFunc
}
It could be a solution may be😗
There are several possible ways:
Use ifdefs
char* doFoo(void){
#ifdef _WIN32
return "win32";
#elif ...
return ...;
#endif ...
}
The pro is, that it doesn't require much setup.
The con is, that it clutters the code, if you do it too much.
Have different implementations in different directories.
As a folder structure:
root
|---->Other files
|---->windows implementation
| |->foo.c
|---->linux implementation
|->foo.c
|---->macos implementation
|->foo.c
And then you can use some build system, or a custom shell script for selecting things.
(Pseudo code)
if(OS==windows)
compileDirectory(windowsImpl);
else if(OS==linux)
compileDirectory(windowsImpl);
if(OS==macos)
compileDirectory(windowsImpl);
The pro is, that it doesn't clutter the code, allows better adding of new features and some sort of abstraction.
The contra is, that it can be quite tremendeous to setup.
This is often done using compiler-specific macros. For example, the gcc compiler defines the preprocessor symbol
__linux
on Linux. So, C or C++ code can use
#if __linux
to compile Linux-specific code. Nothing in the actual code actually #defines this. This is defined by default, by the Linux version of gcc. Similar preprocessor macros are defined on other platforms support by gcc. Other compilers have similar pre-defined macros. Cross-platform library's sources assemble a collection of these macros they check in order to compile operating system-specific code.
Another, common approach is to explicitly define a library-specific symbol as part of the library's build instruction or script. The build instructions that come with a cross-platform library include the instructions for running the appropriate script, from the library's package, that runs the compiler and explicitly sets the appropriate preprocessor symbol, which is then used in this manner.
Generally, preprocessor usage is discouraged in modern C++ code, and used sparingly. This is one use case where preprocessor macro usage is quite common, in practice.
Generally cross platform libraries are implemented in layers.
There is the public interface, the common code, and a minimalized set of platform dependent code.
The public interface usually uses #if defined(symbol) checks to determine which platform it is running on. It may include system headers based on this, but more often will simply forward declare any symbols it needs to expose for platform specific APIs (if any). In some cases, header only libraries will go further than this.
The common code will have minimal platform specific stuff in it. It will try to deal with platform specific stuff using abstraction; it may #include platform specific helper headers, but the code is common between platforms.
Like, it might use Native::RWLock and Native::RWLock::lock( mutex ) functions that are typedefs and thin wrappers around platform specific types.
The platform specific code is different for each platform. It may be conditionally compiled in the build system NativeMutexImp_mac.cpp , or wrapped in #ifdef blocks, or even both.
Now header only libraries are leakier than this. And some less organized cross platform code will mix thenplatform specific and not code all over the place. Finally, performance requirements may require some leaking platform specific code in public header files.
But the main idea is that you hide the OS API use that you in turn use to implement your cross platform functionality.
This can make the end user code faster than native OS API use if your API makes efficient use easier than the native APIs do. Optimization is fungible; code that is easier to make performant is actually faster in practice.

is there a way to run C code designed for an embedded micro-controller on a normal computer?

I have a C code which is written for an ATmega16 chip, and it is full of keywords like :
flash, eeprom, bit
and macros(?) like
interrupt [TIM1_OVF] void timer1_ovf_isr(void)
that come before function signatures.
Now what I want to do is write and run unit tests that verify the correctness of the logic of the controller unit and I want to be able to run these tests on any computer and not need to have the "device" that the code represents.
I searched a lot and came across "abstracting the hardware" and "replacing them with stubs" kind of solutions, but I'm not sure how I can abstract something like "interrupt [TIM1_OVF]" in the code!
I was wondering if there any special tools that provide the environment for running these sorts of codes?
And also if I am going at it wrong, can anybody point me in the right direction? giving that changing or rewriting (!) the micro-controller's code might not be an option?
Thanks a bunch.
Your examples are not ISO C code, they are compiler specific extensions, they are not common across AVR compilers let alone architectures. In many cases they can be worked around by defining macros that require little or no modification of the code. To make your code portable in any case even across different vendor's AVR compilers it is a good idea to do that in any case, although a combination of techniques may be required.
Most compilers support an "always include" option that allows a header file to be included from the command line with an explicit #include directive in the source. Creating a header with your compatibility macros, and including it either implicitly as described or explicitly in the code is a useful technique. For example for the issues you have mentioned, you might have:
// compatability.h
#if !defined COMPATABILITY_INCLUDE
#define COMPATABILITY_INCLUDE
#if defined __IAR_SYSTEMS_ICC__
#define INTERRUPT( irq, handler ) __interrupt [irq] void handler(void)
#elif defined _WIN32
#define INTERRUPT( irq, handler ) void handler(void)
#define __flash const
#define __eeprom const
#define __bit char
#else
#error Unknown toolchain/environment
#endif
#endif
That will remove the memory location qualifiers from the Win32 code, and define __bit as a char. The interrupt handler macro will turn a handler into a regular function on Win32, but does require your code to be modified, but since every toolchain does this differently, that is perhaps no bad thing.
For example in this case you would change:
interrupt [TIM1_OVF] void timer1_ovf_isr(void)
{
...
}
to
INTERRUPT( TIM_OVF, timer1_ovf_isr )
{
...
}
Note that you should use approapriate target macros in the compatability file - I have guessed at IAR for example; you may be using a different compiler. Your compiler documentation should specify the available predefined macros, alternatively Pre-defined Compiler Macros "project" on Sourceforge is a useful resource.
Some of the transformations may change the code semantically, such as swapping __bit for char in some cases for example if the bit is assigned a value greater than one, and then compared with 1, the embedded target is likely to yield true, while on the PC build it will not. It might better be transformed to _Bool but your compiler may give warnings about implicit conversions. My suggestions may not necessarily be the best possible transformation either - consult your compiler's manual for the precise semantics and decide how best to transform them to standard C for test builds.
An alternative that preserves proprietary semantics is to run your unit tests in an instruction-set simulator using debugger scripting if available to implement stubs for hardware interaction, however that method makes it impossible to use off-the-shelf unit-testing frameworks such as CUnit.
Depending on your toolchain, you may already have AVR simulator available, which would allow you to run your unit tests on any PC. For example, IAR provides "C-SPY", an AVR simulator that supports a terminal window, can show show register values, can support generation of interrupts, etc. Assuming you keep your unit sizes reasonable, you do not need significant infrastructure or stubbed interfaces to make this work.
One large benefit of running unit tests on your target platform (with your target compiler) is that you can account for any particular behaviors that will be caused by the platform (endianness, word size, compiler or library peculiarities, etc), compared to running in a PC environment.

Dos and Don'ts of Conditional Compile [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 12 years ago.
When is doing conditional compilation a good idea and when is it a horribly bad idea?
By conditional compile I mean using #ifdefs to only compile certain bits of code in certain conditions. The #defineds themselves may be in either a common header file or introduced via the -D compiler directive.
The good ideas:
header guards (you can't do much better for portability)
conditional implementation (juggling with platform differences)
debug specific checks (asserts, etc...)
per suggestion: extern "C" { and } so that the same headers may be used by the C++ implementation and by the C clients of the API
The bad idea:
changing the API between compile flags, since it forces the client to changes its uses with the same compile flags... urk!
Don't put ifdef in your code.
It makes it really hard to read and understand. Please make the code as easy to read as possable for the maintainer (he knows where you live and owns an Axe).
Hide the conditional code in separate functions and use the ifdef to define what functions are being used.
DONT use the else part to make a definition. If you are ding that you are saying one platform is unique and all others are the same. This is unlikely, what is more likely is that you know what happens on a couple of platforms but you should use the #else section to stick a #error so when it is ported to a new platform a developer has to explicitly fix the condition for his platform.
x.h
#if defined(WINDOWS)
#define MyPlatfromSleepSeconds(x) sleep(x * 1000)
#elif defined (UNIX)
#define MyPlatfromSleepSeconds(x) Sleep(x)
#else
#error "Please define appropriate sleep for your platform"
#endif
Don;t be tempted to expand a macro into multiple lines of code. That leads to madness.
p.h
#if defined(SOLARIS_3_1_1)
#define DO_SOME_TASK(x,y) doPartA(x); \
doPartB(y); \
couple(x,y)
#elif defined(WINDOWS)
#define DO_SOME_TASK(x,y) doAndCouple(x,y)
#else
#error "Please define appropriate DO_SOME_TASK for your platform"
#endif
If you develop the code on windows then test on solaris 3_1_1 later you may find unexpected bugs when people do things like:
int loop;
for(loop = 0;loop < 10;++loop)
DO_SOME_TASK(loop,loop); // Windows works fine()
// Solaras. Only doPartA() is in the loop.
// The other statements are done when the loop finishes
Basically, you should try to keep the amount of code that is conditionally compiled to a minimum, because you should be trying to test all that and having lots of conditions makes that more difficult. It also reduces the readability of the code; conditionally compiling whole files is clearer, e.g., by putting platform-specific code in a separate file for each platform and having that all have the same API from the perspective of the rest of the problem. Also try to avoid using it in function headers; again, that's because that's a place where it is particularly confusing.
But that's not to say that you should never use conditional compilation. Just try to keep it short and minimal. (Where I can, I use conditional compilation to control the definitions of other macros which are then just used in the rest of the code; that seems to be clearer to me at least.)
It's a bad idea whenever you don't know what you're doing. It can be a good idea when you're effectively solving an issue this way :).
The way you describe conditional compiling, include guards are part of it. It's not only a good idea to use it. It's a way to avoid compilation errors.
For me, conditional compiling is also a way to target multiple compilers and operating systems. I'm involved in a lib that's supposed to be compileable on Windows XP and newer, 32 or 64 bit, using MinGW and Visual C++, on Linux 32 and 64 bit using gcc/g++ and on MacOS using I-don't-know-what (I'm not maintaining that, but I assume it's a gcc port). Without the preprocessor conditions, it would be pretty much impossible to create a single source file that's compileable anywhere.
Another pragmatic use of conditional compiles is to "comment out" sections of code which contain standard "C" comments (i.e. /* */). Some compilers do not allow nesting of these comments, for example:
/* comment out block of code
.... code ....
/* This is a standard
* comment.
*/ ... oopos! Some compilers try to compile code after this closing comment.
.... code ....
end of block of code*/
(As you can see in the syntax highlighting, StackOverflow does not nest comments.)
instead you can use#ifdef to get the right effect, for example:
#ifdef _NOT_DEFINED_
.... code ....
/* This is a standard
* comment.
*/
.... code ....
#endif
In the past if you wanted to produce truly portable code, you'd have to resort to some form of conditional compilation. With there being a proliferation of portable libraries (such as APR, boost etc.) this reason has little weight IMHO. If you are using conditional compilation simply compile out blocks of code that are not need for particular builds, you should really revisit your design - I should imagine that this would become a nightmare to maintain.
Having said all that, if you do need to use conditional compilation, I would hide as much as I can away from the main body of the code and limit to to very specific cases that are very well understood.
Good/justifiable uses are based on cost/benefit analysis. Obviously, people here are very conscious of the risks:
in linking objects that saw different versions of classes, functions etc.
in making code hard to understand, test and reason about
But, there are uses which often fall into the net-benefit category:
header guards
code customisations for distinct software "ecosystems", such as Linux versus Windows, Visual C++ versus GCC, CPU-specific optimisations, sometimes word size and endianness factors (though with C++ you can often determine these at compile via template hackery, but that may prove messier still) - abstracts away lower-level differences to provide a consistent API across those environments
interacting with existing code that uses preprocessor defines to select versions of APIs, standards, behaviours, thread safety, protocols etc. (sad but true)
compilation that may use optional features when available (think of GNU configure scripts and all the tests they perform on OS interfaces etc)
request that extra code be generated in a translation unit, such as adding main() for a standalone app versus without for a library
controlling code inclusion for distinct logical build modes such as debug and release
It is always a bad idea. What it does is effectively create multiple versions of your source code, all of which need to be tested, which is a pain, to say the least. Unfortunately, like many bad things it is sometimes unavoidable. I use it in very small amounts when writing code that needs to be ported between Windows and Linux, but if I found myself doing it a lot, I would consider alternatives, such as having two separate development sub-trees.

C++: Is windows.h generally an efficient code library?

I heard some people complaining about including the windows header file in a C++ application and using it. They mentioned that it is inefficient. Is this just some urban legend or are there really some real hard facts behind it? In other words, if you believe it is efficient or inefficient please explain how this can be with facts.
I am no C++ Windows programmer guru. It would really be appreciated to have detailed explanations.
*Edit: I want to know at compile time and execution. Sorry for not mentioning it.
windows.h is not a "code library". It's a header file, and doesn't contain any executable code as such (save for macro definitions, but those still aren't compiled - their expansions are, if and when you use them).
As such, looking at it strictly from performance perspective, merely including it has any effect solely on compilation time. That one is rather significant, though - for example, if using Platform SDK headers that come with VS2010, #include <windows.h> expands to ~2.4Mb of code - and all that code has to be parsed and processed by the compiler.
Then again, if you use precompiled headers (and you probably should in this scenario), it wouldn't affect you.
If you precompile it, then the compilation speed difference is barely noticeable. The downside to precompiling, is that you can only have one pre-compiled header per project, so people tend to make a single "precompiled.h" (or "stdafx.h") and include windows.h, boost, stl and everything else they need in there. Of course, that means you end up including windows.h stuff in every .cpp file, not just the ones that need it. That can be a problem in cross-platform applications, but you can get around that by doing all your win32-specific stuff in a static library (that has windows.h pre-compiled) and linking to that in your main executable.
At runtime, the stuff in windows.h is about as bare-metal as you can get in Windows. So there's really no "inefficiencies" in that respect.
I would say that most people doing serious Windows GUI stuff would be using a 3rd-party library (Qt, wxWidgets, MFC, etc) which is typically layered on top of the Win32 stuff defined in windows.h (for the most part), so as I said, on Windows, the stuff in windows.h is basically the bare metal.
There are multiple places where efficiency comes in to play.
Including <windows.h> will substantially increase compile times and bring in many symbols and macros. Some of these symbols or macros may conflict with your code. So from this perspective, if you don't need <windows.h> it would be inefficient at compile time to bring it in.
The increased compile time can be mitigated somewhat by using precompiled headers, but this also brings with it a little more codebase complexity (you need at least 2 more files for the PCH), and some headaches unique to PCHs. Nonetheless, for large Windows project, I usually use a PCH. For toy or utility projects, I typically don't because it's more trouble than it's worth.
Efficiency also comes in to play at runtime. As far as I know, if you #include <windows.h> but don't use any of those facilities, it will have no effect on the runtime behavior of your program at least as far as calling extra code and that kind of thing. There may be other runtime effects however that I'm not aware of.
As far as the big White Elephant question, "Is Windows Efficient?" I'll not go in to that here other than to say this: Using Windows is much like anything else in that how efficient or inefficient it is depends mostly on you and how well you know how to use it. You'll get as many different opinions on this as people you ask however, ranging from "Winblowz sucks" to "I love Windows, it's awesome." Ignore them all. Learn to code in Windows if you need & want to and then make up your own mind.
As has been noted, #including windows.h slows down compile time. You can use precompiled headers or do a good job of isolating the windows calls only to modules that need them to help with that.
Also, you can add these preproc definitions before the windows.h include like so:
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
#include <windows.h>
It will reduce the number of definitions from windows.h and sub-included header files. You may find later on that you need to remove the lean-and-mean, but try it first and wait until the compiler complains about a missing def.
The namespace conflicts are a legitimate gripe, but technically have nothing to do with efficiency, unless you count efficiency of your personal use of time. Considering how many thousands of definitions will be thrown into your namespace, conflicts are bound to occur at some point, and that can be severely irritating. Just use the practice of isolating your Windows calls into modules, and you will be fine. For this, put #include windows.h in the .cpp file, and not the .h file.
I see no basis for thinking that the runtime performance of the executable will be impacted by including windows.h. You are only adding a large number of definitions to the context used by the compiler. You aren't even putting all the definitions into your compiled code--just allocations, function calls, and referencing based on any definitions used in your source code (.cpp).
Another argument could be made that the Windows API types and functions are inherently wasteful of resources or perform inefficiently. I.e. if you want to create a file, there is some monstrous structure to pass to the Windows API. Still, I think most of this is penny-wise/pound-foolish thinking. Evaluate Windows API performance problems case-by-case and make replacements for inefficient code where possible and valuable.
In general, including windows.h is a necessity: if you need windows functions, you have to include it. I think what you're refering to is (among other things) nested inclusion of windows.h. That is, you include a .h that includes itself windows.h, and you also include windows.h in your .cpp file. This leads to inefficiencies, of course, so you have to study very well in your code what .h files are included in each .h file, and avoid including, say, windows.h n times indirectly.
Just including the header without using it will not have any effects in runtime efficiency
It would affect compilation time ..

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.
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.
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...]
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.
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*/
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.
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"
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 .
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.
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.