Close Encounter of the Mac Programming

2012/02/07 Bug of XCode GCC?

I wanted to compile YSFLIGHT binary on Mac OS X to start a new test phase.  To compile YSFLIGHT, I have been using a makefile that I wrote by myself, but this time I thought to upgrade it so that I can use the same makefile to build 32-bit and 64-bit binaries.  Then I came to know that Mac OS X Lion supports only 64-bit binary.  YSFLIGHT anyway requires OS X 10.5.8 or newer.  So, there is little point of maintaining 32-bit Mac OS X binary.  So, I decided to build only 64-bit binary from the next version of YSFLIGHT for Mac OS X.

It is a different story for Windows.  It makes sense to keep YSFLIGHT for Windows 32-bit because there are so many 32-bit Atom CPU based Windows laptops, which is fast enough to run YSFLIGHT.

By the way, I don't like IDE.  I would rather write makefile by myself.  IDE changes so often.  The time you spend for learning new menus, buttons, and other interface is absolute waste.  On the other hand, if I can build my program with makefile, it stays the same.  It doesn't change like an Visual Studio or XCode.  Only draw back is NMAKE, which comes with Visual C++, is utterly incapable.  It doesn't have a flexibility of gmake.  So, I need to write a separate makefile for Windows.  But, I can use common makefile mostly for Linux and Mac OS X.  I wanted to be more proficient in makefile.  So, I recently bought the following book.

Honestly, it is not a reference book.  I had to read from the beginning.  Maybe that's how this book is designed.  Nonetheless, I found a lot of powerful features of gmake as I read more into the book.  It is a very valuable book to me.  If you are tired of learning new interface every time Apple releases a new version of XCode, maybe you want to switch to makefile.

Some suggested to use cmake rather.  In fact I am using cmake for my real job.  But I don't like it.

I am often asked what to do with debugger if I don't use IDE.  Tell you what, if the bug is something you can easily find fix in the debugger, probably you can also fix it without debugger.  From my experience, the debugger doesn't help if you face a really tough one.  If it is a tough bug to fix, I rather save intermediate states and see when the program messed up first.  The debugger is utterly useless when your program runs perfectly in Debug mode, but messes up in Release mode, which happens too often.  Or, let's say your program messes up only when you feed a huge data once every ten runs or so.  The program may not crash, but simply the final output is a total mess.  In such a case, what I would do is first pick five to ten locations in my code and save intermediate states at those points. Then run the program in a batch over and over again until the program messes up.  Then I look at the intermediate states to find the place where the program starts straying from the right track.  The program messed up somewhere in between the very beginning of the program and the place where I see the first strange state.  I can then divide this part of code into another five to ten sections and again add state-saving code.  I repeat it until I find the location of the real problem.

Debugger is good at finding where the program is crashing.  But, the program crashing in a particular function doesn't mean the problem is in that function.  It only means that something went wrong before the program came to the point where it crashes.  No more than that.  Same information can relatively easily be obtained by adding bunch of printf or cout (as long as you make sure to add '\n' or endl, or the text message is just buffered and not really printed).

Which is not the problem I had to deal with.  Actually, when I tried to build 64-bit YSFLIGHT for Mac OSX, H got a weird link error as follows:

ld: in /Users/soji/ysbin/macosx64/lib/libfsguicore.a(fsgui.o), in section __TEXT,__text reloc 334: local relocation for address 0x0001C802 in section __text does not target section __const

The error message didn't give much information.  All I knew was something wrong with fsgui.o.  334 didn't seem to be a line number.  I had no idea what was at address 0x0001C802.  So I googled for "ld local relocation does not target section" and bunch of web pages came up.  It seemed to be a common problem.  However, most of those people tried to compile a program that they downloaded from somewhere and getting the above error.  The solution posted there were all program-specific.  It didn't solve my problem.

This error appeared only when I was trying to compile a 64-bit binary in Mac OS X.  I didn't get this error or something similar in Windows.  And, I didn't get the error either when I compiled 64-bit Linux binary.  It was specific to 64-bit binary for Mac OS X.

So, I tried to read the nature of the problem, and I didn't understand completely, but it appeared to me that some static data needs to be placed at some offset from a constant address.  The error message seemed to say that the linker couldn't do it for whatever reason.  Maybe the static data in the object file was too big and the linker failed to pack it.  Wait.  It's a 64-bit binary.  The linker is supposed to use 4G x 4G address space linearly.  How can a linker fail to pack some data, which should be far far less than 1 mega bytes.  But, anyway, I checked the size of the object files that were giving the above error.  Some of them were large.  But, some of them were quite small.  In the small one, all static data I was using in the code was wide-character strings.

Was it something to do with wide-character string?

I tried to change all of constant wide-character strings such as:

wStr.Set(L"X");

into:

const wchar_t x[]={'X',0};
wStr.Set(x);

The meanings of both pieces of code are pretty much the same.  Nonetheless, in the latter, the compiler may or may not put {'X',0} in the static-data area.  It can be placed in the stack area.  Maybe removing const qualifier can assure that x[] is placed in the stack, but I wanted to keep it const at least.  Anyway, it is a workaround.  I didn't want to make it a bigger mess.  In the former, L"X" most likely is placed in the static-data area.  Still the compiler may interpret it as the latter code, but I think it is unlikely.  This change essentially moves L"X" from the static-data area to the code area, and the code writes the content when the program reaches this place.

So, I was not very hopeful, but tried.  And WORKED!  I guess it is a bug of GCC that comes with XCode 3.  I believe Apple made modifications to the 64-bit binary generator for Mac OS X.  Apple may have screwed up when they made the modification. 

I also tried the following, and it worked, too.  This might be more humane way of writing.

const wchar_t x[]={L"X"};
wStr.Set(x);

By the way, I am using XCode 3.  The problem may have fixed in XCode 4.  But, if you are facing the above strange error, changing the way you write constant wide-character string might get rid of your problem.

2010/03/03 Opening a help file from menu & Identifying primary language

I realized I was forgetting to enable Help menu in YSFLIGHT for Mac OS X; it was not able to open a help file from menu.  I tried to fix it.  I wrote help files of YSFLIGHT in HTML.  What I needed to do was to launch a default web browser and open an HTML file in it from the YSFLIGHT main program.  In Windows, I can do it with ShellExecute function.  In my impression, doing something similar in Mac OS environment is in general more straight forward than doing it in Windows environment (with some exceptions).  I expected opening a default browser in Mac OS environment should be as easy as calling one function.  About an hour of search in Apple Developer Connection lead me to what's called "Launch Services."  If I knew that this thing was called "Launch Services," I could have jumped to the information needed instantly, but finding the key words "Launch Services" took about an hour.  I tried "execute", "command", "application", "external program", but all of them gave me too many clutters.  I think I finally searched with "cocoa open web site" or something similar to reach "Launch Services."  I wish someone come up with a method to search this kind of information more efficiently.

So I learned I had to use Launch Services.  Then I searched for some sample files and found this page.  This page showed an example of opening a web site with a default web browser.  I had adequate information to write my own functions.

void FsLaunchCommandC(const char cmdLine[])
{
    NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
    NSString *bundlePath;
    bundlePath=[[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/"];
    NSString *fullPath;
    fullPath=[bundlePath stringByAppendingString:[NSString stringWithUTF8String:cmdLine]];
    printf("Open File> %s\n",[fullPath UTF8String]);
    [[NSWorkspace sharedWorkspace] openFile:fullPath];
    [pool release];
}
void FsOpenWebSiteC(const char cmdLine[])
{
     NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
    printf("Open Web Site> %s\n",cmdLine);
    NSURL *url;
    NSString *urlString;
    urlString=[NSString stringWithUTF8String:cmdLine];
    url=[NSURL URLWithString:urlString];
    [[NSWorkspace sharedWorkspace] openURL:url];
    [pool release];
}

Help files are specified by relative path from the bundle directory.  So, FsLaunchCommandC function adds the bundle path in front of the given cmdLine to make it a full path.  FsOpenWebSiteC assumes cmdLine to be a URL, and it doesn't add anything.

These functions are written in Objective-C.  To use them from the main program of YSFLIGHT, I needed a C++ function that bridges between YSFLIGHT main program and the above functions.

extern "C" void FsLaunchCommandC(const char cmdLine[]);
extern "C" void FsOpenWebSiteC(const char url[]);
void FsLaunchCommand(const char cmdline[])
{
    if(strncmp(cmdline,"http",4)==0)
    {
        FsOpenWebSiteC(cmdline);
    }
    else
    {
        FsLaunchCommandC(cmdline);
    }
}

This function checks first four letters to choose which one of the two Objective-C function should be called.

One more thing I did was identifying primary language.  Since YSFLIGHT has many Japanese users, I tried to give a Japanese message when it is critical.  To do so, I needed to identify the primary language installed on the computer.  In Windows, I could do it with GetLocaleInfo function.  If it returns a text string "0411" it indicates that the primary language is Japanese.  In Mac OS, I need to use NSLocale class.  I was easily able to find NSLocale class by searching for keyword "locale," and wrote the following function.

const char *FsGetLanguageIdC(char buf[])
{
    NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
    NSArray *lang=[NSLocale preferredLanguages];
    buf[0]=0;
    if(NULL!=lang)
    {
        NSString *langId=[lang objectAtIndex:0];
        if(NULL!=langId)
        {
            static int first=1;
            if(1==first)
            {
                printf("Language Tag: %s\n",[langId UTF8String]);
                first=0;
            }
            strncpy(buf,[langId UTF8String],16);
            buf[16]=0;
        }
        else
        {
            printf("Primary Language Not Available.\n");
        }
    }
    else
    {
        printf("Preferred Language Not Available.\n");
    }
    [pool release];
    return buf;
}

If it returns "ja" it indicates that the primary language is Japanese.  But, I couldn't confirm it because I didn't have any Mac that the primary language was set to Japanese.  I tried to temporarily change the primary language of one of the iMacs in Wean Hall cluster, but apparently I didn't have a privilege to change the primary language.  I am reasonably sure it works though.

I really want to have a Mac for myself.  But, as I wrote in here, I cannot justify investing $2,000 for something that I cannot use for my work.  I wish new 15inch Macbook Pro comes with an eSATA port or an Express Card slot.

2010/01/27 Joystick Strikes Back

As I wrote in "Joystick-reading function and Joystick calibrator", I have already figured out how to read joystick axes and button status.  That's what I was thinking until I received from an E-mail from a YSFLIGHT for Mac OS X user.  He said he was not able to use a joystick on his Mac.  Luckily, he happened to be a student of Carnegie Mellon, and he kindly brought his MacBook and showed the symptom to me.  Also he ran some test programs that I wrote on his Mac, and I was able to diagnose the problem.  (I really appreciate his help!)

The conclusion was, the function called IOHIDDeviceInterface122::queryElementValue no longer works on Mac OS 10.6.  Also, my joystick reader was detecting X axis and button 0 twice.  One of the two detections was probably saying something else.  And, I learned that I could distinguish real X axis and something else by checking Element Type.  The first one was a Collection type, and the other was Misc for an axis or Button for a button.  I should simply ignore Collection type.  But, checking Element Type didn't solve the queryElementValue problem.

I came back to Apple Developer Connection, and found a shocking description in the following page.

http://developer.apple.com/mac/library/documentation/DeviceDrivers/Conceptual/HID/
new_api_10_5/tn2187.html#//apple_ref/doc/uid/TP40000970-CH214-SW

This page says:

Note: For binary compatibility, HID code written for Mac OS X version 10.4 ("Tiger") will continue to work for the lifetime of Mac OS X version 10.5 ("Leopard"). New development targeting Leopard should use the new HID Manager APIs.

I understand it is not possible to guarantee backward compatibility forever.  But, how come the code that worked on OS 10.4 no longer works on 10.6?  How long has it passed since 10.4?  It seems too early to drop the compatibility.

If they no longer support that function, maybe it was inevitable.  I don't know.  But, the reason why I ended up with using an old-fashioned way was because I found the old-fashioned way in Apple Developer Connection.  I hope they dropped the description of mark it big as "THIS CODE WILL NOT WORK ON OS 10.6 AND LATER!" so that I didn't have to waste time.

So, I started modifying the joystick-reading function with the new way of reading HID device values.  I tried to read Accessing a HID device, but it didn't make sense.  I am interested in a joystick.  But, the descriptions are too general.  Next, I downloaded an example called HID_Explorer.

Whenever I download an example from a site like Apple Developer Connection or Microsoft Developer Network, I always wonder why those example-writers don't learn.  They must have written hundreds of sample programs, but whoever writing those examples simply don't learn.  I am interested in accessing HID devices.  The example must be focused only on the HID-related functions.  I don't need dialogs, menus, and icons.  In fact, dialogs, menus, icons, and other craps must not be there.  What I need is a console program, that I can compile and run from the terminal, and print the values from those devices in the console.  At least who wrote HID_Explorer didn't understand what the sample program should look like.  I spent a few hours to separate those clutters and essential parts to understand what's needed to read joystick values.  I wrote a simple and essential sample code, which you can download from below.  My sample should be 100 times more useful than HID_Explorer, unless you are interested in learning how to make menus, dialogs, and icons.

The new way of reading HID is better than the old-fashioned way in that it doesn't require a COM interface.  In my impression, many of the COM interface don't have to be that complex.  They can simply be a C++ class library.  Anything that can be simple must be kept simple.  Anyways, most importantly, I think now I am able to do what I wanted to do with a joystick.

Below is a console program that shows mouse and joystick status every one second.  HID example should look like this.  I wonder when those example writers learn to write this simple program for us.

JoystickSample.cpp

I also have updated "Joystick-reading function and Joystick calibrator".  Now you can use it from Mac OS 10.6.

2010/01/07 Playing looping WAV file with NSSound without gap

I was trying to fix another problem in YSFLIGHT for Mac OS X regarding sound.  Engine sound is just playing a WAV-format sound in a loop, but I was hearing some blank or gap when the sound reaches to the end position and comes back to the start position.  Google search has pointed me to some web sites including:

http://minikidsgames.sourceforge.net/mini-game-tutorial/let-it-sound/

http://www.idevapps.com/forum/archive/index.php/t-3223.html

which implies that the gap is a specification of NSSound.  Well, I say 'specification' to be nice to Mac.  But, honestly, it is a flaw of NSSound.  I wish Apple will notice this flaw and fix it.  But, for now, I need to do something special.

It is so uncomfortable to hear a gap every a few seconds.  I was looking for a workaround and came up with one.  Basically, if I do:

[NSSound setLoops:YES];
[NSSound play];

the sound will loop.  But, after it reaches the end position, it stops playing then move back to the start position.  Instead, how about force it come back to the start position slightly before the sound reaches the end?  YSFLIGHT for Mac OS X should be able to achieve at least 20 frames per sec easily, so I have chance to check the playing position 20 times per sec, or once every 0.05 seconds at least.  I tried the following code to test this idea:

// Filename: nssound.m
#include <stdio.h>
#include <time.h>
#import <Cocoa/Cocoa.h>

int main(int ac,char *av[])
{
    NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];

    if(ac<2)
    {
        printf("Usage nssound filename.wav\n");
        exit(1);
    }

    NSSound *sound=nil;
    sound=[[NSSound alloc] initWithContentsOfFile:[NSString stringWithUTF8String:av[1]] byReference:NO];

    [sound setLoops:YES];
    [sound play];

    printf("Duration %lf\n",[sound duration]);

    time_t curTime,time0;
    curTime=time(NULL);
    time0=curTime;
    while(1)
    {
        [NSThread sleepForTimeInterval:0.01];

        if(0.05>[sound duration]-[sound currentTime])
        {
            [sound setCurrentTime:0.0];
        }

        if(curTime!=time(NULL))
        {
            printf("* %d\n",(int)(time(NULL)-time0));
            curTime=time(NULL);
        }
    }

    [pool release];

    return 0;
}

This program can be compiled from the terminal as:

g++ nssound.m -framework Cocoa -o nssound

And, can be executed as:

./nssound wavefile.wav

As highlighted, this program checks if the remaining time of the sound becomes smaller than 0.05 seconds, it forces the sound to move back to the start position.  It is not the perfect way of playing a sound in a loop.  I cannot say it is seamless.  Nonetheless, it is much better than hearing a gap.

2010/01/04 How to detect Alt-, Command-, Shift-, and Control-key strokes.

One of the problems I have been facing in porting YSFLIGHT to Mac was apparent lack of way to detect Alt-, Command-, Shift-, and Control-key (called modifier keys) strokes.  At least I was not able to find a way until today.  Cocoa API assigns special key codes (>0xf700) for some keys like ESC key, function keys, arrow keys, etc.  Therefore, if such a key code is sent by keyDown event, I can detect a key stroke.

However, no such special key codes are assigned to the modifier keys.  That was why I thought there was no way of detecting strokes of modifier keys that have no assigned key code.  In YSFLIGHT, I particularly wanted to detect Control-key stroke because Control-key opens in-flight configuration change menu by default.  But, if there were no way of detecting a Control-key stroke, I would have to assign a different key in YSFLIGHT for Mac OS, or ask the user to change key assignment if the user wanted to use in-flight configuration change feature.

But, it is not cool, and I was therefore thinking about a work around.  Every time an event is sent to the view, the event handler receives an event details in the parameter NSEvent *theEvent, which has a member variable called modifierFlags.  This modifierFlags stores status of all those modifies keys.  I thought I should be able to detect modifier-key strokes by setting up a timer and constantly monitoring modifierFlags via timer event.

To implement the work around, I was reading more details about NSEvent class, when I noticed an event type NSFlagsChanged.  The implication was NSView class must have flagsChanged function that receives NSFlagsChanged event.  "Isn't it the exact function that I was looking for?"  I then searched for the keyword flagsChanged, and found the following pages.

Technical Q&A QA1519: Detecting the Caps Lock Key
Mac Dev Center: Cocoa Event-Handling Guide: Handling Key Events

Bingo!  This flagsChanged was the exact function I was looking for.  I walked to the Wean-Hall Macintosh cluster that opened today and confirmed that I could detect modifier-key strokes with this flagsChanged function.

One thing that needs to be kept in mind is this flagsChanged function does not tell which key was pressed or released to raise this event.  Therefore, the program needs to remember previous modifier-key status to see which key was pressed or released.  Just one unsigned integer is sufficient to remember modifier-key status.

I have already updated "Mac OS X Programming Framework for Minimalists," and its FsInkey and FsGetKeyState functions can detect those modifier keys.

2009/12/25 Mac OS X Programming Framework for Minimalists

I have uploaded "Mac OS X Programming Framework for Minimalists".  This framework has been tested by Sword Edge, Planet Attacker 2009, and YSFLIGHT for Mac OS X (now in test phase.)  I believe this framework is reasonably stable by now.

An ideal programming environment allows a programmer to achieve a goal with simple programming for simple purposes and reasonably complex programming for complex purposes.  However, recent trend is more like requiring complex programming no matter how simple the purpose is.  But, with this framework, you can write a simple programs as easy as BASIC language used to be.  I had to make some modifications to use it for YSFLIGHT, but not big change from the one you can download from the above link.  All the source code is available.  You can make modifications to the framework if necessary.

2009/10/17 Directory listing and UNICODE file name issues

In YSFLIGHT, aircraft, ground-object, and scenery definitions are stored in files, air*.lst, gro*.lst, and sce*.lst, respectively.  Therefore, I need to write a function that retrieves those files stored in the appropriate directory to port YSFLIGHT to Mac OS X.  A brief search in Apple Developer Connection led me to the class called NSFileManager, and its member function contentsOfDirectoryAtPath will give a list of files in the specified directory.  I tried the following code.

#include <Cocoa/Cocoa.h>

int main(void)
{
    NSAutoreleasePool *pool;
    pool=[[NSAutoreleasePool alloc] init];

    NSFileManager *fileMngr;
    fileMngr=[[NSFileManager alloc] init];

    NSArray *fileList;
    fileList=[fileMngr contentsOfDirectoryAtPath:@"." error:NULL];

    printf("%d files are found.\n",[fileList count]);

    int i,j;
    for(i=0; i<[fileList count]; i++)
    {
        NSString *str;
        char utf8[256];
        str=[fileList objectAtIndex:i];
        strncpy(utf8,[str UTF8String],255);
        utf8[255]=0;
        printf("[%d] %s\n",i,utf8);
        for(j=0; utf8[j]!=0; j++)
        {
            printf("%02x ",(unsigned char)utf8[j]);
        }
        printf("\n");
    }

    [pool release];

    return 0;
}

This code can be compiled by typing:

g++ filelist.m -framework Cocoa

This code worked with one attempt.  I guess I am getting used to Objective-C.  But, my goal is isolate this Objective-C code and write a function that can be accessed from C++ though.

I have added the following 3 lines:

        for(j=0; utf8[j]!=0; j++)
        {
            printf("%02x ",(unsigned char)utf8[j]);
        }

to see how a UNICODE character is translated into UTF8 characters.  In fact, one of the obstacles in porting YSFLIGHT to Mac OS X was UNICODE.  In Mac OS X, file names are stored in UNICODE by default.  If the user chooses to open a file that has a non-ASCII character, say a Kanaji character, in its file name, the file name may not be translated into a UTF8 character, and I was worried that I cannot open such a file by fopen function.  In that case, I would need to re-write file I/O functions almost entirely.

Since iMacs in Wean-Hall Mac cluster did not have (or at least I didn't know how to use) IME, or Input Method Editor, I opened a Japanese web site, and cut & pasted some kanji characters in Finder to make a file that has some Kanji characters in the file name.  And, I ran the above code again.

I was not expecting to see Kanji characters.  I was expecting to see some weird characters which those Kanji characters were translated into.  To my surprise, I saw Kanji characters in my UNIX-console output.  This is what I saw:

Apparently, utf8 function of NSString class translates a UNICODE character into three bytes in a utf8 string.  The next question would be, can I open a file with a non-ASCII character in the file name?  I tried the following code:

#include <stdio.h>

unsigned char nihongo[]=
{
    0xe6,0x97,0xa5,0xe6,0x9c,0xac,0xe8,0xaa,0x9e,0
};

int main(void)
{
    FILE *fp;
    fp=fopen((char *)nihongo,"w");
    fclose(fp);
    return 0;
}

In fact, this code worked.  This simple code created a file, "".

The conclusion is that utf8 function of NSString can safely translate a UNICODE string into a non-UNICODE string, which can be given to fopen function.  One more step closer to YSFLIGHT for Mac OS X.

2009/09/21 Setting Up Application Icon

You know what?  I was able to port Sword Edge, a retro-looking game that I made when I was an undergrad, to MacOS X.  I will upload the game soon.

Porting the program was not difficult.  I knew everything I needed by now.  However, it took some time to figure out how to set up an application icon.  Again, I wanted to avoid IDE as much as possible.  Apple Developer Connection implied that first I need to use a program called Icon Composer, and then save the icon file in Contents/Resources sub-directory of the application directory, and link it from the file Info.plist located in Contents sub-directory of the application directory.

I followed that instruction.  I created PNG pictures of the icon, and dragged it to Icon Composer, and saved it as SwordEdge.icns.  Then, I copied it to the Contents/Resources sub-directory.  And, I made a minimum Info.plist as follows,

(Failed Attempt)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleIconFile</key>
	<string>SwordEdge.icns</string>
</dict>
</plist>

and copied to the Contents sub-directory.  I was expecting my icon showing up in Finder, but it didn't.

I thought my Info.plist was wrong.  So, next thing I tried was making an empty project in Xcode, and copied Info.plist from the empty project, and added CFBundleIconFile so that it points to my icon.  It didn't work.  Then I suspected the file name.  It may need to be APPL.icns because the example in Apple Developer Connection was naming it this way.  I changed the file name of my icon file to APPL.icns, and changed my Info.plist accordingly.  My icon still didn't show up.  This time I followed the instruction in Apple Developer Connection entirely.  I wanted to see if it really works.  It did work.  What was the difference?  Again, Xcode seemed to be doing a magic behind the scenes.

I had to leave Wea‚Ž-hall Macintosh cluster at that time.  I was thinking to start from the program I created with Xcode.  I would delete files, directories, and Info.plist entries one by one until my icon disappears, then I should be able to figure out minimum requirements to set up an application icon.

So, I came back to Macintosh cluster next day.  I opened my source directory.  And, I saw my icon is shonw for the SwordEdge application.  Not the one I created with Xcode IDE, but the one I created from scratch without Xcode IDE.  What happened?  I deleted Resources sub-directory from SwordEdge application directory.  As expected, the icon changed back to the default application icon.  I made Resources sub-directory, copied APPL.icns.  I waited a few seconds, and my icon replaced the default application icon.  I changed the file name to SwordEdge.icns, and changed the Info.plist accordingly.  My icon disappeared.  I changed the file name of SwordEdge.icns to APPL.icns.  My icon came back.  Did I really need this Info.plist?  I renamed Info.plist to Infoplist.  My icon stayed there.  I am not still 100% sure what was the right way.  But, the file name was important, apparently.

So, I made a copy of SwordEdge.app to a different directory.  I copied readme.txt to the same directory to make an upload archive.  To my disbelief, my icon was gone.  Well, the situation was, I didn't see my icon yesterday.  And, I did see an icon today, although I made no change.  Now I copied my application folder to a different location, and the icon disappeared.  I guessed that Finder is caching the icon image somewhere, and it is not refreshed unless I log off and log on again.  I logged off, and logged on again, and I saw my icon there.  So, I made an upload package, and it is on the way.

I also uploaded OpenGL font-drawing function that can be used cross platform.  I have tested the functions in MacOS X and confirmed it worked.  But, one thing I found was that NSOpenGLView's OpenGL context is not made current immediately when the view is created.  Some test cases implied that NSOpenGLView assumes that the initialization of OpenGL is done in prepareOpenGL function of NSOpenGLView.  If I want to do some initialization, I need to override this function and write initialization code within.

That is something I need to keep in mind.  The prepareOpenGL function is apparently called sometime after the creation of NSOpenGLView with delay.  When I tried to initialize OpenGL immediately after creating NSOpenGLView, it didn't work.

2009/09/17

I'm getting better understanding of MacOS X programming.  Still I have several points I don't understand though.  But, overall, the architecture seems to be good.  I can easily write a plain program and compile and get an OpenGL-ready window.  If I try to do the same thing in MFC, aagh, what a mess!  MFC relies too much on the resource file, which destroys portability.  I'm not talking about inter-platform portability.  If I write a dialog class, and if I want to copy it to my different program, I need to copy source files, then I need to copy resources.  I cannot make it an independent library.  COM?  I don't think Microsoft's domination lasts forever.  I don't want to go too much deep into Windows.  So, basically, I like MacOS X's API structure.  If only it were written in C++.

I'm also getting better sense of Objective-C, and I see its flexibility.  In C++, most of the smart things are done by the compiler.  It means that most of the things need to be known at compile time.  If you use what's called implicit template, the linker needs to be smarter.  But, what class has what methods needs to be all defined before the program is executed.  In other words, all the methods (or member functions) of a class needs to be defined before the method is used in C++.  However, in Objective-C, as far as I understand, I can use a method of a class without defining it.  Of course, I need to write it somewhere, but, the method will be fetched at run-time.

Probably, this flexibility comes with substantial sacrifice in the performance.  Performance-wise, it is no match for C++'s virtual-function mechanism.  In my experience, C++ is flexible enough.  Objective-C is overkill.  Maybe Objective-C is more object-oriented than C++, but C++ is more practical.  To me, Apple has always been more like a group of artists than a group of engineers.  They value cool appearance than practicality.  Probably, Objective-C appeared more artistic than C++ to them, at least when they designed NeXT STEP.  What a missed opportunity.  They had more than 10 years since NeXT STEP fell.  If they had known this day was coming, they had more than enough time to re-write NeXT STEP in C++.

Anyways, I successfully wrote MacOS X wrapper for my course.  I have a feeling that I can port YSFLIGHT to MacOS X.  So, my interest has shifted to learn more about Cocoa APIs.  I had some homework to do.  One of them was to figure out how to take key strokes if I compile my program from Unix terminal.  I guessed it was because my program compiled from Unix terminal didn't have some features of a typical MacOS X program, such as main menu.  So, I tried to create my own main menu and attach it to the NSApp.  But, it didn't work.

After hours of search, I came across the following post.

http://forums.macnn.com/79/developer-center/209595/cocoa-without-nib-file-need-help/

It suggested that MacOS X program needed to be placed under the directory:

Something.app/Contents/MacOS

So, let's say my program was called YsProgram, then the program file should be:

YsProgram.app/Contents/MacOS/YsProgram

Seriously?  I doubted.  But, I tried.  And, it worked.  In fact, it also solved another homework: running terminal-compiled program from Finder.  Just by placing my program under the above directory structure, I was able to run it from Finder.  Now I am absolutely free from Xcode IDE.  I can compile from Unix terminal by a single command.  Since I don't need Xcode IDE, I don't get default main window.  It's a big progress.

Another issue was when to set up default NSNotificationCenter so that my window class can get various events.  Since I am creating window in my own function, I can set it up after window creation is done.  But, it seems that the right thing is overriding initWithContentRect function and then set up NSNotificationCenter within the overrided initWithContentRect function.  I made my YsOpenGLWindow class as follows:

@interface YsOpenGLWindow : NSWindow
{
}

@end

@implementation YsOpenGLWindow
- (void) keyDown:(NSEvent *)theEvent
{
    NSString *chrs;
    chrs=[theEvent characters];
    if([chrs length]>0)
    {
        int unicode;
        unicode=[chrs characterAtIndex:0];
        printf("keyDown %d\n",unicode);
    }
}

- (void) keyUp:(NSEvent *)theEvent
{
    NSString *chrs;
    chrs=[theEvent characters];
    if([chrs length]>0)
    {
        int unicode;
        unicode=[chrs characterAtIndex:0];
        printf("keyUp %d\n",unicode);
    }
}

- (void) mouseDown:(NSEvent *)theEvent
{
    printf("%s %d %d\n",__FUNCTION__,(int)[theEvent locationInWindow].x,(int)[theEvent locationInWindow].y);
}

- (void) mouseUp:(NSEvent *)theEvent
{
    printf("%s %d %d\n",__FUNCTION__,(int)[theEvent locationInWindow].x,(int)[theEvent locationInWindow].y);
}

- (void) rightMouseDown:(NSEvent *)theEvent
{
    printf("%s %d %d\n",__FUNCTION__,(int)[theEvent locationInWindow].x,(int)[theEvent locationInWindow].y);
}

- (void) rightMouseUp:(NSEvent *)theEvent
{
    printf("%s %d %d\n",__FUNCTION__,(int)[theEvent locationInWindow].x,(int)[theEvent locationInWindow].y);
}

- (void) otherMouseDown:(NSEvent *)theEvent
{
    printf("%s %d %d\n",__FUNCTION__,(int)[theEvent locationInWindow].x,(int)[theEvent locationInWindow].y);
}

- (void) otherMouseUp:(NSEvent *)theEvent
{
    printf("%s %d %d\n",__FUNCTION__,(int)[theEvent locationInWindow].x,(int)[theEvent locationInWindow].y);
}

- (void) mouseMoved:(NSEvent *)theEvent
{
    printf("%s %d %d\n",__FUNCTION__,(int)[theEvent locationInWindow].x,(int)[theEvent locationInWindow].y);
}

- (id) initWithContentRect: (NSRect)rect styleMask:(NSUInteger)wndStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferFlg
{
    [super initWithContentRect:rect styleMask:wndStyle backing:bufferingType defer:deferFlg];

    [[NSNotificationCenter defaultCenter] 
        addObserver:self
        selector:@selector(windowDidResize:)
        name:NSWindowDidResizeNotification
        object:self];

    [[NSNotificationCenter defaultCenter]
      addObserver:self
      selector:@selector(windowWillClose:)
      name:NSWindowWillCloseNotification
      object:self];

    [self setAcceptsMouseMovedEvents:YES];

    printf("%s\n",__FUNCTION__);
    return self;
}

- (void) windowDidResize: (NSNotification *)notification
{
    printf("%s\n",__FUNCTION__);
}

- (void) windowWillClose: (NSNotification *)notification
{
    printf("%s\n",__FUNCTION__);
    [NSApp terminate:nil];    // This can also be exit(0);
}

@end

I have also added functions to intercept key and mouse events.  You can download the full source from the following.

[Current Test Code]  <= Mac OS X Cocoa program, animation without using timer event.

Actually, even though I used Application Delegate to automatically terminate the application when all windows are closed, my program didn't terminate once I open my own window.  But, now I can capture NSWindowWillCloseNotification, I can simply terminate the program in the call-back function for the notification.

You can compile this single-source program from Unix terminal.  Simply compile as:

gcc minimum-04-KeyMouse.m -framework Cocoa -framework OpenGL

Then create a directory minimum.app/Contents/MacOS and copy a.out there.  You can run the program from Unix terminal or Finder.  For testing, it should be better to run from Unix terminal because you can see console output.

2009/09/09

I spent about an hour to work on the graphics-API wrapper for Mac before my class.  It's almost done.

By the way, I found one thing very useful.  In fact, I found it earlier, but I don't think I wrote this thing here yet.  If I create a project and compile the program from Xcode, it doesn't come with Unix terminal.  So, I cannot see console output.  But, if I ran the same program (compiled in Xcode) from Unix terminal, I see all the console output.  Let's say my project is in /User/soji/MyProgram.  Then, the program will be stored in:

/User/soji/MyProgram/build/Debug/MyProgram.app/Contents/MacOS

The program file name probably is same as the project name.  If I go down to this directory in Unix terminal and type:

./MyProgram

I can run the program and can see all console output.  Console output is often more useful than the debugger.  In fact, if the complexity of my program gets big, console output is almost always better than the debugger.  The debugger can give you only a snapshot of the program status.  But, by effectively choosing what to write to the console, you can see the log of the status of your program.  I usually use the debugger to pin-point where the program is crashing, but later on, I rely more on the console output to debug.

Back to MacOS programming.  My remaining problems were:

  1. The program does not terminate when all all the windows are closed.
  2. The program opens a default main window that is described in the resource file in addition to the one opened by my function.
  3. If I compile it from Unix terminal, it does not respond to key stroke, and also cannot run from Finder.  (Finder gives an message "You can't open the application "a" because the Classic environment is no longer supported.")

For the first problem, I knew I needed to attach an object called "Application Delegate" and tell the application that it should terminate when all windows are closed.  I tried the following last time.

(I wrote the following code based on an example, but I got an error.)

@interface YsAppDelegate : NSObject <NSApplicationDelegate>

- (BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApp

@end

@implementation YsAppDelegate

- (BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApp
{
	return YES;
}
@end

But, I had an error saying NSApplicationDelegate is not defined.  If I remove <NSApplicationDelegate>, I was able to compile, but the application did not close when I closed all the windows.

After looking at some sample codes, it looked like I was not supposed to write the function within @interface block.  This time I tried the following code.

(The following code worked!)

#import <Cocoa/Cocoa.h>
@interface YsAppDelegate : NSObject

@end

@implementation YsAppDelegate

- (BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApp
{
	return YES;
}
@end

int main(int argc, char *argv[])
{
	[NSApplication sharedApplication];
	[NSBundle loadNibNamed:@"MainMenu" owner:NSApp];

	YsAppDelegate *delegate;
	delegate=[YsAppDelegate alloc];
	[delegate init];
	[NSApp setDelegate: delegate];

	[NSApp run];
}

I left @interface block of YsAppDelegate empty.  But, I wrote applicationShouldTerminateAfterLastWindowClosed function in @implementation block.  I compiled and ran from Xcode.  I closed the main window, and the program terminated!  It worked!

However, the program did not terminate if I added my own window.  I closed both the default main window and my own window, but the program didn't terminate.  I was getting closer, but something seemed to be still missing.

Because I was able to make an application delegate, I also tried to add a window delegate.  Apparently, a window delegate is needed for dealing with some window events such as resize notification.  Later my understanding turned out wrong.  Anyways, I tried the following code.

(The following code didn't work.)

#import <Cocoa/Cocoa.h>

@interface YsAppDelegate : NSObject

@end

@implementation YsAppDelegate

- (BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApp
{
	printf("%s\n",__FUNCTION__);
	return YES;
}

@end

@interface YsWndDelegate : NSObject

@end

@implementation YsWndDelegate

- (void)windowDidResize:(NSNotification *)notification
{
	printf("*\n");
}

@end


int main(int argc, char *argv[])
{
	[NSApplication sharedApplication];
	[NSBundle loadNibNamed:@"MainMenu" owner:NSApp];

	YsAppDelegate *delegate;
	delegate=[YsAppDelegate alloc];
	[delegate init];
	[NSApp setDelegate: delegate];

	YsWndDelegate *wndDelegate;
	wndDelegate=[YsWndDelegate alloc];
	[wndDelegate init];

	NSWindow *mainWnd;
	mainWnd=[NSApp mainWindow];
	[mainWnd setDelegate: wndDelegate];

	printf("%08x\n",mainWnd);

	[NSApp run];
}

If I compiled the program in Xcode and ran it from Unix terminal, I was supposed to see "*" in the terminal every time I resized the main window.  But, this didn't work.  In fact, before [NSApp run], main window pointer [NSApp mainWindow] is not set.  So, I suppose the main window is actually created after the event loop is started.  The situation seemed to be a typical chicken and egg.  I want to set a window delegate when the window is created, but to seize the moment of window creation, I thought I needed to set a window delegate.  (I was guessing I needed to do it during window-creation event.)  There must be a different way.

In fact, NSWindow class reference of Apple Developer Connection lists whole varieties of notifications including NSWindowDidResize.  But, I didn't see any function that receives a notification.  How come?  Here I just made a guess and searched by keyword "Notification programming", and it was a good guess!  I came to this page.  In summary, I can make my own callback function for a specific notification, and I should tie the function by addObserver function of NSNotificationCenter class.  The question was where.  The right place seemed to be initWithContentRect function of NSWindow class.  But, the time was up for the day.  I'll try some more next time.

2009/09/07

I was supposed to be in Cleveland Air Show.  This time, the weather was even below IFR minimum.  I cancelled the trip.  I was able to go to only two air shows this year.  Toward the afternoon, the weather seemed to recover a bit.  It looked like moderate IMC.  I tried to fly an IFR training to earn some actual IMC time, but when I came to the airport, the weather was almost at the minimum for ILS.  And, the glide slope for runway 10, which was in use then, was out of service.  It made higher minimum.  So, I gave up.  My labor-day weekend was over.

So, I came back to CMU and played with Cocoa a little more.  First I made a function YsPollEvent(void).

static void YsPollEvent(void)
{
    NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];

    while(1)
    {
        [pool release];
        pool=[[NSAutoreleasePool alloc] init];

        NSEvent *event;
        event=[NSApp
            nextEventMatchingMask:NSAnyEventMask
            untilDate: [NSDate distantPast]
            inMode: NSDefaultRunLoopMode
            dequeue:YES];
        if(event!=nil)
        {
            [NSApp sendEvent:event];
            [NSApp updateWindows];
        }
        else
        {
            break;
        }
    }

    [pool release];
}

This function loops until the event queue is empty.  With this function, I can now write the main function as:

int main(int argc, char *argv[])
{
    [NSApplication sharedApplication];
    [NSBundle loadNibNamed:@"MainMenu" owner:NSApp];

    [NSApp finishLaunching];

    while(1)
    {
        YsPollEvent();
    }
}

I can run continuous process, like animation, in this while loop.  Next, I tried to open a window not from a NIB file.  I wrote as follows.

@interface YsOpenGLWindow : NSWindow
{
}
- (void) keyDown:(NSEvent *)theEvent;
@end

@implementation YsOpenGLWindow
- (void) keyDown:(NSEvent *)theEvent
{
    exit(1);
}

@end

@interface YsOpenGLView : NSOpenGLView 
{
}
- (void) drawRect: (NSRect) bounds;
@end

@implementation YsOpenGLView
-(void) drawRect: (NSRect) bounds
{
     static float top=0.0;

    glClearColor(0,0,0,0);
    glClear(GL_COLOR_BUFFER_BIT);

    glColor3ub(0,255,0);

    glBegin(GL_POLYGON);
    glVertex2f(top,0.1);
    glVertex2f(0.3,0.8);
    glVertex2f(0.7,0.8);
    glEnd();

    top+=0.1;
    if(top>1.0)
    {
        top=0.0;
    }
    glFlush();

    [[self openGLContext] flushBuffer];
}
@end

static YsOpenGLWindow *ysWnd=nil;
static YsOpenGLView *ysView=nil;

static void YsOpenWindow(void)
{
    NSRect contRect;
    contRect=NSMakeRect(32, 32, 800, 600);  // Top-Y coordinate is neglected.  Why?

    unsigned int winStyle=
        NSTitledWindowMask|
        NSClosableWindowMask|
        NSMiniaturizableWindowMask|
        NSResizableWindowMask;

    ysWnd=[YsOpenGLWindow alloc];
    [ysWnd
        initWithContentRect:contRect
        styleMask:winStyle
        backing:NSBackingStoreBuffered 
        defer:NO];

    NSOpenGLPixelFormat *format;
    NSOpenGLPixelFormatAttribute formatAttrib[]=
    {
        NSOpenGLPFAWindow,
        NSOpenGLPFADepthSize,(NSOpenGLPixelFormatAttribute)32,
        NSOpenGLPFADoubleBuffer,
        0
    };
    format=[NSOpenGLPixelFormat alloc];
    [format initWithAttributes: formatAttrib];    

    ysView=[YsOpenGLView alloc];
    contRect=NSMakeRect(0,0,800,600);
    [ysView
        initWithFrame:contRect
        pixelFormat:format];    

    [ysWnd setContentView:ysView];
    [ysWnd makeFirstResponder:ysView];    

    [ysWnd makeKeyAndOrderFront:nil];
    [ysWnd makeMainWindow];
}

This YsOpenWindow function worked!  For some reason, the window left-top corner didn't appear at (32,32).  I need to look into it.  Nonetheless, now I am able to open my own window without relying on a .NIB file.

I tried one more thing today.  I wanted to make it so that the program terminates when all the windows are closed.  For that purpose, I need to make a class called 'delegate' and set it in NSApp.  Referring to the sample "Fire", I made my own delegate as follows.

@interface YsMacDelegate : NSObject <NSApplicationDelegate>
{
}
- (BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication;
@end

@implementation YsMacDelegate
- (BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
{
    return YES;
}
@end

However, the compiler gave me an error saying NSApplicationDelegate is not defined.  I also tried to compile the Fire example, but I had a same error.  I couldn't figure out what to do for this part today.

You can download the source file I tried today here.  You can compile it by creating a Cocoa framework application in Xcode and replace main.m created by the template.  Or, you can also compile it from the command line as:

gcc main.m -framework Cocoa -framework OpenGL

If you compile from the command mode, you can execute the program from the terminal, but you cannot execute from Finder.

So, now, my homeworks are:

  1. How to define a delegate?
  2. Why left-top corner of my window doesn't appear as I specify?
  3. Why I cannot run the program if I compile from command line?

2009/09/05

I was supposed to be in Cleveland Air Show.  But, due to some things, I was not able to go today.  I will go on Monday instead.  I instead spent about an hour chasing Hummingbirds in Shenley park and then came back to CMU to play with Cocoa API.

Actually, there was a big break.  As I searched on Google, I came to the following post.

http://cocoawithlove.com/2009/01/demystifying-nsapplication-by.html

This post describes how to write my own event loop.  I don't have to give it up to the framework. 

So, I wanted to see if it really works.  I created a plain Cocoa application in Xcode and modified the main function as follows.

#import <Cocoa/Cocoa.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    //return NSApplicationMain(argc,  (const char **) argv);

    NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];

    FILE *fp;
    fp=fopen("/Users/soji/MyAFS/src/MacOS/test.txt","w");
    fprintf(fp,"!\n");
    fclose(fp);

    [NSApplication sharedApplication];
    [NSBundle loadNibNamed:@"MainMenu" owner:NSApp];
    // [NSApp run];

    [NSApp finishLaunching];
    while(1)
    {
        [pool release];
        pool=[[NSAutoreleasePool alloc] init];

        NSEvent *event;
        event=[NSApp
            nextEventMatchingMask:NSAnyEventMask
            untilDate: [NSDate distantPast]
            inMode: NSDefaultRunLoopMode
            dequeue:YES];
        [NSApp sendEvent:event];
        [NSApp updateWindows];
       
        static int x=0;
        fp=fopen("/Users/soji/MyAFS/src/MacOS/test.txt","w");
        fprintf(fp,"%d\n",x++);
        
fclose(fp);
    }
 
    [pool release];
    return 0;
}

From the list shown in the above post, I modified so that I could put it inside main function and changed the NIB file name.  Also I changed [NSDate distantFuture] to [NSDate distantPast] so that the loop runs even when no event is in the queue.

Although I have no idea what NSAutoreleasePool *pool is doing, it worked.

Then, I came back to my OpenGL project, and modified the drawRect function of my view class as follows:

-(void) drawRect: (NSRect) bounds
{
    static float top=0.0;
           
    glClearColor(0,0,0,0);
    glClear(GL_COLOR_BUFFER_BIT);
 
    glColor3ub(0,255,0);
           
    glBegin(GL_POLYGON);
    glVertex2f(top,0.1);
    glVertex2f(0.3,0.8);
    glVertex2f(0.7,0.8);
    glEnd();
           
    top+=0.1;
    if(top>1.0)
    {
        top=0.0;
    }
    glFlush();
}

so that one of the corners of the triangle moves every time it is drawn.  I modified the main function as follows:

int main(int argc, char *argv[])
{
//    return NSApplicationMain(argc,  (const char **) argv);
 
    NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
           
    FILE *fp;
    fp=fopen("/Users/soji/MyAFS/src/MacOS/test.txt","w");
    fprintf(fp,"!\n");
    fclose(fp);
           
    [NSApplication sharedApplication];
    [NSBundle loadNibNamed:@"MainMenu" owner:NSApp];
    // [NSApp run];
           
    [NSApp finishLaunching];

    while(1)
    {
        [pool release];
        pool=[[NSAutoreleasePool alloc] init];
                       
        NSEvent *event;
        event=[NSApp
            nextEventMatchingMask:NSAnyEventMask
            untilDate: [NSDate distantPast]
            inMode: NSDefaultRunLoopMode
            dequeue:YES];
        [NSApp sendEvent:event];
        [NSApp updateWindows];
 
        [[NSApp mainWindow] display];
                       
        static int x=0;
        fp=fopen("/Users/soji/MyAFS/src/MacOS/test.txt","w");
        fprintf(fp,"%d\n",x++);
       
fclose(fp);
    }
           
    [pool release];
    return 0;
}
 

so that it redraws the window in the event loop. 

With this code, I was able to do an animation without using the timer event. 

Another big break was that I found the following post. 

http://www.cocoalab.com/?q=node/11

In here, you can find how to compile a program that uses a gframeworkh.  You can type like: 

gcc mytest.m -framework cocoa

to compile a program that uses Cocoa framework from the command terminal.  If I figure out the way to open a window without relying on the NIB file, I should be able to get out of Xcode IDE.

And, gMac Dev Center: Window Programming Guideh should give me a way to open and close a window without NIB file.  I though I already searched with keyword gOpen Close Windowsh but maybe I didn't or I overlooked this page.

I'm planning to go step by step.  Next time, I'll try to open an extra window from within a Cocoa-template program.

2009/09/04

By now, I understood that main part of Mac OS API is written on a dead language, Objective-C.  And, there apparently is no easy way to create a simple 600x800 window with short code.  A big fat framework is needed just to open a simple window.  There should be a way to avoid this big fat framework, but it apparently is hidden deep inside somewhere.  But, the good news was I can easily make a call to and from a function within an Objective-C code by extern gCh expression in C++.

I had only short time today to waste time in front of Mac.  But, I tried a different thing: hijacking main function that is created by the framework.  So, I created a Cocoa framework application in Xcode and then changed the name of the main function to gsubmainh.  I added a new C++ source file and wrote my own main function.  In the C++ code, I wrote:

extern "C" int submain(int ac,char **av);

and called it from my main function.  I was able to compile and run.  I saw a window popped up.

Next try was to make it multi-threaded, and push Cocoa framework thing to my sub-thread.  So, I included pthread.h, and modified the submain function so that it can be called in a different thread.

I compiled and ran, but the program crashed.  Apparently, Cocoa needs to be in the primary thread.  It's still ok.  I can run my own function as a second thread.  At least now I know that I can take over my program-entry point.

What I'm thinking to try is drawing a moving object from my thread.  Hopefully I can spend some time next week to explore this option.

2009/09/01

I am teaching an introductory programming to Mechanical Engineering students this semester.  I am planning to start some 2D graphics next week.  Since it is an introductory course, I don't want to go into system-specific functions, like MFC or Win32 API functions.  I want to start with just like open a window, draw some primitives, wait for key stroke, and end the program.  For that purpose, I wrote a wrapper function for Windows that students can use.

But, at least one student wants to do assignments in his iMac.  In fact, I wanted to learn programming in Mac.  But, I couldn't justify spending time for learning it.

Last experience was long time back.  I was lucky enough to have a friend who was proficient in Mac OS programming then.  But, in my impression, programming in Mac OS was way too much more difficult than programming in Windows, and I would have given it up without his help.  That was then.  Mac OS is now version 10.  I thought  it should be improved by now and should be much more reasonable.

So, I walked in to Mac cluster of School of Computer Science in Wean Hall.  Logged on and opened a Unix terminal.  I typed just g++ and enter.  I made sure GCC was installed.  That was a good sign.  So, first thing I wanted was making a function that opens a simple window like a plain 800x600-pixel window.

I opened Safari, and opened Apple Developer Connection.  I had very little idea about Mac programming.  So, I just started learning what libraries to use.  According to the web site, there are two libraries called Cocoa and Carbon.  Why two?  I had no idea then.  But, apparently Apple was recommending Cocoa.  I just made a guess about the location of the header, and wrote a simple program as follows:

#include <stdio.h>
#include <Cocoa/Cocoa.h>

int main(void)
{
            return 0;
}

and saved it as test.cpp.

This program was going to do absolutely nothing, but I just wanted to make sure I can compile Cocoa program from Unix terminal.

I typed g++ test.cpp and go weird errors.  It says something like 'stray @' exists in one of the header files included from Cocoa.h.  So, I used Unix find command to locate Cocoa.h and other header files and opened with emacs.  I indeed saw strange expressions in there.

I came back to Apple Developer Connection and figured that Cocoa is for Objective-C, and Carbon is for C++.  Aha!  Cocoa must be for the legacy code.  Nobody uses Objective-C now.  That's a dead language.  Then, Carbon must be the standard API library.  But, as I carefully read through the Apple Developer Connection, I realized Cocoa is the standard API library, and Carbon was a sort of wrapper.  And, Carbon was not kept up to date with Cocoa.  They got to be joking....

I guess that's a natural reaction to the situation.  How can I imagine that brand new highly publicized Mac OS X is based upon a long-dead programming language?  I am somewhat a stubborn programmer and I don't change my programming style unless I feel it necessary.  But, apparently there seems to be even more stubborn programmers in Apple.

I have no interest in Apple's internal problems.  My interest was writing a function that opens a simple window.  After searching in Apple Developer Connection and Google for 30 minutes or so, I finally gave up.  Everything I was able to find implied that a window needs to be created from something called .NIB file, something similar to a resource (.RC) file of Windows.  I didn't find any example of opening a window of a specific dimension.  How can I make a picture viewer?  I need to open a window that fits a picture.  There must be a way.  But, I simply couldn't find such an example.

One reason was because it was written in Objective-C.  I can read C++ code a lot quicker than Objective-C code.  I had enough with Cocoa.  I tried to look at Carbon.  But, I found even less Carbon examples.  Also, all of the samples I looked at were using a function called CreateWindowFromNib.  Again, I needed to use NIB.  I never found a function to open a window with a specific dimension.  There must be a way.  But, it was hidden somewhere.

I gave up writing a program from scratch, and tried to use Xcode IDE.  I don't like to use a pre-defined template given by the IDE because it hides a lot of things behind.  For the same reason, I am not a fan of MFC.  I use MFC when necessary, but I avoid as much as I can.  But, to start Mac programming, I found no good short code that I could start with.

I tried the following example:

http://developer.apple.com/mac/library/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_intro/opengl_intro.html

And, it gave me an OpenGL-enabled window.  It was a one small step of progress.  Now I wanted to modify the code so that I can make a wrapper function that allows students to write something like:

#include <stdio.h>
#include ggraphics.hh

int main(void)
{
    OpenWindow(800,600);
    SetColor(255,255,255);
    DrawLine(0,0,799,599);
    while(Inkey()==0)
    {
        PollEvents();
    }
    return 0;
}

Then I hit a wall again.  In main function, there's a function called NSApplicaionMain, and it takes the event loop and doesn't give it to me.  I thought there should be a way to take it back to me.  But, my search didn't give an answer.  Apple wants me to use a timer event to repeat something.

Event driven is an important concept.  But, there are steps to reach there for beginners.  Those who are learning a programming should start from basic looping, then they can learn more complex program structure like event handling.

When people realized the power of object-oriented programming, some people came up with an idea that everything must be done by events, which I utterly disagree.  In my opinion, simple thing should be done in a simple way.  Just opening a window and drawing some primitives should be done by a plain simple program.  Forcing a programming style is an arrogance of the API designer. 

I thought Cocoa and Carbon didn't work.  I tried a different way.  Apple Developer Connections says X-Window and GLX wrapper should be available.  X-Window is also an obsolete system, but is more flexible and not as obtrusive as Cocoa since it doesn't force a programming style.

So, I copied my simple GLX code and tried to compile.  Apple has a bad habit of doing things differently from standard.  That's why they are always minor.  I figured OpenGL header, which was supposed to be in GL sub-directory, was for some reason located under OpenGL sub-directory.  But, GLX header, glx.h was nowhere to be found.  So, it apparently didn't support GLX.

I spent a few hours.  But, I even was not able to open a 800x600 window.