Mac Development

May 21, 2008

Creating Cocoa applications programatically (i.e. NIB-less)

"Why on earth would you want to create a Cocoa application without using Interface builder?"

That's the basic reaction you get if you try to seek out help online in writing a Cocoa application from scratch.

There may not be a lot of valid reasons but if there is at least one reasonable answer, then the question should be moot.

Our applications at DVFilm are cross-platform.  The user interfaces are very simple.  The majority of the code is written in C/C++ and in the business logic.  So I have not given up on the idea that the simple form-based UI can be written in such a way that the majority can be shared between Windows and Mac. 

There, that's my "reasonable" explanation on why I am crazy enough to consider creating a Cocoa application programmatically.  If that doesn't satisfy, here is another reason.

I have inherited a Carbon application (a QT template app, actually) and simply want to add a single dialog using existing Cocoa code. 

If neither answer satisfies, I DON'T CARE.  I do appreciate all the arguments for stopping my Tilting at Windmills but THEY don't satisfy ME.  If you can't bring yourself to suspend your disbelief and offer help, I'll figure it out on my own. 

It's not like it's NOT SUPPORTED.  And apparently, creating applications in Cocoa programmatically might very be well be the norm for iPhone development.

Here is someone with the same goal.

April 09, 2008

Notifications in Cocoa

Notifications seem unnecessarily confusing the way they are documented.

I want to know when my my window resizes so I notice in the documentation for NSWindow that there is a NSWindowDidResizeNotification.  Great seems that's what I want to write a handler for.

What do I do?  The documentation does not describe what the handler function signature should be but I locate some sample code that uses:

- (void)windowDidResize:(NSNotification*)theNotification

so I add that to my NSWindow derived class for my main window but the method is not called.

I read up on Notifications and they talk about registering for notifications you are interested in but that seems unnecessary for this particular event, especially since I've subclassed the window.  What is it in my background that expects that by simply subclassing the window that, that would be sufficient to capture the resize event?  Is it my Windows background?

Anyway, I added

    [self setDelegate:self]; // Set as delegate in order to receive notifications

in my MainWIndow awakeFromNib handler and my resize handler started getting called.

In another application I had to set the delegate again when adding the resize handler to the NSHandler object for a Window wasn't sufficient.

"Delegates and Data Sources" in the Cocoa documentation sheds some light on this.

March 20, 2008

Porting C++ code to Objective-C

I'm in the middle of porting a Windows C++ application to the mac.  I've posted before that I was pleasantly pleased to find that with some limitations, XCode supports mixing C++ and objective-C code.

Here are a few things to look out for when working with C++ code in a Cocoa application:

1. Missing C++ member functions may not be flagged at compile time and your program will abort with a message similar to:

ZeroLink: unknown symbol '__ZN8DVFArray6ExistsEP6DVFObj'

You can use the C++filt program which is part of the GNU Binary Utilities to help demangle the function in error.  I could not build this package so I looked and found a copy of the C++filt program. Unfortunately I could only find a copy for the PC.
The example listed demangles to: DVFArray::Exists(DVFObj*)

2. You cannot store C++ objects/pointers in Cocoa containers.  One workaround is to store NSValue objects in the containers and store C++ pointeres in the NSValue objects:

[myNSArray addObject:[NSValue valueWithPointer:pCPPObj]];
.
.
pCPPObj = [[_array objectAtIndex:idx] pointerValue]

3. You should not use static C++ objects or static data members.  Constructors for static objects will be called before main() is called and you will not have an NSAutoReleasePool initialized yet. You will get a message similar to:

*** _NSAutoreleaseNoPool(): Object 0x307f60 of class NSConcreteValue autoreleased with no pool in place - just leaking

The discussion "NSAutoreleasePool and static data member constructors" is very good on this subject

March 04, 2008

EXC_BAD_ACCESS during startup

Once again, the XCODE debugger not only failed to help but got in the way by showing me a stack trace that was not directly relevant to my problem.   The program crashed with EXC_BAD_ACCESS and the stack trace looked like this:

#0    0x90a594c7 in objc_msgSend
#1    0xbffff7b8 in ??
#2    0x932899d8 in loadNib
#3    0x932893d9 in +[NSBundle(NSNibLoading) _loadNibFile:nameTable:withZone:ownerBundle:]
#4    0x9328903a in +[NSBundle(NSNibLoading) loadNibFile:externalNameTable:withZone:]
#5    0x93288f7c in +[NSBundle(NSNibLoading) loadNibNamed:owner:]
#6    0x93288cc3 in NSApplicationMain
#7    0x00009f80 in main at main.mm:17

This made me think that my NIB was corrupt since I didn't see anything in the stack trace that pointed to my code. After wasting several hours investigating that rat hole, I decided to try to do what the debugger didn't help with and that's track down the offending line.  With breakpoints and Debugger(); calls (breakpoints don't always work for me) I found that an IBOutlet was not being initialized and I was sending a message to nil.  If the run-time was not going to flag this as an error, why would this cause a problem down the line?  Arrggg! 

Lesson learned, if a stack trace looks similar to the above, then look at your initialization code of your Nib objects.

Memory bugs in Cocoa

At work, our software runs on Windows and Mac OS.  I'm trying to reuse as much code as possible; not through some fancy cross-platform tools but by simply writing in portable C or C++ code and by using class wrappers that can each be implemented in the native platform.  One thing I've been pretty pleased with XCode is it's support of mixing Objective-C and C++ code fairly seamlessly.

Recently, in trying to port some C++ Windows code over I started having some memory crashes.  Unfortunately the XCode debugger was of very little help with the stack trace not including any calls from my code.  This was an excellent thread on some techniques to troubleshoot EXC_BAD_ACCESS problems.  Fortunately, I didn't have to try all the various techniques listed.  After reading the entire thread I decided to pursue the notion that I might be over-releasing an object (though why should this be a problem?  A warning perhaps but seems once a reference counter is down to 0 calling release again should be harmless).  This DID lead me to the offending code so I was able to fix my problem without wasting too much time.  Once again, the developer community saved me hours if not days of debugging.  How did we get along as programmers before the Web?


Debugging AutoRelease problems

February 07, 2008

Linking up Help in Cocoa

I haven't had to "hook-up" help in a Cocoa appplication yet since the application I inherited at work already had it working.  But recently, when the application invoked help I've been getting the message:

"Help isn't available for <application name>"

I found this article to help me understand how help is hooked up in Cocoa.

I checked the required metatag and the info.plist values and they seemed in order.  What worked for me is to remove the Help Book Folder from the resources folder and re-insert it.  Be sure to select the radio button titled "Create Folder References for any added folders"

The one thing that is different in my Xcode 2.4.1 version project from the article was that the Resources folder exists inside a Frameworks folder.

September 19, 2007

objective-c error message: invalid conversion from 'objc_object*'

This error message had me stumped for a while:

invalid conversion from 'objc_object*' to 'int'

the line in question was something like this:

int iResult = [MyUtils utilsMemberFunc:param1,param2];

it doesn't matter what the "to" type is, what is important is that you recognize that this message, in this context, is reporting that the utilsMemberFunc declaration was not found and due to objective-c's dynamic binding it is assuming it returns an objc_object* rather than the type that utilsMemberFunc was declared to return.  So why isn't it finding the declaration?  Because I used ',' rather than ':' to separate the parameters.  Besides the fact my brain still thinks in C++, I use commas to separate parameters in a variable list alot ( [NSString stringWithFormat:@"%d,%d", num1, num2]).

Through trial and error and comparing similar code that works it finally dawned on me that my syntax was incorrect.

September 06, 2007

Problem with breakpoints in Xcode

I'm running Xcode 2.4.1

I have an odd breakpoint problem that I have not seen written about. I can set breakpoints no problem, my program will break correctly. My problem is when I run my program again, though the breakpoints appear marked correctly in the UI and in the breakpoint window, none of the breakpoints break.  Going to the command-line GDB (GNU debugger) via <Option><Apple> (must be in the debugger window) I type in "break info" and it reports no breakpoints!

Don't know what happened in my environment for this to start occuring but it is very inconvenient to re-enter all my breakpoints on each invocation.  I hope I find the problem soon.

Update: I found the "Stop on Debugger()/DebugStr()" command in the Debug window.  This will save me, for now, allowing me to break inside some initialization code which runs before I get a chance to set breakpoints.  I just insert a Debugger(); call in the code where I want to "break".

August 23, 2007

Working with Pascal Strings in Cocoa

In trying to access information from a quicktime file I came across my first pascal string (pstring).  Trying to copy this string using strncpy or NSString:stringWithCString did not work so I looked at the memory and saw a number as the first byte.  I recognized this as a pascal string so I searched on conversion from pstring to NSString and found this:

http://www.omnigroup.com/mailman/archive/macosx-dev/2001-August/030492.html
http://developer.apple.com/documentation/CoreFoundation/Reference/CFStringRef/Reference/reference.html#//apple_ref/c/func/CFStringCreateWithPascalString

The documentation for CFString states: "in a method where you see an NSString * parameter, you can pass in a CFStringRef" but the compiler complained so as in the example code linked above, I resorted to using the NSString:stringWithFormat. My first thought was to simply use NSString:stringWithString.

Here is the wrapper function I came up with:

-(NSString*)NSStringFromPString:(unsigned char*) pstring
{
    CFStringRef strRef = CFStringCreateWithPascalString (NULL, pstring, kCFStringEncodingMacRoman);
    return [NSString stringWithFormat:@"%@",strRef];
}

Update 8/24/07: It seems the error when using stringWithString has something to do with what source is being compiled.  If it is in an .mm file (enables C++ compatibility?) I get the error:

error: cannot convert 'const __CFString*' to 'NSString*' in argument passing

but if compiled in an .m file it works fine.  It appears that in the former case, the compiler is being stricter, though a forced cast will eliminate the error message.

Mac Documentation, not so good

I've given the Development Documentation for XCode a few weeks, but yesterday, after spending half a day searching how to retrieve the Codec information from a quicktime file, I finally concluded that the best source of documentation is source code on the web.  With the lack of examples the documentation is only of minimal help when you are new and trying to discover HOW to do things, not just reference information.

Two sites helpful in searching code are Krugle and Koder.  Krugle has a nicer UI. I haven't used Koder much.