Why Standards Alone Won't Get You Portable Software and How to Make Open-Source Middleware Work for You
Problem: Programming Yourself into a Corner with OS APIs
An OS can be viewed as a "silicon abstraction layer," which shields application software from the details of the underlying hardware. If just one instance of one version of an OS application programming interface (API) were adopted universally, our profession would clearly be simplified. As noted above, however, that's not the case today, nor will it ever be, due to the need both to support legacy applications and to advance technology. As a result, the common practice of programming applications directly to OS APIs yields the following problems:
It's not portable. Some standards are implemented on only a subset of your target platforms. For example, three popular threading APIs are Windows, UNIX International (UI) threads, and POSIX threads (Pthreads). They all have different features and semantics, and of course different APIs. Therefore, if your code needs to run on Windows and anything else that isn't Windows, you'll need to deal with at least two of these three standards. To make matters worse, these standards and their APIs have evolved over time, so code written to an earlier version of the API may not compile with later versionsor it may compile, but behave differently! If the source code for OS APIs isn't available, you can't adjust it to achieve backward compatibility. Likewise, trying to integrate your own versions of vendor-supplied libraries is problematic, even if you could find a way to obtain the source code.
The differences are tedious to find and work around. For instance, the enormously popular BSD Socket API is used for TCP/IP network programming. It's widely implemented and you can usually count on it being available on any platform that supports the TCP/IP networking protocols. However, the integration of the Socket API into the operating system's runtime libraries can yield functionality that's not portable. For example, although the Socket API defines the send() and recv() functions to send and receive socket data, the read() and write() system calls on many UNIX systems can also be used for the same purpose, which allows simple substitution of another interprocess communication (IPC) mechanism (such as pipes) for sockets. However, code written to take advantage of this feature won't work on systems in which the Socket API is not closely united with other I/O subsystems, as you've no doubt noticed if you've tried to port UNIX-based Socket applications to other operating systems, such as Windows or some real-time systems.
It's error prone. Native OS APIs written in C often lack type-safe, reentrant, and extensible system function interfaces and function libraries. For example, endpoints of communication in the Socket API are identified via weakly typed integer or pointer I/O handles. Weakly typed handles increase the likelihood of subtle programming errors that don't surface until run time, which can cause serious problems for your customers.
It encourages inadequate design techniques. Many applications written using OS APIs are based on algorithmic design, rather than object-oriented (OO) design. Algorithmic design decomposes the structure of an application according to specific functional requirements, which are volatile and likely to evolve over time. This design paradigm therefore yields nonextensible software architectures that can't be customized readily to meet changing application requirements.
The bottom line is that it's easy to program yourself into a corner by developing applications entirely from scratch using nonportable native OS APIs and algorithmic design techniques. In this age of economic upheaval, deregulation, and stiff global competition, it's prohibitively expensive and time-consuming for companies to make these mistakes, particularly when there's a better way!