« GUI-Less Applications

Posted by Andy Monitzer on November 12, 2001 [Feedback (0) & TrackBack (0)]

This article describes how to create background applications and GUI-less programs (Apple calls them "tools").

 

// What's That?

In Mac OS 9, there was no difference between GUI- and non-GUI-apps. Command line apps just used a window to display their data.

But that changed with Mac OS X. Its UNIX background enables programmers to do things users never even dreamed about in their worst nightmares.

Which leads us to rule #1 (the only rule for now): NEVER ever let regular users see your command line apps.

So, why should you create command line apps?

  • Runtime speed: No GUI means much less overhead.
  • Development speed: Even when using Cocoa, a printf() is much faster to create that a window full of controls wiht outlets & actions.
  • No GUI available: When there is nothing to display a window on, like at boot time.

 

// About The Window Server

Open up your Terminal, and type the following command:

% ps axwww|grep WindowServer

You'll see the following answer (or a similar one):

1444 ?? Ss 18:03.03 /System/Library/CoreServices/WindowServer console

That's your window server. It's automatically launched at boot time, just before the login panel pops up.

So, where's the difference between a GUI-app and a non-GUI-app? It's pretty simple: All applications that connect to that window server are GUI.

The main difference for the programmer is that GUI apps are terminated when the window server quits (which happens at shutdown and at logout). If you want to do something while the login panel is displayed, you must not use a GUI.

 

// Possibilities

So, if you don't have a GUI, what can you do?

Those applications are mostly servers (like databases and webservers) or things ported from other UNIXes/Linux (except X11-apps).

But what if your tool needs to tell the user something critical (like that the connection has dropped)? Mac OS X has a solution for this: CFUserNotification.

I won't go into great detail about what you can do, since that basically the same like on every UNIX platform.

Just one thing if you need heavy networking:

W. Richard Stevens: UNIX Network Programming, Volume 1, Second Edition

In addition to the POSIX API, Mac OS X offers a completely new C-API, based on Cocoa's Foundation framework. It's called CoreFoundation, and you're very likely to get contact with it when writing background programs. It even offers a great foundation (hence the name) for event handling.

Of course, you can use Cocoa/Objective-C, but be aware that you can't call any function or method that requires a GUI - generally, the whole Foundation framework is ok, but you should only use NSWorkspace from the AppKit.

 

// Example!

Well, here we go:

int main(int argv,char**argv) {
	printf("Hello, World!\n");
}

It's that easy! Now for some more complicated (Cocoa-based):

int main(int argv,char**argv) {
	NSLog(@"Hello, World!");
}

 

// Using CoreFoundation

The basic rules are the same like in Cocoa (retain, release etc). Note however that there is no autorelease pool. The basic types everybody knows from Foundation - like NSDictionary, NSArray and NSString are still here, they are only prefixed by "CF" like in CFDictionary. Additionally, you don't use pointers (*), only references (suffix "Ref", like in CFDictionaryRef).

When you're using the regular Foundation, you can even typecast them ((CFDictionaryRef)[NSDictionary dictionary] or vice-versa), that's called toll-free bridging and is something the Apple guys are very proud of (and AFAIK they got that feature by trial and error <grin>). This is pretty handy when using a CoreFoundation-based API (IOKit, SystemConfiguration etc) in an Objective C-app.

Since CF is C-based, special characters like @"" aren't allowed. Apple wrote a macro for that purpose, called CFSTR(). @"Hello" is the same as CFSTR("Hello"). You can only use constant strings for this macro, however.

To create a CFArray, you have to call the following function:

CFArrayRef myArray=CFCreateArray(...);

The function call itself is quite complicated compared to Foundation, and I won't go into much detail here. Additionally, the documentation isn't quite as good. The best way to get some information is to press Cmd-Shift-D in PB and enter "CFArray.h", followed by enter. The header file will be opened (provided the CoreFoundation framework is properly included in the project). One hint: the parameter "CFAllocatorRef allocator" is always NULL except when you know what you're doing.

The identifiers "init" and "copy" signal in Foundation that you own the returned object afterwards. In CoreFoundation, they're called "create" and "copy". [object retain]; is replaced by CFRetain(object);, [object release]; by CFRelease(object);. The major difference between those two APIs is that you can't create your own classes (types), since C doesn't know about subclassing (there are no real classes, only functions that take a reference). If you really need to do something like a subclass, use CFDatas to store the object's state, which can be passed around.

 

// Non-NSApplication RunLoops

Since you can't use NSApplication in non-GUI apps, you have to create your own event-handling loop.

In UNIX, there are generally only two types of events:

  1. External: Sockets (network, files, pipes etc)
  2. Internal: Timers

In a regular UNIX tool, those can be handled by the call select(). select() blocks until a file selector has changed state (external) or the timeout has expired (internal). But it's not very convenient.

Apple (while it was still called NeXT) created a Foundation-class called NSRunLoop which took care of the low level things like file descriptors and multiple timers. In Foundation-apps, it's still the way to go. Since NSApplication builds upon NSRunLoop, all the calls are the same like in regular applications, you only have to call [[NSRunLoop currentRunLoop] run]; once per event-handling thread.

NSRunLoop handles all events by "sources", those can be sockets and timers. Note that -run usually never returns (except when all input sources are removed, which I never got to work).

In pure C-apps, you can't use NSRunLoop. But Apple in its work to move away from Objective C has created CFRunLoop, which basically does the same things. Everything you add to the current CFRunLoop is also in the current NSRunLoop, so it's possible to mix both.

To add sockets to the current runloop (e.g. network connections), you have to create a CFSocket and add it:

void addSocketToRunLoop(int mysocket) {
	CFSocketContext context={0,0,NULL,NULL,NULL};
	CFRunLoopSourceRef rls;
	CFSocketRef newSocket;
	
	newSocket = CFSocketCreateWithNative(NULL,
		mysocket,
		kCFSocketReadCallBack, // see CFSocket.h
		mycallback,
		&context);
	if(!newSocket) {
		close(mysocket);
		return;
	}
	
	rls=CFSocketCreateRunLoopSource(NULL, newSocket, 0);
	if(!rls) {
		CFRelease(newSocket);
		return;
	}
	CFRunLoopAddSource(CFRunLoopGetCurrent(), rls,
		kCFRunLoopDefaultMode);
	CFRelease(rls);
	CFRelease(newSocket);
}

mycallback is a C-function that takes the parameters (CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info). There's no documentation anywhere on those parameters, but most of them are pretty self-explanatory. It's called whenever the event defined in the previous parameter (kCFSocketReadCallBack in my example) occures. When the kCFSocketReadCallBack-callback is called, the data is not yet read from the socket, so you have to do it (by using read() for example). If you don't do it, the callback is permanently called and you slow down the whole machine (a common mistake). There are events available for listening, which can be pretty handy in server applications.

A great example for a CFRunLoop-based tool is available from darwin's CVS-repository, it's called "scutil" and is part of the SystemConfiguration-module.

 

// Conclusion

As you can see, writing non-GUI apps is a wide-spread field, reaching from Mac OS 9-legacy things (AppleEvents) via the system's UNIX-inheritance (select(), sockets) to completely new creations (CFRunLoops, IOKit). I can't cover everything, but I'll try to add more tutorials on these things as time passes. If you think something is important to be covered, feel free to write me!.


Comments
Post a comment