« Cocoa and Perl - There's More Than One Way to Do It
by Don Briggs
& George Szynal
Introduction—the PerlObjCBridge
The intended audience for this article includes:
- Cocoa Objective-C veterans who want to explore using Perl from Objective-C
- Perl veterans who want to deploy their Perl skills in Cocoa.
Since September 2002, the Cocoa development environment in Mac OS X has included the PerlObjCBridge module. Users can find it at /Library/Perl/darwin/PerlObjCBridge.pm in Jaguar or at /Library/Perl/5.8.1/darwin-thread-multi-2level/PerlObjCBridge.pm in Panther to begin exploration. Jaguar installs with Perl 5.6; Panther installs with Perl 5.8.1.
The PerlObjCBridge allows Perl to interoperate with Objective-C objects of Cocoa's Foundation kit. The first example from PerlObjCBridge compares this Objective-C passage:
#import <Foundation/Foundation.h> NSString *s1 = [NSString stringWithCString:"Hello "]; NSString *s2 = [[NSString alloc] initWithCString:"World"]; NSString *s3 = [s1 stringByAppendingString:s2]; printf("%s\n", [s3 cString]);
to this passage in Perl that relies on the PerlObjCBridge:
#!/usr/bin/perl use Foundation; $s1 = NSString->stringWithCString_("Hello "); $s2 = NSString->alloc()->initWithCString_("World"); $s3 = $s1->stringByAppendingString_($s2); printf "%s\n", $s3->cString();
Save these few lines of Perl as plain text to a file called helloBridge.pl Be sure to remove any leading white space on the first line. Compare to the file in this article's distribution, if you like. Set the file's execution bit and then run it:
> chmod +x helloBridge.pl > ./helloBridge.pl Hello World >
We have just demonstrated that the Perl passage above, when saved to a file and executed, runs from the command line with no compilation. Clearly, the PerlObjCBridge provides new opportunities for developers.
From Stand-Alone to Cocoa Delegate
This first demonstration of the PerlObjCBridge in helloBridge.pl is just a "stand-alone" Perl executable file that accesses Cocoa's NSString class. The PerlObjCBridge offers more, but this article focusses on just one pattern. A Perl object can vend itself through Cocoa's Distributed Objects (DO) mechanism. An Objective-C object can then connect to it through Distributed Objects and the Perl object can then act as a kind of delegate. The Perl object sends and receives Perl messages; the Cocoa object sends and receives Objective-C messages. Distributed Objects and the PerlObjCBridge work together to provide the translations.
This article assumes only basic knowledge of DO. In Mac OS X installations, DO newbies can find documentation in the file Distributed Objects (Jaguar) or Distributed Objects (Panther). Perl objects can also register to receive NSNotifications, but this article does not provide an example. We'll stick to DO.
This article discusses the AddSystem example provided in the PerlObjCBridge module, then presents it repurposed as MathSystemDO, an example Cocoa application in Objective-C and Perl. The disk image distribution accompanying this article includes source code and resources for the AddSystem and MathSystemDO examples, in versions appropriate for Jaguar and Panther.
In the MathSystemDO application, a distinct "target" of the MathSystemDO project encapsulates the Perl portion. A Makefile resource integrates this Perl target into the Xcode or Project Builder environment seamlessly.
The light weight approach presented in this article relies only on resources included in your Mac OS X installation. It requires no third-party framework. The AddSystem example installs in shared system resources; MathSystemDO does not. The MathSystemDO Cocoa application installs by the typical drag-and-drop method—authenticated installation is unnecessary.
Motivation—Why Mix Objective-C and Perl in Cocoa?
Programming languages differ not so much in what they make possible, but in what they make easy1. Each language, taken with its APIs, has its own "sweet spots." From this perspective, Perl and Objective-C excel in different arenas.
Perl was first designed primarily for processing text. It has since diversified and become popular and more powerful. It supports objects. Its developers support large, powerful open-source projects in wide use. These APIs establish Perl as a valued component of the "business logic" of the web and commerce.
Objective-C and its Cocoa APIs excel for new application development on Mac OS X. They provide a very productive development environment for the rapid development of full-featured, high-performance, and highly reliable applications built on UNIX.
Programming languages foster communities of developers. Undoubtedly, the Objective-C community has enjoyed better a connection to the C++ and Java communities than to the Perl community because the Cocoa environment supports Objective-C++ and the Objective-C/Java bridge. The PerlObjCBridge offers improved integration of Cocoa and Perl, and can serve to draw the interest of the Perl community to Mac OS X.
The Cocoa development environment encourages the "Model-View-Controller" design pattern. This article demonstrates how Perl components can serve in the "Model" of Cocoa applications. The PerlObjCBridge makes it fairly easy to provide an Aqua interface to a Perl utility, or to add Perl's power to Objective-C in the "Model."
No Bones About It
This article presents an example that uses the PerlObjCBridge directly - a light weight approach. Cocoa developers can also use the CamelBones framework by Sherm Pendley. CamelBones offers several advantages and conveniences to the developer, including the ability to write GUI Cocoa applications in Perl. However, its use incurs a dependency on a third-party framework. Developers considering Perl in Cocoa should evaluate both approaches. After all, with Perl, there's more than one way to do it.
The AddServer Example from the PerlObjCBridge
You can read the PerlObjCBridge man page from the command line,
> man PerlObjCBridge
but in the discussion below, we refer to the file itself. As users of the PerlObjCBridge, our particular interest begins with the Summary section. The discussion tells us how to build the AddSystem example. In this section, we follow that discussion to build and test it, adding a few bonus clues for Perl newbies.
The AddSystem code demonstrates how to use Perl's XSUB (for "eXternal SUBroutine") mechanism with Cocoa's Distributed Objects. The AddSystem example comprises a client/server pair, but both are in Perl. They communicate through Cocoa's DO. We run each of the Perl executables from its own shell to test their communication. Following the expected practice, we install the AddSystem package using root privileges.
The section on Distributed Messaging introduces the AddSystem example, with instructions on how to create it. A shell command line generates the initial AddSystem directory and populates it with a few initial files. In Jaguar, the command line response is:
> h2xs -A -n AddSystem Writing AddSystem/AddSystem.pm Writing AddSystem/AddSystem.xs Writing AddSystem/Makefile.PL Writing AddSystem/test.pl Writing AddSystem/Changes Writing AddSystem/MANIFEST >
In Panther with Perl 5.8.1, the response is slightly different. Also, the files built by the command above in your own filesystem may differ from those described in PerlObjCBridge.pm. Refer to this article's distribution.
Continuing in PerlObjCBridge.pm, instructions follow on how to modify the files Makefile.PL AddSystem.pm AddSystem.xs
Warning: Read Ahead Before You Proceed
Before we perturb some system resources, make a backup. Do something like this:
> ditto /Library/Perl/darwin/ ~/PerlDarwinBackup (in Jaguar)
or
> ditto /Library/Perl/5.8.1/darwin-thread-multi-2level/ ~/PerlDarwinBackup (in Panther)
before you proceed. If this step makes you feel squeamish, you might want to read the whole article first. The AddSystem example installs in the system's Perl resources, but the Cocoa application we build below does not. Students on shared Macs without root privileges will find it impossible to install AddSystem. We welcome them to Cocoa and Perl anyway, and offer them MathSystemDO.
And Proceed Carefully
After editing the files following the discussion in PerlObjCBridge, compile with root privileges as a test:
root > perl Makefile.PL Checking if your kit is complete... Looks good Writing Makefile for AddSystem root > and then install: root > make install
If working from the distribution, perform these commands in a writable copy of the AddSystem directory appropriate for your version of Perl.
The discussion then tells you how to edit the files created, and how to create the addServer.pl and addClient.pl files.
See the appropriate version in the distribution to check your results. Open a shell, become root, "cd" to your writable copy, and "make install".
Bonus Clues for Perl Newbies
The commentary in the Panther release of PerlObjCBridge.pm has improved since the first Jaguar version, but the following points still deserve emphasis in finishing the AddSystem example.
1) Make sure that the first lines
#!/usr/bin/perl
in the addServer and addClient programs have no leading whitespace. Passages copied from the man page and pasted into text files may contain bonus white space. Use the FileMerge application to compare your AddSystem folder with the appropriate version in the distribution.
2) After editing the Makefile.PL, AddSystem.pm, and AddSystem.xs files as instructed in PerlObjCBridge.pm, do the following inside the AddSystem directory (this requires "root" privileges):
root > perl Makefile.PL root > make install (and you can now revert to normal user privileges). Note that, with 'admin' privileges, you may get an error result:
sysadmin > make install mkdir blib mkdir blib/lib mkdir blib/lib: Permission denied at /System/Library/Perl/ExtUtils/Command.pm line 180 make: *** [blib/lib/.exists] Error 255 sysadmin > whereas 'root' privileges succeed: root > make install mkdir blib/lib mkdir blib/arch ... Installing /Library/Perl/darwin/addClient.pl Installing /Library/Perl/darwin/addServer.pl Skipping /Library/Perl/darwin/AddSystem.pm (unchanged) Writing /Library/Perl/darwin/auto/AddSystem/.packlist Appending installation info to /System/Library/Perl/darwin/perllocal.pod root >
3) Be sure that the addClient and addServer files are executable.
> chmod +x addClient.pl addServer.pl
4) Finally, as shown in Figure 1, you may have better results using in one shell
> ./addServer.pl
and in another shell
> ./addClient.pl 19 23 42
both under your own user account. If you run the server as root and your client as yourself, your client process may not be able to connect to root's server process.
Fig. 1
Use 'root' privileges to 'make install' as in step (2), then run each process in its own shell, as in (4).
The addServer process takes no command line arguments and returns none. It continues as if hanging until you kill it.
The addClient process takes two numeric command line arguments and returns one numeric result immediately.
Success! — At a Price...
Congratulations—AddSystem works, but you've just sullied your Mac OS X installation with example code of rather limited utility, now installed as a system resource in "/Library/Perl/darwin/" (Jaguar) or "/Library/Perl/5.8.1/darwin-thread-multi-2level/" (Panther). We recommend that you clean it up quickly, before your system administrator finds out. However, this article does not provide a script for that. We leave it as an object lesson for the hapless reader. Using root privileges in your AddSystem folder, try
> make uninstall
but pay close attention to the result—you'll have to remove a few files individually. When you do, by all means avoid removing "real" system resources, such as PerlObjCBridge.pm and Foundation.pm. If you err, perhaps you can recover them from the backup we hope you made above.
Recap
The AddSystem example from PerlObjCBridge provides two Perl processes, addServer and addClient. They communicate through DO. AddSystem has been installed as a shared Perl system resource — oops!
The Mac OS X Way — UNIX for the Rest of Us
Clearly, there is a role for shared libraries of code. In the case of Perl, Mac OS X provides support for the Comprehensive Perl Archive Network (CPAN). Try > man cpan from a shell. A collaboration of Perl developers manages CPAN. The AddSystem example seems inappropriate for inclusion; let's not submit it.
Cocoa applications can often encapsulate all the custom resources they need. When a user installs or uninstalls a well encapsulated application, she requires no authentication—no root or admin privileges—because she perturbs no system resources. In other desktop operating systems, entire cottage industries thrive, creating and solving the problems of shared piecemeal third-party resources. Let's not go there. Newbies who were squeamish about installing example code among system resources are invited to skip ahead to the MathSystemDO example below.
MathSystemDO
The disk image distribution accompanying this article provides Jaguar and Panther versions the MathSystemDO example as Project Builder projects. MathSystemDO derives directly from AddSystem, repurposed as an Objective-C Cocoa application that uses a Perl object through DO as a kind of delegate. The Perl code differs from that in the AddSystem example in two ways. Its name is different—to distinguish it from AddSystem, in case you failed to remove all its traces from the system's Perl resources. Also, it omits the Perl file for the client. The Cocoa application's instance of the AppDelegate class will act as the client instead.
The Cocoa application launches the Perl server task mathServer.pl (neé addServer.pl) from its main bundle in AppDelegate's -(void)launchMathServer method. The Perl server vends itself as a Distributed Object, just as in AddSystem. The AppDelegate instance also plays the role of AddClient, and adds a Cocoa graphical user interface. Our Objective-C client object connects to its Perl server through DO in its -(void)connectToServer method.
Finally, user actions trigger an exchange of messages between the client and server. In its
-(IBAction)addAction: sender method, the AppDelegate sends a request message to its server proxy—to the Perl Server over DO. In response, the server asks the client for its two numbers to add. Finally, the server sends the sum back to the client. The inefficient messaging emphasizes that the Cocoa client and its server proxy "speak" Objective-C while the Perl server and its client proxy "speak" Perl. The combination of Cocoa's Distributed Objects mechanism, Perl's XSUB system, and the PerlObjCBridge conspire to translate.
The Project
The project comprises three targets: MathServerDO, PerlServer, and All. The MathServerDO target provides the Objective-C application. It amounts to one class and its nib file. It also contains the file "Protocols.h", which serves as the Objective-C complement of the Perl XSUB. The PerlServer target provides the file mathServer.pl and the XSUB interface to DO. The All target combines the other two.
The project's "Other Sources" group adds an extra Makefile and the MathSystem folder. The Makefile (below) has two targets, 'install' and 'clean'. It installs the Perl server in the product application's Resources folder, not among shared system resources. The MathServerDO application encapsulates its resources politely.
install:: cd MathSystem && perl Makefile.PL && make mkdir -p $(BUILT_PRODUCTS_DIR)/MathServerDO.app/Contents/Resources/MathSystem ditto MathSystem/MathSystem.pm MathSystem/mathServer.pl \ $(BUILT_PRODUCTS_DIR)/MathServerDO.app/Contents/Resources/MathSystem ditto MathSystem/blib/arch/auto \ $(BUILT_PRODUCTS_DIR)/MathServerDO.app/Contents/Resources/MathSystem/auto clean:: @if [ -f MathSystem/Makefile ]; then \ cd MathSystem; \ make clean; \ fi
(For consistency and readability, the passage above appears in a particular format. Don't just copy/paste this passage to a file. Of course, white space has meaning in such files. Instead, you should rely on the Makefile in the MathServerDO project directory of the distribution.)
A custom build command attached to the PerlServer target integrates this Makefile into the Project Builder environment seamlessly. See Figure 2. (Xcode developers: just double-click on the PerlServer target.)
Fig. 2
The Custom Build Command (Jaguar, Project Builder)
Try This at Home
You can add a comparable Perl "Model" resource to your own project. Just use the command
> h2xs -A -n MyModule
as above, substituting an appropriate name as the final argument. Add the resulting directory to your project. Then use the
> perl Makefile.PL
command. Specify your bridge interface API in an Objective-C protocol file, the *.xs file, and a Perl implementation. Do not install. Add a copy of the Makefile above, again with the appropriate substitutions for the name of the project in its embedded paths.
Gotchas
To use the technique presented with confidence, developers should be aware of the following "gotchas."
-
DO Newbie Alert
The Objective-C and the Perl sides must agree on the name used for the DO connection. In the case of the MathSystemDO example, the Perl server publishes a connection named 'MathServer' and the Objective-C subscribes to a connection named @"MathServer". The DO newbie who changes one name and not the other may thrash a bit. -
Agreement on the Perl/Objective-C API
The developer must ensure agreement among three expressions of the Objective-C/Perl bridge interface:
- the Objective-C protocol and its implementation;
- the XSUB file with the suffix *.xs; and
- the Perl implementation.
-
Zombies in Early Development
Notice that the AppDelegate class implements the applicationWillTerminate: method to kill its delegate task. In early development, your application might crash before it terminates cleanly. It might then leave its Perl server task running as a zombie. In that case, be sure to kill the delegate task manually. Track it down:> ps -awx | grep perl 682 ?? Ss 0:00.20 perl /blah/blah/MathServerDO/build/MathServerDO.a 987 std R+ 0:00.00 grep perl note the process number (highlighted here in red), kill it: > kill -9 682 and check again, just to be really, really sure: > ps -awx | grep perl 989 std R+ 0:00.00 grep perl >
Otherwise, you might find your application stubbornly connecting to "old code"—and your fixes that span the bridge will have no effect.
To avoid zombies, consider this option in early development of your own project:
- add a method in your bridge API for the server/delegate to kill itself—when the client sends this message, the server process dies;
- in your Objective-C client code, try to connect to any existing zombie tasks and kill them all by sending that message;
- and only then launch your new Perl task—your most recent version, contained in the newly built app's Resources—and connect to your new server/delegate instead.
To support this tactic, the Objective-C protocol might declare
-(oneway void)die_zombie_die;
which should be implemented in the Perl server with no return:
sub die_zombie_die { print "MyPerlServer->die_zombie_die() -- Gack!!\n"; die; }
We leave the remaining implementation details as an exercise for the reader. More advanced forms of client/server command and control may prove appropriate for real-world applications. -
Exposure
The technique discussed exposes the application's Perl code. It all arrives in the downloaded application's bundle. Any user can "Show Package Contents" and examine the Perl code in the Resources folder.Within the limits set by the XSUB and DO protocol—the bridge API—the user can even modify the Perl implementation. Try modifying the primary method in the built application's MathServerDO.app/Contents/Resources/MathSystem/mathServer.pl as follows:
sub addNumbersForClient_ { my($self, $client) = @_; return 42; }
then run the built application again without recompiling. The "calculation" now returns only the value 42. To protect against this opportunity, shipping applications might encapsulate their Perl resources in some encoded form, and decode them only at run-time, perhaps "just in time." -
Evolution
Finally, advanced developers should track any release notes concerning the evolution of Perl support in Mac OS X. An application built in this way for Jaguar/Perl 5.6 may fail under Panther/Perl 5.8.1. Similarly, a Panther/Perl 5.8.1 delegate may fail in a Jaguar/Perl 5.6 environment.
Beyond Primitive Types
In this example, the Perl server/delegate makes limited use of Cocoa objects and the Foundation API. Certainly, its simple math calculation does not present a compelling case for using Perl. The Distributed Objects mechanism can support more than just the transfer of simple primitive types, though. It can support the exchange of graphs of objects using Foundation container classes. For a more extensive example of Perl code using Cocoa's Foundation classes, see the Resources bundle of Suite Modeler 2.0 (Jaguar) or 2.1 (Panther). Suite Modeler uses Perl to generate "rapid prototype" code for a Cocoa suite. The generation of source code from an abstract specification is a textbook2 example of one of Perl's "sweet spots." Since Suite Modeler's intended users are other developers, its Perl code remains 'in the clear.'
Conclusion
The PerlObjCBridge module permits Cocoa developers to mix Perl and Objective-C in Cocoa applications. The Perl and Objective-C components exchange messages through Cocoa's Distributed Objects mechanism. The technique works well to add Perl resources as "Model" components following the "Model-View-Controller" design pattern. Project Builder or Xcode can manage the Perl code handily with a Makefile and a Custom Build Command. The Cocoa application encapsulates its Perl code seamlessly. The resulting application requires no authenticated installation, because it perturbs no system resources. It does not require a third-party framework. However, Cocoa Objective-C developers who wish to explore Perl should also consider CamelBones.
Acknowledgments
Many Bothans died to bring us this information. In November 2002, it seemed feasible to deliver a Objective-C/Perl application like this example, and we hacked a solution. It was, perhaps, the worst of both worlds—Perl newbie and Cocoa newbie—but it worked. We submitted a a "technical incident" to Apple DTS with our working example, asking how best to do the same thing in a way friendlier to Cocoa development. After a latency of almost two months, Apple DTS demurred that Perl and PerlObjCBridge were outside their purview, and referred us to the developer lists. The developer lists we tried proved unhelpful.
The authors gratefully acknowledge the help provided by the Apple engineer who provided the Makefile above, its custom build command, and a tweak to our MathServerDO source. However, he declined to be named in this article.
This article with its Jaguar example was in draft form at the time of WWDC 2003. In early Panther pre-releases these examples failed, so it seemed best to delay this article until the official release of Panther.
Bibliography
- Programming Perl, Third Edition, by Larry Wall, Tom Christiansen, and Jon Orwant, O'Reilly, ISBN 0-596-00027-8, Preface.
- Advanced Programming in Perl — Foundations and Techniques for Perl Application Developers, First Edition, by Sriram Srinivasan, O'Reilly, ISBN 1-56592-220-4, Chapter 17, "Template-Driven Code Generation."
I'm playing around with bootstrapping AppKit out of Foundation. Its a big stab in the dark of course, but it should be possible, right? Anyway, I've got as far as being able to call various AppKit classes by loading AppKit at runtime via NSBundle, so its proving interesting if nothing else...
Posted by: James Duncan on November 11, 2003 12:21 PMAbout using AppKit and making GUI applications: I think CamelBones (mentioned above) is the way to go here. Its distribution includes some example apps, such as the perldoc viewer ShuX. CamelBones integrates nicely with ProjectBuilder (and InterfaceBuilder).
To quote the PerlObjCBridge manual page: "This version of PerlObjCBridge does not directly support writing GUI Cocoa applications in Perl. Consult http://www.source-forge.net/projects/camelbones for a freeware package that supports GUI Perl/Cocoa apps."
Posted by: Thilo Planz on November 25, 2003 10:59 PMYou say that, but I have got it working :-)
I've documented some of it on my blog at http://www.whoot.org/archives/000009.html. The last couple of things that need doing are to make actions connect to Perl classes, which I'm going to be working on shortly, but other than that it all works.
Yes, CamelBones works, but its nice to be able to bootstrap from the ground up with PerlObjCBridge.
Posted by: James Duncan on November 26, 2003 05:13 AMI think I prefer CamelBones, but then, I've never been particularly fond of programming client server applications. Still, I was able to program a crude cocoa client to WWW::Babelfish. If anything, it demonstrates that the ability of the Mac and Perl 5.81 to handle MacRoman encodings is somewhat tenuous. (Email me, removing the NOSPAM first, if interested.)