A History of Porting Software
I’ve been involved in creating and maintaining commercial and open source software for as long as I can remember, reaching back to 1996 when the world wide web was in its infancy, and Java wasn’t even a year old.
I was attracted to the NetBSD project because of its focus on having its software run on as many hardware platforms as possible. Its slogan was, and remains “Of course it runs NetBSD”.
Although the NetBSD team worked tirelessly for its operating system to work across every imaginable hardware platform; much of the new open-source software development was talking place in for the i386 focused GNU/Linux operating system, not to mention the huge volume of Windows-only software that Wine tried, and mostly failed, to make available to people on non-Windows operating systems.
Advocates of cross-platform software like me were then constantly choosing between recreating or porting this software depending on its licenses terms and source availability just so we can use it on our platform of choice.
Some of the early of my open source contributions that are still available to download demonstrate this really well such as: adding NetBSD/OpenBSD support to Afterstep asmem in early 2000. Or allowing CDs to be turned into MP3s on *BSD platforms with MP3c in the same year.
In 2002 when the large and ambitious KDE and GNOME desktops started to dominate the Linux desktop environments, I worked on changes to the 72 separate packages needed bring GNOME 2 and to NetBSD and became the primary package maintainer for a number of years.
As an early adopter of C# and the Microsoft .NET Framework I also worked through 2002 and 2003 to make early versions of the Mono project execute C# code to FreeBSD, NetBSD, and OpenBSD too.
The #ifdef solution
How was software ported between platforms back in those days? Well to be honest, we cheated.
We would find the parts of the code that were platform specific and add #ifdef and #ifndef statements around them with conditions instructing the compiler to compile, or omit, different sections of code depending on the target platform.
Here is an example of read_mem.c from asmem release 1.6:
/* * Copyright (c) 1999 Albert Dorofeev <Albert@mail.dma.be> * For the updates see http://bewoner.dma.be/Albert/ * * This software is distributed under GPL. For details see LICENSE file. */ /* kvm/uvm use (BSD port) code: * Copyright (c) 2000 Scott Aaron Bamford <sab@zeekuschris.com> * BSD additions for for this code are licensed BSD style. * All other code and the project as a whole is under the GPL. * For details see LICENSE. * BSD systems dont have /proc/meminfo. it is still posible to get the disired * information from the uvm/kvm functions. Linux machines shouldn't have * <uvm/vum_extern.h> so should use the /proc/meminfo way. BSD machines (NetBSD * i use, but maybe others?) dont have /proc/meminfo so we instead get our info * using kvm/uvm. */ #include <stdio.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include "state.h" #include "config.h" #ifdef HAVE_UVM_UVM_EXTERN_H /* sab - 2000/01/21 * this should only happen on *BSD and will use the BSD kvm/uvm interface * instead of /proc/meminfo */ #include <sys/types.h> #include <sys/param.h> #include <sys/sysctl.h> #include <uvm/uvm_extern.h> #endif /* HAVE_UVM_UVM_EXTERN_H */ extern struct asmem_state state; #ifndef HAVE_UVM_UVM_EXTERN_H #define BUFFER_LENGTH 400 int fd; char buf[BUFFER_LENGTH]; #endif /* !HAVE_UVM_UVM_EXTERN */ void error_handle( int place, const char * message ) { int error_num; error_num = errno; /* if that was an interrupt - quit quietly */ if (error_num == EINTR) { printf("asmem: Interrupted.\n"); return; } switch ( place ) { case 1: /* opening the /proc/meminfo file */ switch (error_num) { case ENOENT : printf("asmem: The file %s does not exist. " "Weird system it is.\n", state.proc_mem_filename); break; case EACCES : printf("asmem: You do not have permissions " "to read %s\n", state.proc_mem_filename); break; default : printf("asmem: cannot open %s. Error %d: %s\n", state.proc_mem_filename, errno, sys_errlist[errno]); break; } break; default: /* catchall for the rest */ printf("asmem: %s: Error %d: %s\n", message, errno, sys_errlist[errno]); } } #ifdef DEBUG /* sab - 2000/01/21 * Moved there here so it can be used in both BSD style and /proc/meminfo style * without repeating code and alowing us to keep the two main functions seperate */ #define verb_debug() { \ printf("+- Total : %ld, used : %ld, free : %ld \n", \ state.fresh.total, \ state.fresh.used,\ state.fresh.free);\ printf("| Shared : %ld, buffers : %ld, cached : %ld \n",\ state.fresh.shared,\ state.fresh.buffers,\ state.fresh.cached);\ printf("+- Swap total : %ld, used : %ld, free : %ld \n",\ state.fresh.swap_total,\ state.fresh.swap_used,\ state.fresh.swap_free);\ } #else #define verb_debug() #endif /* DEBUG */ #ifdef HAVE_UVM_UVM_EXTERN_H /* using kvm/uvm (BSD systems) ... */ #define pagetok(size) ((size) << pageshift) int read_meminfo() { int pagesize, pageshift; int mib[2]; size_t usize; struct uvmexp uvm_exp; /* get the info */ mib[0] = CTL_VM; mib[1] = VM_UVMEXP; usize = sizeof(uvm_exp); if (sysctl(mib, 2, &uvm_exp, &usize, NULL, 0) < 0) { fprintf(stderr, "asmem: sysctl uvm_exp failed: %s\n", strerror(errno)); return -1; } /* setup pageshift */ pagesize = uvm_exp.pagesize; pageshift = 0; while (pagesize > 1) { pageshift++; pagesize >>= 1; } /* update state */ state.fresh.total = pagetok(uvm_exp.npages); state.fresh.used = pagetok(uvm_exp.active); state.fresh.free = pagetok(uvm_exp.free); state.fresh.shared = 0; /* dont know how to get these */ state.fresh.buffers = 0; state.fresh.cached = 0; state.fresh.swap_total = pagetok(uvm_exp.swpages); state.fresh.swap_used = pagetok(uvm_exp.swpginuse); state.fresh.swap_free = pagetok(uvm_exp.swpages-uvm_exp.swpginuse); verb_debug(); return 0; } #else /* default /proc/meminfo (Linux) method ... */ int read_meminfo() { int result; result = lseek(fd, 0, SEEK_SET); if ( result < 0 ) { error_handle(2, "seek"); return -1; } result = read(fd, buf, sizeof buf); switch(result) { case 0 : /* Huh? End of file? Pretend this did not happen... */ break; case -1 : error_handle(2, "read"); return -1; default : } buf[result-1] = 0; result = sscanf(buf, "%*[^\n]%*s %ld %ld %ld %ld %ld %ld\n%*s %ld %ld %ld", &state.fresh.total, &state.fresh.used, &state.fresh.free, &state.fresh.shared, &state.fresh.buffers, &state.fresh.cached, &state.fresh.swap_total, &state.fresh.swap_used, &state.fresh.swap_free ); switch(result) { case 0 : case -1 : printf("asmem: invalid input character while " "reading %s\n", state.proc_mem_filename); return -1; } verb_debug(); return 0; } #endif /* (else) HAVE_UVM_UVM_EXTERN_H */ int open_meminfo() { #ifndef HAVE_UVM_UVM_EXTERN_H int result; if ((fd = open(state.proc_mem_filename, O_RDONLY)) == -1) { error_handle(1, ""); return -1; } #endif /* !HAVE_UVM_UVM_EXTERN_H */ return 0; } int close_meminfo() { #ifndef HAVE_UVM_UVM_EXTERN_H close(fd); #endif /* !HAVE_UVM_UVM_EXTERN_H */ return 0; }
It wasn’t neat. It increased code complexity and maintenance costs, but it worked. And we all accepted it as the best we had for now.
Hopes of a Brave New World
Like many cross-platform advocates, I had big hopes for Java and C# with the Microsoft .NET Platform. But sadly we never saw the fulfilment of their “platform independent” coding promises. Too many times we have to choose between a GUI toolkit for a platform and looking out of place. Other times we had to P/Invoke to native APIs to get at functionality not exposed or reproduced by the frameworks. Even now the GUI toolkit Gtk# is recommended over standard Windows’ System.Windows.Forms on Mono when creating C# programs for Linux or *BSD.
Cross Platform Toolkits such as SWING for Java and Qt for C++ sprung up to abstract the user from the platform they were working with. But they were primarily GUI toolkits and their APIs only went so far, and eventually, like it or not, all but the most simple applications ended up with a native API call or two wrapped in an #ifdef style condition.
How Web Development Made it Worse
With the rapid increase in Web Development many saw this as finally the way to deliver software across multiple platforms. Users accessed software via a web browser such as Netscape Navigator and didn’t need the code to work on their own PC or operating system.
Of course behind the scenes the CGI programs were still platform specific or littered with #ifdef statements if they needed to work on more than one server OS. But the experience of the end user was protected from this, and it looked like a solution may be in the pipeline.
But then the Netscape vs Internet Explorer browser wars happened. Browsers competed for market share by exposing incompatible features and having sites marked as ”recommended for Netscape” or “works best in IE”. People wanting to support multiple browsers started having to litter their code with the JavaScript equivalents of #ifdef statements. The same happened again CSS as it became popular. Nothing really changed.
Enter the Mobile
Then along came the iPhone, and made a bad situation even worse.
Those who went for a native implementation had to learn the rarely used Object-C language. This helped Apple to avoid competition as developers scrambled to be part of the mobile revolution, but deliberately made portability harder rather than easier. That still remains part of their strategy today.
People turning again to the web for solutions found that accessing web sites carefully formatted to look great on 1024×768 screens, now being viewed on a tiny mobile phone screen in portrait orientation – was ugly at best, but more often unusable! And it wasn’t just about text size. Touch and other mobile specific service meant users expected a different way of interacting with their applications, and browser based software felt more out of place than ever. Yes Responsive Web design and HTML 5 go a long way towards solving some of these web specific mobile issues, but it doesn’t take us away from the #ifdef style logic that has become an accepted part of web application development as it did C and C++ development before it.
So What is to be Done?
Most of this article has been about a history of failures to tackle cross-platform software head on. Each attempt did bring us a little closer to a solution, but throughout we resigned ourselves to the fact that #ifdef style code was still ultimately necessary.
As application designers and developers we had to choose between how native our applications felt and limiting users from using our software in situations we didn’t plan for.
For almost two decades I’ve been involved in trying to overcome this cross-platform problem. Now the landscape is more complicated than ever. Can the same software really run without compromise both inside and outside the browser? Can we really have a native look and feel to an application on a mobile, tablet, and as desktop PC, is wearable computing going to be the next spanner in the works?
All this is why to move forward, we went back to basics. We thought first about how software was designed, rather than the libraries and languages that we used. We first made the Mvpc design pattern, and only then did we make there reference libraries and the commercial Ambidect Technology (soon to be known as Ambicore). Its fair to say that our many years of experience led us to be able to finally learn from the past we had been so involved with, rather than allowing ourselves to repeat our mistakes again and again.
Because Ambicore provides access to the whole .NET Framework gives a complete cross-platform API that’s developers already know. Use of C# as our first reference language gives us access to the great thinking that went into creating the Java JVM and Microsofts IL environments that really can abstract us from the operating system and help us avoide #ifdef statements.
Providing native GUI interfaces for each platform means applications using the platforms own recommended toolkit helps applications look and feel native everywhere – simply because they are native to each platform.
Providing a design pattern that works equally well in request-response stateless environments and in rich state-full environments allows us from day one to provide a browser based experience for those who want or need it, as well as a native rich client experience for those wanting to get more from their Windows PCs, phones, tablets, Macs, Linux, *BSD, or…
Its taken 17 years of personal involvement, and recognising and listening to visionaries in the industry. But by standing on the shoulders of others we re-thought the problem, knowing #ifdef statements were as much part of problem as they were a solution. We redesigned the development pattern to be portable by default, not as an after thought. And we based our reference libraries on trusted platforms from market leaders such as Microsoft to make our technology available to the largest pool of developers possible in a language, framework, and IDE they already know.
We are stepping into a new chapter of software development where the platform and device is there to enable, not restrict, the end user from the software they want. And just as we stood on the shoulders of giants to get here – we want you to join us in the new world too.
