Slashdot Mirror


Applications and the Difficulties of Portability?

insane_coder asks: "I'm a software developer who writes a lot of freeware utilities in C/C++ which are all cross platform and work well. Lately some of my users have been pestering me to stop wasting precious development time supporting minority OSs like Linux, and get more work done for the majority — the Windows users. Now all of my utilities are simple tools that perform various operations on files such as compression or rearranging. I've also made a few frontends for them using the excellent Qt library to allow the user to select a file and process using a simple GUI. In the dozens of applications I wrote, most of them several thousand lines long, I haven't written a single conditional for any particular OS. When I release, I just compile each app for all the OSs I have access to and post them on my website. I barely expend any effort at all to achieve portability. So the question I have to ask is: Why do the masses perceive portability as something that requires effort and a waste of time?" "Most applications don't do anything fancy or need to talk to devices and therefor there is no need to do anything special other than compile them on a particular OS to run on that OS. So why are there so many simple apps using native APIs to do simple things like file reading instead of the standard ones? Why are we projecting an image that one must go out of their way or switch to a different language in order to achieve portability?"

3 of 145 comments (clear)

  1. Portability Isn't Hard by RAMMS+EIN · · Score: 5, Informative

    For the most part, portability isn't hard. You can write pretty much all the functionality of your software without getting into platform-specific issues. Generally, the higher the level of abstraction a languages is at (assembly C Python), the easier it gets. Of course, you do have to use standard APIs and avoid platform-specific ones (win32, GNU extensions, etc.) Things that have caused trouble for me in the past:

      - Sockets (BSD sockets vs. Winsock's almost-compatible variant); this is not a problem in most higher level languages
      - GUIs (there isn't really a standard; perhaps wxWidgets?)
      - Threads (POSIX threads vs. whatever Windows has)
      - Processes (fork, AFAIK, really doesn't exist on Windows)

    If I need any functionality that isn't readily portable, I usually target POSIX or BSD, which makes my code portable to many *nix variants, and Windows using Cygwin.

    Of course, there are also a whole bunch of cross-platform libraries out there, like glib, the APR, SDL, Qt, ...

    --
    Please correct me if I got my facts wrong.
    1. Re:Portability Isn't Hard by Twylite · · Score: 5, Informative

      I think you've identified on most of the major areas that cause portability problems, with the possible exception of endianness. Since I actively maintain software under Windows, Linux and Solaris, let me add some comments:

      If you're developing a GUI application then a decent GUI library will handle most of your problems. Libraries like wxWidgets and QT provide cross-platform GUI widgets as well as thread creation and synchronization primitives. They usually also provide socket abstractions, or you can use another cross-platform library like ACE.

      Things get more hairy when you enter the realm of server applications and high performance. Software written for *nix is pretty much straightforwardly portable to Windows, but not the reverse.

      The difference is in the schedulers, which affects processes, threads, synchronization, and blocking and non-blocking IO. In a nutshell the Windows scheduler deals with threads and not processes, is a fair scheduler, and a thread can block on multiple conditions simultaneously.

      That should be a lot to digest, so let me try to explain that statement and its implications:

      First a disclaimer: I am going to talk about *nix in generalisations, because both Linux and Solaris support a variety of schedulers and the default scheduler can change between versions, and userland and kernel threading libraries behave very differently.

      ONE: The Windows scheduler schedules threads. Processes are merely containers for threads and their address space, access tokens, etc. Processes are not scheduled. This means that a multithreaded process on Windows has fundamentally different time sharing characteristics to a *nix application. The characteristics would be most similar to a multiprocess application in the *nix world. Most *nix schedulers I have encountered schedule processes first, then schedule threads within the process.

      TWO: The Windows scheduler is "fair" -- it is a multilevel queue with round-robin at each level. Threads with a higher priority will always preempt those of a lower priority, and priority elevation is used to prevent starvation.

      A number of *nix schedulers use a n adaptive algorithm that favours historically busy processes. This means that a producer-consumer approach may work well on a Windows system, but experience high latency on *nix (note: not reduced throughput under load, but increased latency). Such an approach can work on *nix, but must be implemented differently.

      Both Linux and Solaris can be set to use round-robin schedulers (e.g. Solaris's real-time class process), but this can degrade overall system performance.

      THREE: A thread can block on multiple objects simultaneously. I said "conditions" before, but I'd prefer to get more specific. A Windows thread can wait for the end of another thread or process, a mutex, and event (like a condition in *nix), an IO operation, a semaphore, a timer, and a couple of others. The thread can also block under any one, or all, of a number of objects are in the signalled state.

      Compare that to *nix where you can wait on one of anything except for IO, where you can use select (or poll or dev/poll or kqueue) to wait on multiple non-blocking file descriptors. kqueue is a little more functional, but to my knowledge still doesn't support synchronization objects. If you are using SysV semaphores you can wait for multiple semaphores to be signalled.

      This has huge implications for application design. On Windows you can have a consumer thread wait on multiple producer threads using one mutex per consumer to control synchronization. All threads can be awoken from any wait state by a global event to inform them of reconfiguration, shutdown, etc. This is quite trivial, and is a straightforward and easily understood design.

      Move the same design to a *nix platform and you have problems. First your consumer thread can't wait on multiple producers without some radical changes (use could pipes rather tha

      --
      i-name =twylite [http://public.xdi.org/=twylite], see idcommons.net
  2. Hah! I have yet to see a decent port to MacOS by Blakey+Rat · · Score: 3, Informative

    Caveat: If your app is command-line, then this isn't really an issue. (Well, it still sort-of is, in that DOS CLI apps have different mechanisms for help than Unix ones, but we'll ignore those details.)

    The reason cross-platform is hard is because GUIs behave very differently from each other. To be a "correct" Mac OS program, you must support:

    1) AppleScript, at least the bare minimum actions.

    2) The Services menu. Many ported apps don't have it.

    3) The integrated spell checker. Most ported apps don't have it. Including biggies like Microsoft Office. (Needless-to-say, Firefox also doesn't support the integrated spell checker.)

    4) Verb dialog boxes. That is, no "yes/no" questions in dialogs, they must all be "Save" "Don't Save" (or similar.) In addition, the location of Ok and Cancel are different in different OSes.

    5) Drag&Drop, another simple feature the majority of ported apps get wrong. (Note: this includes drag&drop text editing as well as dragging snippets to the desktop.)

    6) Mac-like edit boxes. Here's an example ported apps almost always get wrong: if your cursor is at the bottom line, but in the middle of the text, and you hit the down arrow the cursor should move to the end of the bottom line. On Windows, it should do nothing. On Linux... well I have no clue if Linux has any standards for arrow behavior in edit boxes. (Firefox gets this wrong also, as do many, many apps.)

    7) Standard Mac menu shortcuts. This is pretty easy since other OSes ripped-off most of Apple's shortcuts anyway.

    8) Being responsive to sleep, hibernate and shut down requests from the OS. Every time I see OS X telling me that shutdown was cancelled because of some mis-behaving app I want to scream.

    9) Not relying on any absolute paths other than those defined by the OS. Lot of apps get this wrong.

    10) Using Apple's color picker, font picker, "Special Characters" picker, etc instead of your own. Many apps get this wrong.

    I'm probably missing items on this list. In addition, Windows has items on its list different from the Macintosh list. (For instance, coping with Active Directory, having an installer.)

    I can guarantee that your cross-platform framework gets at most half of these things right. The Java VM gets basically none of them right. Firefox gets like 3 of them right. The reason you think cross-platform development is easy is one or more of the following:

    1) You don't bother to QA your product on Mac/Windows/whatever. (This is the most likely.)

    2) Your programs have trivial GUIs and/or you don't give a whit about the quality of the GUI.

    3) Your programs are CLIs and have no GUI at all. (Note that if this is the case, they're still probably wrong on DOS, which is quite different from Unix CLIs.)

    Tell you what, the instant I see a single ported app to Mac OS X that actually looks and behaves like an OS X app, I'll eat my words and agree with you. But I don't think that'll happen.