Is Windows Safari using Objective-C 2.0?

November 12th, 2009

If you look at the Safari installation directory on Windows you probably noticed the objc.dll file. Are you curious if Windows Safari is using Objective-C version 1.0 or 2.0? I was, and this is my little adventure on trying to find that out. The short answer is NO. Safari on Windows still uses Objective-C 1.0. In this article I’m going to build the Objective-C DLL and show you why I don’t think Safari for Windows is using version 2.0.

files

The Objective-C Runtime Environment

In my last article I showed you how to build a bare bones Objective-C program in Windows with Cygwin. In that case, the Objective-C runtime used was installed with Cygwin. You can execute the following command to take a peek at the Objective-C header and library files that Cygwin uses.

find /lib/gcc | grep objc

This is not the same Objective-C runtime that Apple uses when compiling Safari for Windows. In fact there are three Objective-C runtime environments that I know of:

  1. Cygwin
  2. Apple
  3. GnuStep

We used Cygwin in the last article and now we’re going to build Apple’s implementation on Windows. I may discuss GnuStep at a later date.

Let’s Start Building Something

The first thing we need to do is get the latest source.
http://www.opensource.apple.com/tarballs/objc4/objc4-437.tar.gz

Once you unpack the tarball it’s time to find out how to build this sucker. You won’t find a Makefile but you will find objc.vcproj. This is a Microsoft Visual Studio project file.

If you don’t have Visual Studio installed you can download the free version here. Why Microsoft insists on on continuing to charge for an IDE is beyond me. Must be a cultural thing. What you want is Visual C++ Expression Edition 2008. It may or may not work with the new 2010 Beta; I have no idea. I ran 2008 so I’m telling you to do the same.

Once you have Visual Studio installed, open up the project file and choose build –> build solution. You’re going to get some errors about AvailablityMacros.h and TargetConditionals.h not being found.

Did you really think it was going to be that easy?

Error #1 TargetConditionals.h

If you have XCode installed on a Mac you can easily find TargetConditionals.h. However, that file doesn’t support the Microsoft compiler. This is evident by the comment at the top of the file. It’s good to read the source no?

/*
     File:       TargetConditionals.h
 
     Contains:   Autoconfiguration of TARGET_ conditionals for Mac OS X and iPhone
 
                 Note:  TargetConditionals.h in 3.4 Universal Interfaces works
                        with all compilers.  This header only recognizes compilers
                        known to run on Mac OS X.
 
*/
 
#ifndef __TARGETCONDITIONALS__
#define __TARGETCONDITIONALS__

So with a little Googly magic you’ll find the TargetConditionals.h that you’re looking for here under Universal Interfaces 3.4.2. Of course, a *.img.bin file isn’t much help on a Windows operating system so you’re going to have to extract the files on a Mac and then copy them to the Windows Machine.

Oh, and the files are going to have those classic Mac line breaks instead of Unix line breaks so you’re going to have to convert them.

for file in `ls *.h`
do
  tr '\r' '\n' < $file  > tempfile.txt
  mv tempfile.txt $file
done

Put those UniversalHeaders files you worked so hard to get somewhere logical, and include the header files in your project search path. What’s that? You can’t figure out where to do that? Confusing Microsoft UI got you down?

Tools –> Options –> VC++ Directories

Then set the “Show directories for” combo box to “Include files”.

includeFiles

If you do another build then you’ll notice there are no complaints about TargetConditionals.h but Visual Studio still can’t find AvailabilityMacros.h.

Error #2 AvailabilityMacros.h

To get rid of the AvailabilityMacros.h error grab all the header files in the latest SDK you installed on your Mac. We are trying to compile the version of Objective-C that is compiled on Snow Leopard so you should probably get the 10.6 *.h files.

Create another directory for some extra includes. I called mine
/cygdrive/c/Dev/usr/include/VisualStudio, but you can call it what you like. Now copy these three files to that new directory, and add the directory to the list of include directories in Visual Studio.

  • Availability.h
  • AvailabilityInternal.h
  • AvailabilityMacros.h

Error #3 stdint.h

Stdint.h is used for making C code more portable and you need a copy.

Download a copy and put it in that new include directory you created.
http://msinttypes.googlecode.com/svn/trunk/stdint.h

Error #4 _unmap_image_nolock

Next you will see a link error about some functions in the file objc-runtime-old.m. There is some information about this issue here:
http://lists.apple.com/…/msg00075.html
http://lists.apple.com/…/msg00076.html

Basically you need to comment out three functions so that it will compile. The explanation on the mailing list is that the source is out of sync which means nobody at Apple has probably tried to build objc on Windows for a while.

  • unmap_image
  • map_images
  • load_images

Add #if !TARGET_OS_WIN32 … #endif around those three functions.

#if !TARGET_OS_WIN32
/***********************************************************************
* unmap_image
* Process the given image which is about to be unmapped by dyld.
* mh is mach_header instead of headerType because that's what 
*   dyld_priv.h says even for 64-bit.
**********************************************************************/
__private_extern__ void 
unmap_image(const struct mach_header *mh, intptr_t vmaddr_slide)
{
    recursive_mutex_lock(&loadMethodLock);
    unmap_image_nolock(mh, vmaddr_slide);
    recursive_mutex_unlock(&loadMethodLock);
}
 
 
/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
**********************************************************************/
__private_extern__ const char *
map_images(enum dyld_image_states state, uint32_t infoCount,
           const struct dyld_image_info infoList[])
{
    const char *err;
 
    recursive_mutex_lock(&loadMethodLock);
    err = map_images_nolock(state, infoCount, infoList);
    recursive_mutex_unlock(&loadMethodLock);
 
    return err;
}
 
 
/***********************************************************************
* load_images
* Process +load in the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: acquires classLock and loadMethodLock
**********************************************************************/
__private_extern__ const char *
load_images(enum dyld_image_states state, uint32_t infoCount,
           const struct dyld_image_info infoList[])
{
    BOOL found;
 
    recursive_mutex_lock(&loadMethodLock);
 
    // Discover +load methods
    found = load_images_nolock(state, infoCount, infoList);
 
    // Call +load methods (without classLock - re-entrant)
    if (found) {
        call_load_methods();
    }
 
    recursive_mutex_unlock(&loadMethodLock);
 
    return NULL;
}
#endif

Error #5 SRCROOT & DSTROOT

We’ve almost got this DLL built, we just need to do one last thing. Several places in the project properties the variables $(SRCROOT) and $(DSTROOT) are referenced.

srcRoot

Created a batch file to define those variables right before opening up the Visual Studio Project file.

set DSTROOT=c:\Dev\builds\ObjC\
set SRCROOT=c:\Dev\sourceRoot\ObjC\
C:\cygwin\bin\cygstart.exe objc.vcproj

Now you should be able to successfully build the objc.dll library. It can be found under $DSTROOT\AppleInternal\bin\objc.dll.

Attempt to Build Objective-C 2.0

So far you’ve built Objective-C 1.0. Now let’s try to build version 2.0. If you look at the source code you see __OBJC2__ littered all over the kode. To build version 2.0 you need to define it.

Add /D “__OBJC2__” to the C/C++ command line arguments in the project properties and do a build. You will get lots of errors.

If you’re building on 32-bit windows like myself you’ll get an error about an unknown preprocessor command in objc-runtime-new.m. Apparently the Microsoft C++ compiler does not recognize the #warning directive, must be a cultural thing.

430
431
432
433
434
435
436
#if defined(__x86_64__)
    uint16_t *p = (uint16_t *)(dst + vtable_prototype_index_offset + 3);
    if (*p != 0x7fff) _objc_fatal("vtable_prototype busted");
    *p = index * 8;
#else
#   warning unknown architecture
#endif

I didn’t try too hard to build Objective-C 2.0 because I bumped into the following nugget when perusing objc-confg.h.

28
29
30
#if TARGET_OS_EMBEDDED  ||  TARGET_OS_WIN32
#   define NO_GC 1
#endif

Conclusion

If Objective-C 2.0 can be built on 32-bit Windows I don’t know how to do it. Garbage collection is one of the staple features of Objective-C 2.0 (properties and fast enumeration are just syntactic sugar) and if it’s not supported, is there really any point of building it? Therefore we can firmly conclude that Safari for Windows is not using Objective-C 2.0, at least the 32-bit version anyway.

Keep on Koding on!

References
A Quick Objective-C 2.0 Tutorial

Objective-C on Windows Tutorial

November 5th, 2009

This is a explanation of how to compile an Objective-C hello world program on the Windows platform. To follow this tutorial you will need cygwin installed with gcc.

Let’s get started.

First create the header file Hello.h

#import <objc/Object.h>
 
@interface Hello : Object {
 
}
 
-(void)world;
 
@end

Because this is a pure Objective-C program and does not use Apple’s cocoa framework the Hello object extends Object instead of NSObject.

Now create the implementation file Hello.m.

#import "Hello.h"
 
@implementation Hello
 
-(void)world {
  printf("Hello World!\n");
}
@end

Nothing unusual in the Hello.m file. Let’s do some compiling.

gcc -c -Wno-import Hello.m

GCC can compile and link programs so we pass the -c flag to tell it to compile, and it happily creates a sparkling Hello.o object file for us.

The second flag is much more interesting. It specifies not to display warnings if #import statements are used. The free software movement fanatics don’t like #import and it is listed as an obsolete feature[2]. Of course, it has probably been listed as obsolete for twenty years, and if it is ever removed Apple will just fork GCC. Oh wait, Apple is now providing llvm as an option with Xcode so that problem has already been solved.

Now let’s use our object in a program. Create a main.m file.

#import <objc/Object.h>
#import "Hello.h"
 
main () {
 
  id hello;
 
  hello = [Hello new];
  [hello world];
  [hello free];
}

New and free? Where’s alloc, init, retain and release? Those are all defined in NSObject of the Cocoa framework and are not part of the core Objective-C language.

Now compile this new implementation file to create a second object file main.o.

gcc -c -Wno-import main.m

Take the two object files and squish them together with the GCC linker to create a windows executable.

gcc -o helloWorld Hello.o main.o -lobjc

This is simple stuff. The -o parameter specifies the name of the executable. Then we give a list of the object files we created and specify to link to the Objective-C library (-lobjc). Now you should have helloWorld.exe on disk.

The -mno-cygwin Option

You’re really proud of your helloWorld.exe and you think you might have something here. You want to sell your program. Who wouldn’t pay for an application that displays “Hello World”? It makes that computer seem so friendly.

Well you might have a problem. You see you built your program in cygwin and the EXE got linked to a DLL file called cygwin1.dll. If you link and distribute this DLL file then your program falls under the GPL license agreement.

If the following command displays cygwin1.dll then you have a GPL problem.

objdump -p helloWorld.exe | grep “DLL Name”

If you prefer a GUI Dependency Walker is a free tool that can also show you what DLL’s an executable is linked to.

By adding the -mno-cygwin option to the link stage we can create a helloWorld.exe that we can sell without the constraints of the GPL.

gcc -o helloWorld -mno-cygwin Hello.o main.o -lobjc

objdump -p helloWorld.exe | grep “DLL Name”

Kode on.

References

1. Indiana University – CS304 – Compiling Objective-C http://www.cs.indiana.edu/classes/c304/ObjCompile.html

2. The C Preprocessor for gcc version 3(Section 11.3.2)
http://gcc.gnu.org/onlinedocs/gcc-3.4.6/cpp.pdf

3. -mno-cygwin — Building Mingw executables using Cygwin http://www.delorie.com/howto/cygwin/mno-cygwin-howto.html

Simulating the open command in cygwin

August 5th, 2009

You may or may not be familiar with the open command in the terminal application. With the open command you can type
“open .”
at the command line and a Finder window will appear. You can also type
“open <filename>”
and the file will be opened with it’s associated application, as if you double clicked on it from the finder.

Now suppose you’re a Mac aficionado using Windows. You’ve installed Cygwin to gain access to the power of Unix but you miss the open command. The following script can help you out.

#!/usr/bin/bash
 
if [ -z "$1" ] ; then
  echo "I'm hungry give me a parameter."
  exit
fi
 
PARAM=`cygpath --dos --absolute "$1"`
 
/cygdrive/c/Windows/explorer.exe $PARAM &

UPDATE: Or you can alias the cygstart command in your .bashrc file as this stackoverflow posting explains.

The Ripple Effect

July 25th, 2009

Software by it’s very nature, tends towards complexity.

When a program is in it’s infancy, with few features and few lines of code, adding a new feature can usually be done quickly. But as the program evolves, although adding a new feature may still be quick and easy it may cause little side effects in other parts of a program. So adding the new feature may not take long but adjusting the rest of the program so that everything flows properly will take time.

Think of stable software as a calm pond, with a surface smooth as glass and adding a feature is like throwing a pebble in that pond, which causes ripples that need to be dealt with. One of the things that distinguishes a junior from a senior programmer is that the junior programmer will not foresee these ripples and will think he has completed his task prematurely.

To illustrate what I’m talking about I’m going to talk about a new feature that will be in the next SunFlower release.

The Problem

Snapshots in SunFlower can pile up, and eventually you need to delete some. To delete a snapshot you must control-click on the snapshot and choose delete.

rightClick

This interface works but is kludgey. It’s not convenient when you need to delete many snapshots because control-clicking on each snapshot is slow and cumbersome. There is definitely room for improvement in this interface.

The Solution

The solution is simple. Add a delete button that appears when the user positions the mouse over the snapshot. This is an established interface, it’s how Safari deals with the close buttons for tabs without making the interface feel cluttered.


mouseOver

The Ripples

So what were some of the ripples caused by adding the delete button?

Because the user can navigate the snapshots with the keyboard, we need to detect if the mouse is over a snapshot after keyboard events. We cannot simply use mouse move events to detect if the delete button should be shown. ((Ripple.))

In this case most of the ripples were design based. Snapshots have a state of being “read” or “unread”, and an image is displayed on the top right corner when the snapshot is brand new.

ripples

Having two images represent different things with varying red colours is not good. So the unread marker image now needs to be changed. ((Ripple.)) I could change the colour of the unread image to green but I’d prefer that both images weren’t the same shape to prevent confusion. ((Ripple.)) The delete button has nice shading and puts the unread marker image to shame so the ante has been upped. ((Ripple.))

Addendum (Aug 03 2009)

Brent Simmons describes this concept in more detail in a recent blog posting.

It’s not enough just to write the basic functionality and add a menu item that runs it. Even a feature as simple as this one requires some up-front thinking, some design.

The code behind the feedback window is, again, bigger than the http-call code. (By now you’ve gotten the idea that the core functionality of a feature is often the very smallest part.)

United Lemur Returns to a Teaser Page

July 3rd, 2009

The United Lemur website has been converted into a teaser page again.


lemur

Discovered with SunFlower.