Writing cross-platform C++ Code (Windows, Linux and Mac OSX)
This is my first-attempt at writing anything even slightly complicated in C++, I’m attempting to build a shared library that I can interface with from Objective-C, and .NET apps (ok, that part comes later. ) The code I have is —
#ifdef TARGET_OS_MAC // Mac Includes Here #endif #ifdef __linux__ // Linux Includes Here #error Can't be compiled on Linux yet #endif #ifdef _WIN32 || _WIN64 // Windows Includes Here #error Can't be compiled on Windows yet #endif #include using namespace std; bool probe() < #ifdef TARGET_OS_MAC return probe_macosx(); #endif #ifdef __linux__ return probe_linux(); #endif #ifdef _WIN32 || _WIN64 return probe_win(); #endif >bool probe_win() < // Windows Probe Code Here return true; >int main()
I have a compiler warning, simply untitled: In function ‘bool probe()’:untitled:29: warning: control reaches end of non-void function — but I’d also really appreciate any information or resources people could suggest for how to write this kind of code better.
5 Answers 5
instead of repeating yourself and writing the same #ifdef . lines again, again, and again, you’re maybe better of declaring the probe() method in a header, and providing three different source files, one for each platform. This also has the benefit that if you add a platform you do not have to modify all of your existing sources, but just add new files. Use your build system to select the appropriate source file.
include/probe.h src/arch/win32/probe.cpp src/arch/linux/probe.cpp src/arch/mac/probe.cpp
The warning is because probe() doesn’t return a value. In other words, none of the three #ifdefs matches.
Hear, hear! (1) This adds clarity to your code, (2) reduces mental effort to read it for someone who’s only familiar with only one platform, (3) doesn’t depend on arcane non-portable compiler/platform-specific flags.
I know it has its uses, but IMHO the preprocessor should be dropped and this is how all cross-platform coding should be done.
@ Roger Pate — yes if you are careful everything can be escaped by #ifdef (except possibly «#pragma once» has to be at the top of the file?) but it’s a lot easier not to have to worry about it.
I’ll address this specific function:
Writing it this way, as a chain of if-elif-else, eliminates the error because it’s impossible to compile without either a valid return statement or hitting the #error.
(I believe WIN32 is defined for both 32- and 64-bit Windows, but I couldn’t tell you definitively without looking it up. That would simplify the code.)
Unfortunately, you can’t use #ifdef _WIN32 || _WIN64: see http://codepad.org/3PArXCxo for a sample error message. You can use the special preprocessing-only defined operator, as I did above.
Regarding splitting up platforms according to functions or entire files (as suggested), you may or may not want to do that. It’s going to depend on details of your code, such as how much is shared between platforms and what you (or your team) find best to keep functionality in sync, among other issues.
Furthermore, you should handle platform selection in your build system, but this doesn’t mean you can’t use the preprocessor: use macros conditionally defined (by the makefile or build system) for each platform. In fact, this is the often the most practical solution with templates and inline functions, which makes it more flexible than trying to eliminate the preprocessor. It combines well with the whole-file approach, so you still use that where appropriate.
You might want to have a single config header which translates all the various compiler- and platform-specific macros into well-known and understood macros that you control. Or you could add -DBEAKS_PLAT_LINUX to your compiler command line—through your build system—to define that macro (remember to use a prefix for macro names).
Simultaneous C++ development on Linux and Windows
We have a handful of developers working on a non-commercial (read: just for fun) cross-platform C++ project. We’ve already identified all the cross-platform libraries we’ll need. However, some of our developers prefer to use Microsoft Visual C++ 2008, others prefer to code in Emacs on GNU/Linux. We’re wondering if it is possible for all of us to work more or less simultaneously out of both environments, from the same code repository. Ultimately we want the project to compile cleanly on both platforms from the start. Any of our developers are happily willing to switch over to the other environment if this is not possible. We all use both Linux and Windows on a regular basis and enjoy both, so this isn’t a question of trying to educate one set devs about the virtues of the other platform. This is about each of us being able to develop in the environment we enjoy most yet still collaborate on a fun project. Any suggestions or experiences to share?
13 Answers 13
Use CMake to manage your build files.
This will let you setup a single repository, with one set of text files in it. Each dev can then run the appropriate cmake scripts to build the correct build environment for their system (Visual Studio 2008/2005/GNU C++ build scripts/etc).
There are many advantages here:
- Each dev can use their own build environment
- Dependencies can be handled very cleanly, including platform specific deps.
- Builds can be out of source, which helps prevent accidentally committing inappropriate files
- Easy migration to new dev. environments (ie: when VS 2010 is released, some devs can migrate there just by rebuilding their build folder)
I like this suggestion, despite my occasional disdain for makefile syntax. I’d take this one step further (maybe it’s already implied) and suggest that you setup CI builds for each platform you’re targeting. This allows you to identify build-breaking changes quickly. There are numerous CI servers out there — I’ve used Hudson for this purpose with success several times.
If you’re using CMake, it’s very easy to integrate it with CDash — which lets you do CI or nightly builds on multiple distributed platforms, and give a nice clean web interface: cdash.org
@Mike: Also — Cmake’s syntax is cleaner than makefile syntax, IMO — and completely eliminates any need to touch makefiles — it will generate them for you, if you choose that generator (although I usually use generators targetting IDEs)
We have used CMake for this type of development for over a year. It is brilliant. I use it for windows only development too, the syntax is easy and it is much easier to refactor your project structure with CMake that in all the dialog boxes in VC++. One of its nicest features is that you can abstract common include/link paths, preprocessor definitions and libraries, so they are specified in one place for all projects in the solution. Then changing one of these becomes easy, no more changing 20 project files with a text editor.
I’ve done it and seen it done without too many problems.
You’ll want to try an isolate the code that is different for the different platforms. Also, you’ll want to think about your directory structure. Something like
Basically you just want to be really clear what is specific to each system and what is common.
Also, unit tests are going to be really important since there may be minor difference and you want to make it easy for the windows guys to run some tests to make sure the linux code works as expected and the other way around.
Also +1 for unit tests. Note that this means that you need to make sure your unit test framework is also cross-platform — avoid the new C++ unit testing framework that is built into VS2012, for example.
as mentioned in previous posts, Qt is a very easy method to do real simultaneous multi-platform development — independent of your IDE and with many different compilers (even for Symbian, ARM, Windows CE/Mobile. ).
In my last and current company I work in mixed Linux- and Windows-developer teams, who work together using Subversion and Qt (which has a very easy and powerful build-system «QMake», which hides all the different platform/compiler specific build environments — you just have to write one build file for all platforms — so easy!).
And: Qt contains nearly everything you need:
- simple string handling, with easy translation support and easy string conversion (utf8, utf16, asci. )
- simple classes for file i/o, images, networking etc.
- comfortable gui classes
- graphical designer for the gui layout/design
- graphical translation tool to create dynamic translations for your apps (you can run one binary with different selectable languages — even cyrillic, asian fonts. )
- integrated testing framework (unit tests)
So Qt is a full featured and very reliable environment — I use it for 10 years.
And: Qt integrates seamlessly into IDEs like VC++, Eclipse or provides its own IDE «QtCreator».
I had such experience working in Acronis. We had the same codebase used to build binaries (fully packaged installers, actually) targetting Win32/64, Linux, and OS X (and a bunch of other more exotic platforms, such as EFI), with everyone working in their IDE of choice. The trick here is to avoid compiler- and IDE-specific solutions, and make your project fully buildable from clean cross-platform make files. Note that you can perfectly well use any make system together with VC++, so it isn’t a problem (we used Watcom make for historical reasons, but I wouldn’t recommend it).
One other trick you can do is add a make script that automatically produces project files from the input lists in your makefiles, for all IDEs you use (e.g. VS and Eclipse CDT). That way, every developer generates that stuff for himself, and then opens those projects in IDE to edit/build/debug, but the source repository only has makefiles in it.
Ensuring that code is compilable for everyone can be a problem, mostly because VC++ is generally more lax in applying the rules than g++ (for example, it will let you bind an rvalue to a non-const reference, albeit with a warning). If you compile with treat warnings as errors, and highest warning levels (with perhaps a few hand-picked warnings disabled), you will mostly avoid this. Having contiguous rolling build set up is another way to catch those early. One other approach we’ve used in Acronis is to have the Windows build environment have Cygwin-based cross-compilation tools in it, so any Windows dev could do a build targeting Linux (with the same g++ version and all) from his box, and see if that fails, to verify that his changes will compile in g++.