Using PerlObjCBridge to Write Cocoa Applications In Perl
The Perl Journal February 2003
By Kevin O'Malley
Kevin is a long-time Macintosh and UNIX developer. He is the author of the upcoming book Mac OS X Programming: A Guide for UNIX Developers (Manning Publications, 2003), on which this article is based. He can be contacted at [email protected].
On March 24, 2001, Apple released its new operating system, called "Mac OS X," to the public. Mac OS X (pronounced "OS Ten") is built on an open-source, UNIX-based core operating system called "Darwin." Mac OS X includes a full set of UNIX commands and tools, and a host of new development frameworks and technologies. One of these is Cocoa, an object-oriented environment for developing native Mac OS X applications. Cocoa provides developers with a rich component framework that greatly simplifies and facilitates the development of Mac OS X applications and GUIs. In fact, Apple recommends that developers use Cocoa when writing new applications for Mac OS X. For more information on Cocoa, see the Cocoa Resources section at the end of the article.
The latest release of Mac OS X (10.2), called "Jaguar," includes a Perl module called PerlObjCBridge. PerlObjCBridge enables Perl programmers to access Cocoa objects and services from their Perl scripts. This is very exciting news for Perl programmers working on the Mac OS X platform.
This article presents an introduction to PerlObjCBridge and shows how to use it in a real Perl program.
PerlObjCBridge Fundamentals
PerlObjCBridge provides the following functions:
- Enables access to many Objective-C objects from Perl.
- Enables Perl scripts to be Cocoa delegates or targets of notifications.
- Enables Perl scripts to access Cocoa's Distributed Objects (DO) mechanism, letting Perl objects send and receive messages to and from Objective-C or Perl objects running on different machines.
One limitation of PerlObjCBridge is its lack of support for accessing Cocoa GUI objects. This means you cannot use it to construct user interfaces for your Perl scripts. (See http://camelbones .sourceforge.net/ for information about a GUI framework for constructing Cocoa interfaces in Perl.)
In terms of syntax, Objective-C uses ":" to delimit arguments, which is not legal in Perl. Therefore, the "_" character is used in its place. To access Objective-C objects and methods from Perl, you use the following forms:
- Static method (through the class)ClassName->method(...args...)
- Instance method (through the instance)$object->method(...args...)
The following code shows a few examples of how to use these constructs:
# Accessing a method through its class (static method). $pref = NSMutableDictionary->dictionary(); # Accessing a method through an instance (instance method). $pref->objectForKey_($key);
One of the more powerful features of PerlObjCBridge is its ability to register Perl objects as recipients of notifications from Cocoa frameworks. For example, PerlObjCBridge automatically provides the stubs, or Objective-C objects, that act as proxies for Perl objects. If you have a Perl object like this:
package Foo; sub new { ... } sub aCallBack { ... }
you register Foo objects to receive NSNotification messages as follows:
$foo = new Foo(); NSNotificationCenter->defaultCenter() ->addObserver_selector_name_object_($foo, "aCallBack", "theNotificationName", undef);
When the event theNotificationName occurs, the Cocoa Foundation sends the aCallBack message to $foo. Behind the scenes, PerlObjCBridge automatically creates a PerlProxy object to stand in for $foo wherever an Objective-C object is expected, as in the observer argument to the addObserver method. Cocoa's DO mechanism enables Cocoa programs to access objects from different programs, possibly running on different machines. You can access DO from PerlObjCBridge, enabling interprocess messaging between Perl objects. Basically, you write Perl scripts that run on different machinesor in different address spaces on the same machineand that send messages to one another. Doing so enables your scripts to communicate with other scripts by directly calling their methods as if they were part of the same program.
Let's look at how to apply this knowledge in a Perl script.
A Perl-Based PIM Using PerlObjCBridge
These days, Palm devices are everywhere. They are used to track contacts and schedules, enter information into databases, access e-mail and the Web, and play games. In this example, we'll use software that comes with most UNIX systems to replicate some of this functionally at little or no cost.
The script, called "pim.pl," uses standard UNIX tools to track contacts, take notes, generate and view calendars, and even keep a list of quotes. The main UNIX programs used are cal and remind. (The remind program is freely available from http://www .roaringpenguin.com/remind/. It does not come with the system, so you will need to download it, compile it, and install it for Mac OS X.) The cal program displays a text-based calendar. If you have never used remind, you are in for a treat. Basically, remind is a calendar generator and reminder program with lots of options and uses. For the complete source code, see http://www.tpj.com/source/.
The first step in using remind is to create and edit your reminders file, called ".reminders", which is located in your home directory.
# The .reminders file. REM 15 November +1 2002 MSG 4-5 Development meeting REM 05 November 2002 *7 MSG 4:00-5:00 AI seminar REM 04 November 2002 *1 until November 06 2002 MSG Out of office
Entries in this file represent calendar events or reminders. Once you add entries to the file, you run the remind command to process the file. Depending on the options, remind will output everything from a reminder list to a text- or Postscript-formatted calendar. Figure 1 shows a text-based calendar generated from the reminders file.
To view or edit your contact list, the Perl-based pim script opens the file in an editor; to edit tasks, it opens the task file in an editor; and to view tasks, it processes the file and prints a formatted version of the task list.
The Cocoa classes include a particularly useful set of methods: the writeToFile and stringWithContentsOfFile family of methods. Collectively, these methods let you to take an object, serialize its data to disk, and read the stored data from disk into an object at run time. When used in conjunction with the NSMutableDictionary, a hash data structure, you do not need to deal with formatting or parsing data; it's all done for you. This feature is particularly attractive and is a strong reason to use Cocoa objects in your Perl scripts. This program uses these features to store and access preference settings.
Application preferences are stored in a preference file, which is a text file formatted as XML. Each key/value pair in the file describes a particular program option. For example, the editor keyword is used to look up the editor that the script uses to open files (contacts-file for the name of the contacts file). See Listing 1 for an example of the preferences file.
You can initially create this file either programmatically or by hand. To create it programmatically, you can use elements of the following code fragment:
my $pref = NSMutableDictionary->dictionary(); setPrefVal("editor", "/usr/bin/emacs"); setPrefVal("contacts-file", "contacts.txt"); writePrefs("pim.prefs"); sub setPrefVal { my ($key, $val) = @_; $pref->setObject_forKey_($val, $key); } sub writePrefs { my ($fName) = @_; $pref->writeToFile_atomically_($fName, 1); }
When the script starts, it creates a new, empty dictionary object by calling the static method dictionary from Foundation's NSMutableDictionary class. Next, it populates the dictionary (readPrefs) with key/value pairs from the preference file. To do so, it uses the NSDictionary static method dictionaryWithContentsOfFile. In a single call, it reads the preference file and stores each key/value pair into the dictionary object. This saves you the trouble of creating a new file format and writing code to parse and store the preference values:
my $prefs = readPrefs($PREFS_FILE); sub readPrefs { my ($fName) = @_; my($dict) = NSDictionary->dictionaryWithContentsOfFile_($fName); if (!defined($dict)) { logExit("preferences file not read: $PREFS_FILE"); exit; } return $dict; }
The rest of the script is quite simple. It goes into an infinite loop in which it displays a menu and handles user selections (see Listing 2). To exit the program, press Control-c. The handling code is also straightforward, as demonstrated by the if/elsif block. In both cases, it gets the appropriate value from the dictionary (based on a key) and handles the operation. Figure 2 shows the program responding to the Task feature.
Overall, this script does the job, but it could be improved. The program could be extended to use Cocoa's Distributed Objects mechanism. For example, the script could act as a server running on one machine, and you could access it from another machine to read and update information. Additionally, you could write a Cocoa GUI client that displays the information in an interface while the main handling and storage code runs on the server.
Another addition would be to add items to your calendar or task list from your e-mail inbox. This functionality would let you send yourself e-mail that goes directly into your PIM. For example, to add a task to your PIM, you could send yourself an e-mail with a special tag (TASK, for example) in the subject. Then, your PIM could read your inbox looking for message subjects with this tag and add the appropriate e-mails to your task list.
Summary
This article has given you a brief look at PerlObjCBridge, a Perl module that comes installed under Mac OS X 10.2, Jaguar. Use the PerlObjCBridge man page to get more information on its features. Using PerlObjCBridge, Perl programmers can now take advantage of Mac OS X's Cocoa Foundation directly from their Perl scripts. PerlObjCBridge is a good piece of software engineering, providing many more functions than I've discussed here. I hope it will continue to grow and include more features, including the ability to construct Cocoa GUIs from Perl scripts. If you are a Perl programmer working under Mac OS X, take a serious look at PerlObjCBridge.
Cocoa Resources
- Apple Computer Inc. Learning Cocoa. Ed. Troy Mott. Sebastopol, CA: O'Reilly, 2001.
- Buck, Eric, Donald Yacktman, and Scott Anguish. Cocoa Programming: Programming for the MAC OS X. Indianapolis: Sams, 2001.
- Garfinkel, Simson, and Michael K. Mahoney. Building Cocoa Applications. Sebastopol, CA: O'Reilly, 2002.
- Hillegass, Aaron. Cocoa Programming for Mac OS X. Boston: Addison-Wesley, 2002.
- Cocoa Developer Documentation: "New to Cocoa Programming." http://developer.apple.com/techpubs/macosx/Cocoa/ SiteInfo/NewToCocoa.html.
Acknowledgement
Thanks to Doug Wiebe of Apple Computer, the author of the PerlObjCBridge, for his information and insights on this topic, and for some of the code examples that appear in this article.
TPJ
Listing 1
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer// DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>prefs-path</key> <string>./</string> <key>editor</key> <string>/usr/bin/emacs</string> <key>viewer</key> <string>more</string> <key>file-path</key> <string>./</string> <key>contacts-file</key> <string>contacts.txt</string> <key>tasks-file</key> <string>tasks.txt</string> <key>words-file</key> <string>word_list.txt</string> <key>notebook-file</key> <string>notebook.txt</string> <key>quotes-file</key> <string>quotes.txt</string> </dict> </plist>
Listing 2
for(;;) { system("clear"); print "=======================\n"; print "My PIM\n"; print "=======================\n"; print "0. Edit preferences file\n"; print "1. Edit reminders\n"; print "2. Edit contacts\n"; print "3. Edit tasks\n"; print "4. Show tasks\n"; print "5. Generate calendar (ps)\n"; print "6. View calendar (txt)\n"; print "7. Print calendar's"; print "8. Show system cal\n"; print "9. Show today's reminders\n"; print "-------------------\n"; print "-1. Edit word list\n"; print "-2. Edit notebook\n"; print "-3. Edit quotes\n"; print "-4. View quotes\n"; print "=======================\n"; print "-> "; my $s = <STDIN>; chop($s); if ($s == 0) { system(getPrefVal("editor") . " " . $PREFS_FILE); } elsif ($s == 1) { system(getPrefVal("editor") . " ~/.reminders"); }