Monday, December 19, 2005

Most of the past weekend, and much of tonight was spent working on a port to Windows XP. The goals at this stage were:

-- get the cross platform portions of the layout engine building
-- build Windows versions of the mozilla JavaScript library, libexpat, and NSPR downloaded or built, and get them checked into CVS
-- create Windows-specific versions of the project makefiles

To get the layout engine porting started, the first thing that I needed to do was get Windows-based makefiles written. In addition, I have a few bash scripts that were used to setup the runtime environment (their primary function is to create a distribution directory appropriate for the platform, and to copy libraries and headers from their location in the development tree this location before invoking make). When going from MacOS X to Linux, porting the bash scripts was trivial, since both platforms use a GNU toolchain. One of the major differnces between MacOS X and Linux was caused by the layout of the dist directory (in MacOS X there is a fixed disk layout that is required by all applications (see Chapter XXX), while there are no hard and fast rules on Linux). Using the output of uname in my bash scripts allowed me to isolate differences. At the top of each bash script is the following:


#!/bin/sh
platform=`uname`;
if [ `echo $platform | grep -i "Linux"` ]
then
platform="linux"
fi
if [ `echo $platform | grep -i "Darwin"` ]
then
platform="macosx"
fi
if [ `echo $platform | grep -i "NT"` ]
then
platform="windows"
fi


With the platform variable set, all I need to do is perform tests of it within the script, for example:


if [ $platform = "macosx" ]
then
# code specific to MacOS X
fi


As far as makefiles go, at the moment, I have separate makefiles for each platform, one called Makefile.macosx and the other called Makefile.linux; I just made a copy of the MacOS X makefile to come up with the linux version, and tweeked the linux version slightly to deal with changes in flags passed to g++ that were required. Later I will create a more unified makefile, but for now, it's not high on my list of priorities.

To preserve my investment in the development of these makefiles and bash scripts, I installed cygwin on my Windows machine, which provides ports of bash and GNU make. The alternative would be to create a makefile compatible with Microsoft's nmake, and convert the bash scripts to DOS bat files (or, even better, install perl). But cygwin is easy enough to download (http://www.cygwin.com) and install.

As far as the compile/link portion of the toolchain goes, I made the decision to go with Microsoft Windows .NET. Starting with the Linux makefile, it was a relatively simple matter of text substitution -- I replaced "g++" in the Windows makefile with "cl", "link", and "lib", I replaced all occurances of .o with .obj, and changed the pathnames from which the compile read include files and libraries, and to which the compiler generated its output. The primary reason for going with .NET is my need to port the concrete widget implementation to a native Microsoft GUI toolkit (e.g, Win32). If this were a console based app, I might consider just using g++ (although cygwin does require a license if you are building a commercial application, something to keep in mind).

Microsoft provides a free version of their command line-based compiler for download (see http://msdn.microsoft.com/visualc/vctoolkit2003/). Or you can use Visual Studio .NET, which I recommend if you plan to do any debugging of your code (there might be freely available debuggers out there, perhaps even gdb(1) works just fine, but I have my head in the sand on that issue). I've done plenty of Windows programming in my life, and Microsoft tools were always available wherever I worked, and I suspect that they are available where you work, too.

Regardless of your choice of compiler, the main point is this - GNU make is decoupled from the actual compiler/linker that is used. Don't think for a moment that because you are using a Microsoft toolchain that you are forced to use nmake. It's far better to install cygwin, and use what you developed for MacOS X and Linux to build on Windows. Perhaps the most notable example of the use of building with the GNU make/Visual C++ combination is mozilla.org, which uses this strategy (developed by Chris Seawood) to build all their major apps (mozilla, firefox, thunderbird) on the Windows platform.

So, with the toolchain in place, the makefiles ported to Windows and the Microsoft toolchain, and the bash scripts updated to support Windows, it was time to type:

C:\ sh build

and see what happened.

For the most part, everything built unchanged, with the following exceptions.

The first problem was getopt(3) is not portable to Win32. Fortunately, NSPR provides a portable solution -- PL_CreateOptState(), PL_GetNextOpt(), and PL_DestroyOptState() -- and it was straightforward replacing one with the other.

The next problem was my use of basename(3) and dirname(3). Neither of these functions, as it turns out, are implemented by the Win32 api, nor are they implemented by NSPR. To fix this, I spent an hour or two developing and testing my own version of these functions. You can find an implementation of both of these functions by clicking here for the header and here for the source.

The next problem I faced was getcwd(3). Like both basename(3) and dirname(3), this function is the same on Linux and MacOS X since it is defined by POSIX, but on Windows, its name is _getcwd(), with a leading underscore (there are a few functions in Win32 like this, see my page on POSIX.1 interfaces on Win32, MacOS X, and Linux.

There are at least three ways to deal with this difference that come to mind:

-- use a macro to hide the difference in syntax. Fortunately, getcwd() and _getcwd() are the same except for the leading underscore, leading one to do something like this:

#if defined(XP_WIN)
#define GETCWD _getcwd
#else
#define GETCWD getcwd
#endif

However, Win32 requires you to include <direct.h>, while getcwd on Linux and MacOS X requires <stdlib.h>. So, there would need to be another #ifdef introduced into the sources referencing getcwd() to bring in the right header. Another reason against using is a macro is that, in general, macros are ugly -- they are hard to read, and can be hard to deal with in a debugger. And I tend to avoid using them. So, this isn't the way I ultimately went.

-- wrap getcwd() with a factory. The idea is to define an abstract class that implements getcwd(), and use a factory class to get a pointer to the corresponding, platform-specific concrete class, much like the layout engine uses a factory to wrap Cocoa, Gtk+, and Windows-based concrete GUI classes.

-- isolate getcwd() in a source file of its own, and use #ifdefs to in this source file to isolate the platform specific code. This is essentially the approach taken by NSPR.

A factory that just serves up just getcwd() is overkill, in my opinion. Were the factory to abstract a larger collection of APIs (much like NSPR), it would probably be worth doing. The overhead for supporting just one function, however, is in my opinion too great. So I decided to implement a function PL_getcwd(), using #ifdefs to separate the platform-specific code. Click here for the header, and here for the source.

Having ported the core layout engine code, my next step is to ponder the various options I have in front of me with respect to chosing a native GUI toolkit for the Windows platform. On MacOS X, this was a no-brainer -- Cocoa is the way to go. On Linux, the choice was a little more difficult - Gtk+ vs Qt. In reality, I could have gone either way, but since I have more Gtk+ experience, it was a natural choice to go with Gtk+ (though someday, after this book ships I imagine, I need to get busy learning Qt - I have a Zaurus Qtopia-based Linux PDA that is just waiting for me to write applications for it). On Windows, the only real choices are Win32, MFC, and .NET Framework. Win32 is clearly sufficient for the job (Mozilla's cross platform toolkit is based upon it), but the API is not modern -- choosing it would be equivalent to going with Carbon (MacOS Toolbox) on MacOS X, or Xlib on Linux. MFC is C++ based, but it is well know that it only provides little more than a wrapper above Win32. MFC shows signs of age as well. And both MFC and Win32 require the use of res files to describe controls and window/dialog layout, which is undesirable.

Microsoft, fortunately, has recognized the need to upgrade their GUI platform, and so the best option for this project is probably going to be to use their .NET Framework. In .NET there are classes for each of the GUI controls and containers that I have already running on the other platforms, and the syntax looks like it will be a whole lot cleaner to use than anything Microsoft has managed to put out in the past 20 years, at least from what I have seen so far. The only concerns I have are with the .NET CLR (Common Language Runtime), and .NET's permutations on the Standard C++ language. I'm hoping that I can compile native, vs. managed, and I am also hoping I can do this work in standard C++. Although, I suppose having neither would not necessarily be a deal killer.

To learn more about the .NET framework, the next few weeks will be spent writing "hello world" apps in .NET Framework to get an idea of just what I am signing up for. Then it will be a few hours I suspect of coding to bring the Windows port into existence. Stay tuned...