Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2003-106
$eventId
ID of event: wwdc2003
$eventContentId
ID of session without event part: 106
$eventShortId
Shortened ID of event: wwdc03
$year
Year of session: 2003
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: ...

WWDC03 • Session 106

Choosing an Interprocess Messaging Strategy for Mac OS X

Core OS • 1:02:13

The goal of this session is to survey the interprocess and intermachine communication mechanisms in Mac OS X. We cover the basics of Mach Messaging, CFMessagePort, Objective-C distributed objects, AppleEvents, and Web Services APIs, as well as a strategy for choosing which model is appropriate for your application.

Speaker: Steve Zellers

Unlisted on Apple Developer site

Transcript

This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.

What I'm here to talk about today is application, inter-application communication on Mac OS X. There are a lot of different ways of achieving inter-application communication, but I want you to get out of this talk the understanding of what frameworks are available and some of the different limitations of the frameworks. When we talk about inter-application communication, when you get right down to it, the operating system is kind of like just another application. When you request a service from the operating system, you're making an interprocess call, and the kernel is going to give you back some result.

And it really lets you think about the whole system as individual applications are building blocks, just like frameworks are building blocks that let you build your application. Only when applications are participating in interprocess communication, they can all be put together by users. That's really scripting, and I'll get to scripting quite a bit later. Steve Zellers So there are different frameworks in our system.

Our system has a long legacy from Unix to Mac OS 9 APIs, 7 APIs, and Objective-C APIs, and it keeps growing into the future. And all these different APIs are going to be available to us. So this is what I'm hoping you'll learn coming out of this session. What frameworks are available? What APIs are available? A familiarity with these APIs and what they're all capable of.

I want you to be able to decide whether there's an existing conversation someplace that would make sense for you to participate in. The scripting conversations, there's a well-established community of scripting that's available. So you would want to plug into that with your application so that scripters can script your application.

And then there's finding someone to talk to. This is actually a really important thing. And because of the way our system partitions applications into their own address space, the behavior of finding somebody to talk to is different in Mac OS X than it's ever been in Mac OS 9. There's been a lot of confusion about this and how things don't quite work the way they did under Mac OS 9. And I'm hoping that we'll be able to explain some of why that is.

So here are the frameworks that we're basically going to talk about. There's the Unix and Mach APIs that came out of, well, Darwin, basically. and Apple Events and Objective-C distributed objects are, of course, an application layer API. It's a much higher level of doing interprocess communication. These can both be used locally between processes and over the network with other applications running on other machines.

And then finally, Web Services and what we're doing with Web Services in the application frameworks. All right, so here's our bubbly diagram. It shows basically where POSIX and UNIX live and Mach Messaging lives right at the bottom. Application frameworks live on top, and then your applications live on top of that. So let's talk just first about POSIX streams. All right.

POSIX streams are, well, POSIX pipes and sockets are typically stream-based. What we mean by that is you're going to write a byte in one end and you're going to read a byte out of the other. And there are a couple of different APIs in that space. Pipe and socket are probably the most common.

This is how you create one of these file descriptors that you're going to write data to or read data out of. The to in parentheses, by the way, that means there's a man page for this. I know a lot of you are old Mac developers. Some of you are Unix developers.

Man pages are going to be somewhat new to Mac developers that haven't used Unix a lot. So when you see that to, go ahead and type man to pipe and you'll get some information about it. So pipe and socket are ways of creating a file descriptor. Socket can be used to create interprocess file descriptors. That's the AF Unix address family.

And it can also be used to create TCP IP pipes so that logically you're just writing to a file descriptor, but you're really going out over a network and talking to another site. Now the important thing about file descriptors is that by default they're going to block. If you do a read and there's no data available, your process is now blocked waiting for that read to complete. So if you have multiple file descriptors that you want to read from, you don't want to do that.

You don't want to have three threads all blocked and read. You want to use a function called select. This lets you say here are a bunch of file descriptors that I want to know when data becomes available or when their state changes. Select takes that array and it blocks and then returns when there is data available. Excuse me.

[Transcript missing]

Well, I'll talk about it more in a few slides, but basically it's a way of presenting data to other applications. And typically, you don't want that data to be changing while other applications are using it. I'll talk about that in a couple slides. Pipes and sockets are the most portable interprocess communication mechanism on our platform, as long as most portable means other Unixes. If you're porting a Linux application, chances are if it's using pipes and sockets, it's just going to work out of the box. And that's fine. There's nothing wrong with that.

But it isn't well supported by the Mac OS X application frameworks because your application, especially applications with UI, are receiving events from multiple sources. Events are coming in from the Windows server. Events are coming in from IOCit. You need to be able to capture all these events in a single place. That's what the run loop is for.

So if you're trying to read data out of a file descriptor, you're going to want to wrap it in something that has some affinity for it. It's going to allow the run loop to do its job of demultiplexing several incoming messages and sending them to your application, letting your application deal with it. CFStream, part of the core foundation network, does this by sort of emulating select. It's an implementation detail how that works.

But the upshot is you can use a CFStream wrapped around a file descriptor and receive notifications when data is available. And that's a really good idea if you're trying to do that. You can use a file descriptor based I/O within a higher level application. So pipes and sockets aren't terribly efficient for large transfers of data, arbitrary data, because every byte that gets written gets read out the other side.

What you see then is just a linear time to send a message, depending on the message size. So the best use for pipes and sockets that you're going to see is reading output from a forked command. When you call fork in your application, the child process inherits your file descriptors. So the child process can then start reading, and you can just write bytes, and the child process reads and communicates back and forth that way.

That's typically how you're going to see --

[Transcript missing]

So shared memory is, you know, can be a great way of sharing memory between applications. But the problem here is that typically you're going to have one person performing updates to that memory and other people reading it. When one of the other people reads from that memory, the most it can be guaranteed it read atomically is 32 bits.

So if application A has performed some operation and built a certificate or something that it wants to share among applications, it has to somehow flag that to another application that all that memory can be read and that the other application was able to read all that memory without it changing underneath. So that's why I say it requires some sort of external synchronization.

That could either be a checksum on the memory that was read or perhaps using a file descriptor just saying, okay, the data is available. Tell me when you're done reading it. Then I can unlock this memory and keep going. So the best use for shared memory, then, is going to be to share work that you've done among several other applications.

So there's a new Unix API in Mac OS Tim Panther. This is the Notify API. And what it allows you to do is to subscribe for notifications that something happened. Now, what that something is is entirely up to the notification that's being defined. The notification has no payload associated with it. It's just an indication that something happened. Because of this, or regardless of this, notifications can be coalesced.

If 10 things happen, you may only hear about one of them because all these notifications will be coalesced into one event delivered to your application. Your application subscribes to notifications. It can either pull for them, or it can listen on a file descriptor or receive them via a Mach message. And one way to think about this, it's a better signal. Signal is traditionally how you tell a Unix project. You can either go away, you're dead now, or reread your preferences file.

That would be SIG HUP a lot of times. The problem with signal is you have no control over when your process is going to receive a signal. So when people in the past have used signal, they might want to say, oh, well, you know, I got my signal. I'm going to use some application API. You can't really do that. It's like interrupt time at Mac OS 9. You don't know the state of all the frameworks. So notify.

That would be a better way to architect your processes so that you have some control over where events come in, these specific one-off notifications. So here's a code sample. Okay, it's good and readable. This is how the client is going to register to receive notifications. There's a new call, notify register file descriptor. And you give it the name of the notification you want to subscribe to. In this case, it's handing back a file descriptor that I'm going to block and read.

I already said you don't want to do that, but this is a sample. It also hands back a token that represents that notification so that we can delete it later. Then I'm just going to read from this, and my read is going to block until the notification actually is sent by another process. Notifications.

How do I want to say this? Later on, we're going to see that there are different partition spaces for applications to run in. Notifications are global across all of those. So the server, which is typically running in another process, just says, notify post. Anybody who subscribed to that notification using any of file descriptor, Mach, port delivery,

[Transcript missing]

Back to the architecture diagram. The next thing I want to talk about is Mach Messaging itself.

It would be the subject of a much longer talk to discuss everything about Mach Messaging. So what I'd like to have happen is for us to understand some of the terms that we use when we talk about Mach Messaging so that when you run into it, you're not startled or you're able to work with code that uses Mach Messaging.

Steve Zellers So the first thing is that Mach Messaging allows you to implement servers. Servers exist all over the system. The Windows server, it's even got server in the name, is responsible for handling user events and sending them to your application. Steve Zellers In effect, your application is a server because it receives events and performs some action based on them. Steve Zellers So servers exist in your process, in other processes, and in the kernel. The kernel provides some servers for doing things like getting the time of day. for example, could be a server.

When you send a Mach Message, these are some of the terms that you need to know, need to understand what it is that you're sending into the kernel. Mach Message itself is the name of the kernel trap. This is the subroutine, it looks like, to an application developer that you call to send a message and actually to receive a reply. It's the same trap.

Mach Message Header T is the name of a structure that contains the actual Mach Message data. It's a variable length structure. It's got a header that sort of defines the size of the structure, and then they're going to be in the variable length part. There's what are called descriptors for out-of-band memory regions and Mach ports. So Mach ports, you're going to see these all over the place.

And when you get down to it, it seems like everything is a Mach port. Tasks seem to be a Mach port. Threads seem to be a Mach port. So when you send a Mach Message, you send it to a Mach port. Ports have writes associated with them. You can have a receive write on a port. That means you're allowed to receive data. Somebody else has a send write on that port. That person can send data. You can receive it. There's also a send once write, and this is used when you send somebody.

An event, and you want a reply back. You can send them a send once write on a port. They're allowed to send you exactly one message back on that port. You receive it, and you deal with the reply. This is kind of a security thing. You don't want people just sort of blasting messages out to ports that they don't have rights to do so on.

There's also something called a port set, which is itself, it looks like a port. A port set is effectively the same as a select call. Actually, it's an FD set, which is the parameter to a select call. You can select multiple ports into a port set, and then with one Mach Message call, monitor all those ports for an event to come in.

What's interesting about that is the run loop in CF run loop does that for you. So if you use the core foundation wrappers on top of Mach Ports, you'll get the behavior of an entire selection of ports being received upon at once, and that's a really good thing because you don't have to have 10 people blocked in Mach Message.

Out-of-band memory is a really important concept, and it basically lets you send a message to somebody else sharing your memory. But when the receiver gets the memory, he doesn't strictly own it. He's allowed to read from it, if you give him that option, and he's allowed to touch it. But if he touches it, he's going to get a copy of the memory. Now, a lot of subsystems are built on top of this, because typically when you message data to someone, they're going to read from the data, but they're not necessarily going to write to it.

So that's an out-of-band memory descriptor. It provides virtual copies of memory between applications. You're given the option of marking it read-only or copy-on-write when it gets messaged to the other side. One thing to note is that the memory that you send should be page-aligned. The reason for this is that if you message memory that's not page-aligned, then the kernel is going to copy the first and last page, make new pages for those, and zero out the stuff that isn't actually explicitly sent. So ideally, you're going to send multiple pages of page-aligned data. That's not always possible.

And sometimes you'll wind up with pages copied on either side. This is a process called box-carring. You don't want, you know, if the block beforehand is a pointer that's got, you know, the user's password, you don't want that going to another application. So by aligning the memory, you'll get much better performance through Mach Message.

You need to plan for memory management. When you receive a Mach message that has out-of-band memory in it, you need to deallocate that memory. You own that memory, or you think that you own it, but the kernel will take care of copying it if necessary. But you need to deallocate it, otherwise it will just leak and consume vast resources.

So VM deallocate is the call you use to do that. Whatever the address is that came into you, use VM deallocate to deallocate it later. You don't have to do that right away. You can hang on to that memory for as long as you want. Just be aware that you own it and you still need to get rid of it.

Okay, so a Mach Message structure, here's gonna be an example Mach Message. Basically, it's a green box with, at the very top, there's a Mach Message header T. This is where you specify the message ID. This is how the receiver of the message is going to demultiplex the message and decide what to do with it. The size of the whole green box and the reply port, the port that is going to get sent back from the server that responds to this.

Then this is all variable length data after it. But the first thing after the header has got to be the descriptor count, which could be zero. The descriptor count in this case is going to be two. We're going to have two descriptors following the Mach Message header T. The first descriptor, we're going to send an out-of-line memory region.

These descriptors are fixed length structures. You fill in attributes of them and just append them to the end of the message. For an out-of-line memory region, it's just the address and the size. While it would be nice if the address were page-aligned, if it's not, the Mach Message trap will take care of that for you. There's also the attributes in that memory descriptor about how to deal with the memory, whether or not the memory should be copy-on-write or read-only. the server process.

Then in this example, I'm also going to send a Mach port. This might be the case where I'm telling the server, "Here's some data. Process this out-of-line data, and when you're done doing that work, you can contact me on this Mach port that the work is complete." After that, you can have whatever data you want. So what you're going to see a lot of times is that a Mach message typically will have zero descriptors, and it will have a whole bunch of just data sitting after the header. And that's really the body of the message.

Core foundation in the CFMochPort de-muxing routines, it allocates a certain amount of space on the stack to receive a Mach message. But if the message is too big, it allocates a larger buffer and then does the work again. So, ideally, you want to keep your message size small.

It's kind of an implementation detail of CFMochPort. But it's interesting that if the message that you're receiving is too big for the buffer you supplied, you get a chance to allocate a larger buffer and receive the message again. Obviously, that's less efficient. So you want to make sure if you're receiving the event yourself that it is the minimum size, or sorry, the maximum size of all the messages that you're likely to receive.

So how do you find somebody to talk to with a Mach Message? This is probably one of the most important things now because of fast user switching. You have to discover Mach services, and you do this through a mechanism called the Bootstrap Server. This associates a symbolic name with a server. There are several Bootstrap Servers on the system. When the system is starting up, there's the primordial Bootstrap Server. This is where demons live, where we call them startup items in our new lingua.

When a server that is running in a startup item starts up or it registers itself with a bootstrap server, it is then visible to other bootstrap servers that get created after the fact. So if you're running within an application context, you're a Netscape plug-in inside of Safari, you're allowed to see services vended by daemons. Let me go on to the next slide. This will show it a little better.

Daemons live in this little world out here. User A applications and User B applications are allowed to see into the daemon world. Daemons can't see into, they cannot look up services that are vended by processes within the user's bootstrap ports, within these bootstrap ports that were created for the user. This is why you cannot send an Apple event in Jaguar from a daemon or an Apache module to iTunes, for example. Um, This is done for security reasons. We don't want just arbitrary code running in the bootstrap server world to be able to manipulate user land applications.

So, how do you write a Mach Message? How do you decide what the structure of a Mach Message is going to be? You can do it by yourself. You can roll your own. That's a real pain. Or you can use MIG, the Mach Interface Generator. This is an IDL for describing the structure of a Mach Message. It handles marshaling parameters of scalar types and descriptor types into a Mach Message, sending it to a server, and processing the response.

So, giving it back to you in a way that fits in easily with your application. It associates a unique ID with each message, and it provides a demuxing routine that takes care of switching on that ID and calling your server implementation. So, here's an example that you'll see a lot, which is a check-in on a service.

Where this would be used is a daemon that wants to provide a service for a user application. The user application will check in with the daemon and say, "I'm here. I'm ready to do some work for you. Here's my name." That's in ID. In, obviously, is saying this is an input parameter for the server.

ID is just the variable name. And in this case, the parameter is a C string. The server, then, is going to provide a Mach port. In this case, let's see, a Mach port that the user can use to send a message. that the client can then send additional requests upon.

So when requests come in on this Mach port to the server, he's gonna be able to say, oh, I know what port that is. I know what client checked in and I handed that port out to. So I know like what state I had kept for that application previously.

What gets generated from this is client glue. If you look in user include Mach, you're going to see a bunch of defs files and also the generated header files for those Mach services. And they basically look like this. It's just C glue code. The first parameter is the server port. This was discovered via Bootstrap lookup or handed back from some other routine. Then just a char star ID. Meg will take care of putting that into the body of a Mach message. And then as an out parameter, the client port that the server is going to provide.

It also generates this stub routine for the server to implement. Now, if somebody hands you a defs file and says, call my service, you don't need the server glue. But if you're going to implement the server, you have to basically provide the body of this routine. And toward the end in the demo, I'll show what that looks like.

So how do you handle a Mach Message? You get the data, you cast it to a Mach Message header T, and you switch on the message ID. But that's kind of a pain. So there are two utility routines that you should know about. Mach Message Server and Mach Message Server Once.

Both of these take the Mach port that you registered with the Bootstrap server and the DMUX routine that Meg generated. And in the first case, Mach Message Server will sit there in a loop, accepting messages, calling your DMUX routine, sending the reply. Mach Message Server Once does that, but just once. Sending replies is kind of difficult, because you have to figure out first whether a reply is warranted, and second, you have to deal with, well, the DMUX routine returned a Mach Message header of an appropriate size.

You have to worry about what that size was and deal with errors that occur in sending it. What I would recommend is if you need to send a reply directly, to go to the Darwin sources and grab the source code to Mach Message Server and just sort of understand that, copy and paste it into your stuff with appropriate license, whatever.

Anyway. Core Foundation has support for Mach. And it has this in a couple of different ways, but the most important are CF Mach port, which wraps around a port, optionally it creates it for you, and it gives you a callback routine. So when you register a CF Mach port run loop source with your run loop, and an event comes in, it's going to call your callback routine and say, hey, here's a Mach message.

But like the previous slide alluded to, it doesn't send the reply for you. In that case, you're going to have to set up the reply structure yourself and send it. And again, going back to the Mach Message Server routine to understand how to send that reply, if you want to deal with Mach messages via CF Mach port generated by Meg, that's probably the right way to do it. It gives you an invalidation mechanism. This is really important because say somebody checks in with you and says, I'm going to -- I want you to do some work.

Call me back on this Mach port. And then that application crashes or the user quits it. The invalidation mechanism lets you know that the Mach port you're working with, you can't do anything with. It's now invalid. And if you're doing work on behalf of that Mach port, you could stop now because nobody's ever going to hear about it.

CFMessagePort sits on top of CFMockPort, and it sits on top of the Bootstrap server, and it lets you create, basically, a Mach server that accepts a blob of data. This is a really handy thing to do. If you just need to send data between two applications, CFMessagePort is probably the right way to do it. It has the restrictions of the Bootstrap port world, though. So between sessions, it's not going to work.

It'll work. A daemon can advertise a server, and clients inside the user application will be able to send to it. But userland applications, or per-user applications, I should say, aren't going to be reachable from other user sessions or from daemons. Now, what's the performance of all these different lower-level APIs? Well, pipe is basically linear. These are the numbers that came out of the... the test app I wrote that sent a 5-meg blob to a process that was forked. MockMessage, on the other hand, basically is completely flat. It was kind of independent of the message size.

Obviously, the memory was allocated all beforehand and then sent along. So MockMessage seemed to be just a great way to message arbitrarily large blobs of data. Turns out, and I don't even know if that's visible, CFMessage is pretty much just as efficient. It's doing the Mach port hand-rolled MockMessage directly. That's why I would really recommend it, especially if you don't need to do inter-session communication.

Okay, so let's go up a layer, talk about Apple Events. First, let's define what we mean by Apple Events. It's kind of weird that the word Apple Events means so many different things. It's like calling every product .NET. So Apple Events came around in '91, I think, was the first time I heard about them.

AppleEvents is the technology and the name of the framework. It's also the name of a type in the framework, an AppleEvent descriptor record. And it's also, you know, AppleEvents, which is what we're all at today. This is an AppleEvent. It's under the application services umbrella. What that means is it doesn't necessarily need a process manager connection. And if you use AppleEvents and you're linking against application services, you can get by without doing any user interaction. User interaction is sort of at the Carbon or Cocoa layer.

But if you're linking against application services, you can get by with writing a daemon or writing a background process that uses AppleEvents. There is a lot of predefined Apple Events that you can implement. Back in the old days, this was called the required for Apple Events. Print, quit, save, something else.

So there are a lot of Apple Events that you could implement. The scripting sessions, which I guess the AppleScript session was yesterday, would have been the place to sort of understand how to implement scripting behaviors for your application in terms of factoring your applications for scriptability. I would go back and review slides from previous years' WWDC for that. A couple of things to know about Apple Events.

[Transcript missing]

So when would you need to use AppleEvents? Well, between applications. So there are a bunch of applications on your platform, and some of them do certain things, like, you know, flow text around an object or print with certain attributes. You can leverage those applications because they accept AppleEvents that allow you to program that behavior.

So between applications, this is a great thing. So how do you discover -- how do you discover An application that you can talk to. In Jaguar and previous, it basically was bound to the process manager. Applications would check in with the process manager, and the Apple event framework would talk to the process manager to discover, okay, you know, here's MACS. You're trying to talk to an application whose signature is MACS. Whoever that is, that's great. That happens to be the finder.

You could also, if you launched an application and you're giving back its PID or process serial number, you could target an application by PID and process serial number. In Panther, we've added a new Apple event addressing type application bundle ID. This basically replaces signature. It's better to send to com.apple.finder than to send to MACS, if you can get away with targeting Panther directly.

But in Jaguar, you had to be a client of the process manager to discover applications, and you couldn't send Apple events between sessions. Well, you still can't send, in Panther, Apple events between sessions. We don't really allow that. But we enforce a UID-based restriction. Between user sessions, applications owned by user A can send to applications owned by user B. Sorry. Applications owned by user A can only send to applications owned by user A. Applications.

In B are restricted to sending to B. The exception to this is that applications owned by root, and this includes daemons running in the startup item space, are allowed to send to any other application on the system that's registered for Apple events. This is a very commonly requested feature for people that are trying to write Apache plugins. They wanted to be able to script applications, and now they'll be able to.

So Apple Events are well supported, very well supported, by both the Carbon and Cocoa frameworks and by third party frameworks that are built on top of Carbon and Cocoa. Events are received on the run loop and dispatched to handlers that you have registered within your application space. Events that you send are usually sent synchronously. You send, and then you block for a reply. That's sort of the older model.

There is a preferred way to do that, which is to wait for a queued reply. You send the event waiting for a queued reply, go off and do other things, maybe update your UI, put up a sheet, let other windows in your application work. And then when the reply comes in from the server, it's delivered to you as just another event that you then associate with the original event that you sent.

On the server side, when you receive an event, you can suspend it and then resume it at a later time after you've done the work that the event originally requested. Thinking about suspension and resumption is that these live in the Carbon framework layer and may necessitate user interface.

It's kind of a better model if you can just deal with the event right away or set up the communications so that the original event that came in had information about how to send a reply later, had some state with it that said, "Okay, when you're done with the work, send me an event back." Sort of manage that asynchronous reply mechanism yourself. So scripting is not something that comes for free in the Carbon Frameworks. You need to implement an AETE resource. This describes the classes, the properties, or IVARs of the class, if you will, and verbs that allow you to operate on the class.

It lives in this resource, and then the scripting implementations get the resource and are able to present UI to the user to allow the user to pull out the things that they care about and plug them together in interesting ways. Cocoa, on the other hand, because the application framework has so much metadata available and so much ability to introspect upon the framework and the application running, allows you to use a textual description of what your application can do. That's a script suite file, and it will actually generate the AET resource for you on the fly. This is, again, something that we talk about at length, and probably going back over previous years' WWDC slides, it's better to understand how to implement the scripting.

Okay, so what does an Apple event actually look like? It's a structure. It's an 8-byte structure. It's got a 4-byte data type, which is a 4-character code, and then a data handle. In classic Mac OS 9 land, that was a real handle, and if somebody sent you data, you were expected to dereference it, cast it to whatever you wanted, and then just work with it.

But when we did the Carbon implementation, we took the opportunity to say, it's an opaque data handle. Unfortunately, we didn't really enforce that restriction, and so a lot of people ported their apps to Carbon and kept looking in the handle. So when we changed that for Jaguar, because we always said we were going to change it, a lot of apps broke, so we have sort of a hacky workaround in place, but it's not perfect, and you're much better off using accessories to get into the data. Speaking of which, there are two new APIs in Jaguar.

There's an API called AECreateDesk from External Pointer and AECetDeskDataRange. The first of these lets you say, okay, here's an arbitrarily large blob of memory that I'd like to send to another application, but I don't want to copy it into the Apple Event Manager. I promise not to modify it anytime soon. It's an image or something that I got, and I want to send it to some other application. Using AECreateDesk from External Pointer, the Apple Event Manager will tell you when it's done with it, when you dispose of the descriptor.

It will decrement a reference count and say, okay, that descriptor is done. You can free it now. That will actually avoid copying the memory into the Apple Event Manager, and because the Apple Event Manager is built on Mach messages, it'll avoid copying all that data into the receiving application's address space.

So that's a really good way to improve performance for large messages. AECetDeskDataRange does basically what that says. In the past, you could look at the data handle and offset some offset and pull out a field. If you pulled out of a structure, AECetDeskDataRange lets you do that without having to copy the whole buffer to a local buffer.

So that's sort of the data type in Apple Events. There are also lists and records, which are lists of those data types, or basically dictionaries, where the key to the dictionary is another four-byte code. A lot of the existing Apple Events that you can send and receive have well-established parameter orders. So the open Apple Event, for example, has a list of aliases that you're supposed to open.

It is opaque. Let's see. So an Apple event record then is sort of two records slammed together, two AE records slammed together. One is the parameters for the event. That's what people typically are going to deal with. And the other is the attributes of the event, which is where the event came from, what the event ID and class is.

It's useful to think of it as sort of two records slammed together instead of one sort of mysterious record. So when you message data to another, when you perform a send, the data is copied into the other application's address space. If it is optimizable, then it's optimized to be a virtual memory copy. Okay.

AppleEvents can be sent between machines. We do this in 10 using the type application URL signature-- application signature targeting type thing-- targeted address type. So what that looks like, and this is a horrible, ugly thing, but EPBC is the scheme for remote Apple events. You can optionally add the username and password directly to the outgoing message. Then the host, which is a required parameter, and the app name, which is a required parameter.

And then now we've added for Panther, optionally, the user ID and the process ID. If those are present, those will be used in order to direct the Apple event that comes in to a specific session. So in order to do multi-session support, in order to target a specific session, you have to use the UID and PID. Otherwise, it goes to the, I guess, the current console user.

Panther also adds a new API, AEList Remote Processes. This provides the data of what processes are available on a given host. This was something we had back in AppleTalk days. Sorry. This is something we had back in nine days using remote Apple events over IP. You would specify the name of the server that you want to connect to, and it would list the processes, and then you could click on one and bring it back. This doesn't implement the UI behavior. This is just the data behavior. You'll have to implement the UI yourself, but it should be trivial to do that.

AD servers also advertise themselves on Rendezvous. So, yes, we're back to the AppleTalk days of being able to browse machines on your network that have program linking turned on. So, if you need to do Rendezvous lookups, you can use this eppc.tcp thing. All right. And it's actually pretty trivial to use the Rendezvous APIs to discover, you know, okay, these servers have these applications available.

So, talk about the performance of AppleEvents for a second. There's the CFMessage performance, and the AppleEvent performance is, you know, it's good. It's right there because it's using the same underlying mechanisms that the Mach layer provides. In comparison, the AppleEvent performance in Puma was significantly worse because it was copying the data between address spaces every time.

It was basically linear with message size. So, the AppleEvent performance in Panther is actually 50% better than Jaguar as far as number of messages it could send, and that doesn't really show up in this chart at all. Okay, I'm gonna talk briefly about Cocoa distributed objects, because this is another topic for a much larger session.

Cocoa distributed objects allow you to implement a protocol that says, Here's an object. It has these behaviors. You can now look at it from your process in another address space across the network, make method calls upon it or send it messages, and whatever parameters you pass to it, I will marshal those parameters into a stream and send it over a wire.

It allows you to provide services for other applications, like the services menu uses distributed objects in order to implement a lot of its behaviors. So what you get as a client of distributed objects is an object that looks like an object that you can work with, but method calls to it, messages to it, actually go over a wire and talk to another process that receives those messages, interprets them, and performs an action on a local object. So between processes, it's going to use Mach Messages typically to perform this. I guess there's also an option to use pipes directly. Intermachine, it's going to use sockets, TCP/IP, and it will automatically register your object for discovery with Rendezvous.

So some of the objects that you'll see when we talk about Distributed objects. There's NSPort. This is the object that is responsible for serializing a request given a particular transport. It's sort of created with the intention of I'm going over a Mach message, so how should I serialize this request? There's Connection, which allows you to discover objects. It's sort of how objects are published from your server application. It says bound to run loop, but actually the port is bound to the run loop.

That's sort of the thing that receives the message is actually bound to the run loop. And then there's the NSProxy protocol, which has actually got two concrete implementations. In a client application, you're going to look at the NSDistantObject object. It takes care of using the port and the connection to serialize the request. On the server side, it's called an NSProtocolChecker. That ensures that the message that the server receives adheres to what the server really intends to publish.

Now, this is sort of the flow of events through distributed objects. And I guess it's pretty much self-explanatory. Once an object is vended to a client, he just makes method calls to it. It flows down that pipe across ports between processes or machine boundaries, and then ultimately results in the vended object.

So to finish talking about distributed objects, it allows you to vend an Objective-C object for other applications to see. One thing to note is that messages that are sent to Objective-C objects are synchronous. You block for a reply. If something goes wrong, you could be blocked for a long time. So it's possible to set a timeout on a reply, and that's probably a good thing to do. You set a timeout on the connection object itself.

And it's not as efficient as something you could come up with yourself. If you need to send a particular piece of data to another application, and you need a very high-performance way of doing that, you could probably come up with your own messaging scheme, leveraged on top of AppleEvents, CFMessagePort, or MachPorts, that's going to be more efficient than just using distributed objects.

On the other hand, there are a lot of existing distributed objects conversations taking place, so it may make more sense to use one of those. It's a performance versus ability to leverage what the framework provides. It's a decision you're going to have to make. Distributed objects vended inter-process have the same restrictions as AppleEvents. Well, yeah, let's see. It has the same restrictions as CFMessagePort, in that they're isolated within one session. Discoverability is isolated to within one bootstrap space.

Okay, so Web Services. In Puma, we introduced Web Service Support in the Apple Event Manager, of all places. And what this did was serialized Apple Event records and container types into XML and send it along to a server. In Jaguar, we moved this down a level and made it applicable to Objective-C data types and core foundation data types for the scalar values and container types. It supports XMLRPC and SOAP and still supports Apple Events and AppleScript by virtue of supporting Apple Events.

So it's possible to write an AppleScript application that talks to Web Services out on the Internet, and it just reads like any message. It's not a message called to any other application. You get back a dictionary with some high-level constructs in it. You can look at the data as if it were just an Apple Event record. In Panther, we've added a message parsing API, so it's now possible to write servers in Web Services.

Okay, so how do you invoke a web service? The thing you want to look for is called a WS Method Invocation Ref. This is the point that, this is an object that handles serialization of outgoing requests. It's constructed with a schema and a URL. The schema specifies XMLRPC or so. The URL is, of course, the endpoint that you're going to do the HTTP post to get to.

You then set parameters on it. You have to tell it the dictionary, which is the parameter list. It's prepositional. So you have to say parameter one, value three, parameter two, value hello, and then the order that those parameters should be serialized. The reason you have to do both is because depending on the scheme you use, it may be necessary for the serializer to emit things in a particular order. XMLRPC is positional. It doesn't care about the name of the parameter. It cares about the positions, whereas SOAP tends to be prepositional, where it cares about the name but not necessarily the order.

However, there are some SOAP servers that also require that the order be as specified in the originating document. So adding the order parameter, when you call set parameters, having the order is very important. So then you just invoke it. You can invoke it synchronously, in which case you'll block until the reply comes back from the server. Or you can schedule it on your run loop, and when the reply comes in, you're handed a dictionary.

The dictionary contains either the reply data, the result of this operation, or it could contain a fault with information about what happened, what went wrong with this request. So the reply from a web service invocation is always a dictionary that you have to look into to extract the actual reply that you expect. that you expect.

Okay, so how to write a server. It's actually really simple. The server API isn't really a server. It does not listen on any sockets. All it does is it basically filters a XML document into a CFDictionary, which you then implement the sort of demuxing behavior, pull out the message name and do something based on that.

So the thing to look for in the new API is WS Protocol Handler Ref. This is created with the schema of XMLRPC or SOAP. You then call copy request dictionary with this thing. You've given it a CFData XML document that you received over the wire, and it gives you back a CFDictionary. The CFDictionary has the method name, and it's got the parameter order and the parameter list, the parameter order that it received.

So you extract the parameters from the list. You do something based upon them, and then you call create reply document. The two parameters to create reply document are the original dictionary passed back by copy request dictionary, because that has sort of meta information about the connection, the name of the message, which might be relevant in forming the reply, and then a CF type, which is the type that you wish to send back to the server.

So you don't have to wrap it. It doesn't have a, it's not prepositional. It's not prepositional at all. It's just the raw CF or NS type. So you send the reply document using whatever server that you're embedding this in, and then you profit, because, I don't know, that's what they always say on Slashdot.

Okay, so there is no real free lunch with Web Services discovery. There are some standards that are standardizing, sort of accreting over in the corner, to allow for discovery of Web Services, but we don't support them yet. Steve Zellers Most of the time, when somebody says, "You need to call my web service," they're going to hand you a textual description of what that web service expects, and that's usually a very easy way to implement it.

You just sort of go, "Okay, parameter name this, should be this value, parameter name this, this value." That's sort of how the XMLRPC community works. Steve Zellers The SOAP community is trying to automate that a lot more, and they have the Web Services Description Language for describing the messages and the methods that are available via Web Services. Steve Zellers We have a tool, WSMakesDubs, that tries to understand WSDL documents and produce glue code that sits on top of the Web Services core framework.

So if you actually view the slides from last year, I run that tool and describe and show how that tool is used to create an application that queries some Web Services, sort of automatically generating the glue code for you. Okay, so I do have a few minutes to go through my demo. And if I could have Demo Machine 2.

There it is. So one of the things that comes up a lot is people want to be able to write Apache plug-ins or Apache modules to talk to applications. So I'm going to use a couple of the technologies that I talked about today to build this application. I've got it built, so I'll just run through it and then I'll show you the code. So the first thing is that I have an Objective-C application that uses Web Services to talk to a host over Web Services. So someplace there is a Web Services server, and it is talking to iTunes over Apple Events.

So here's my little application. I get a little drawer here. I can open up these wedges, and it's going to send a request to the server each time I open a wedge to get the-- who created the web. the song, the title of the track, the name of the artist. So go ahead and everybody likes polka. Go ahead and play a little polka music, yeah.

Turn it down because it's annoying. And so as you can see, I've got a little progress bar here at the bottom that updates. It's actually doing a poll to get that once every second, it fires a timer off, sends a request off through Apache, and tries to sort of update the title and the time.

I click stop, and oh no, an exception occurred. This is a good opportunity to first mute the sound and then go into the script that implements all this. So the way the demo is put together-- and I'm actually going to make the demo available through DTS because it does show a lot of these technologies-- is that there is an Apple script application that receives the XMLRPC request and does something based on it. it.

So here's a whole bunch of Apple scripts. Play track. This is probably the easiest thing to understand. When my tool said play track and gave the ID of the track that I wanted to play, it talked to Apache. Apache talked to a daemon that sent the Apple event onto this script, which then sent the Apple event to iTunes to play the song. So I got an exception because I had commented this out.

[Transcript missing]

Polka is still playing in the background. Now I can stop it, which I'm sure everybody is relieved about. Because I'm an uber geek, I really don't want to take my hands off the home row. I have a Perl script. that uses one of the Perl XMLRPC things to call my web service running in Apache. And I'm doing this to show what the message actually looks like. So if I'm going to get the genres, it's a simple call XMLRPC Perl subroutine.

And the name of the XMLRPC method happens to be the bundle identifier of that AppleScript application followed by the name of the subroutine. So the architecture underneath that's glued to Apache is actually general. It will allow you to send a AppleScript do subroutine command to any application that implements AppleScript attachability. So just we can demo that real quick.

So I want to go to Books and Spoken. And I always like this song. It's a good song. Let's go ahead and play Polka Power. Oh, it's all about the Pentiums. OK. So anyway, you can see that the script is just working the same way as an application. It could be running on Linux. It could be running on Windows. That's really what Web Services is all about, having a heterogeneous environment. So I still have a couple minutes. Just going to show you some of the source code that does all this stuff.

Where to start? Let's start with the defs file for the bridge code that runs alongside of Apache. So the thing is, you don't want Apache to be running as root. You never want that. So instead, Apache looks up a daemon that is running as root that is allowed to send Apple events into this user's context.

The daemon implements this single Mach message. It takes an input XML request and an output, and it outputs the XML RPC response. The implementation of that, this is what Mig generated for me, this doMessage server-side stub. You can see that the input parameters are the address of the blob of data and the size of the blob of data, and then I'm to supply to it the size of the blob of data that I'm returning. At its count, Mig will actually take care of sending that response for me.

This is where I'm looking up the name of the daemon. So the Apache module just gets instantiated by Apache when I go to a specific URL, looks up the name of the daemon. It has already gotten the data out of the HTTP post. Then it calls my daemon right here. I'm using the CFData stuff to sort of wrap the data that comes in.

And then it gets its response, and it forwards it back out Apache. And Apache deals with setting up the HTTP connection, accepting it, dealing with errors, et cetera. Okay, so like I said, we're going to put that on the developer website so that people can, you know, write their own daemons doing all this stuff. I forgot my clicker. And with that, if we could have the slides back.

Can I get slides? Okay. So that was the demo. Oh, there was a nice build on that that I forgot to do last time. So showing that you can go and use all these technologies in various places. This is really how you would architect such a system. You would use web services to accept incoming events from a heterogeneous environment and use XMLRPC and the parsing APIs to figure out what the data is and use Apple Events to talk to an application that's providing some service for you.

All right, so let's summarize what we've talked about today. It was a lot of different technologies. Pipes and sockets, this is typically for legacy code or for tools or for things that need stream data between processes. Notify, this is great for broadcasting a state change, and if you're writing new code where you might think you're going to use Signal, please use Notify instead. You'll much prefer it.

Mach Message allows you to write really complex servers, and it's very powerful, and it's really at the core of what ROS does, and the best reference for that is probably the source code in Darwin. CFMessagePort is a great way of doing blobs of data messaging between applications. It's very efficient, and it works really well with all our frameworks.

Apple Events, anytime you can add Apple Events to your application to make it more scriptable. The iTunes guys didn't know that I was going to have a Perl script talking to their applications. They didn't need to know. They just had to implement the scriptability, and then I can come along and leverage the work that they did.

And then Objective-C distributed objects, if you need to participate in an existing conversation or if you have a new application that wants to be able to create a new conversation, Objective-C distributed objects is a fine way to do that. Okay, so we wrap up. Here are a couple of sessions I want to point you at.

Building applications for managed network environments. A lot of what is applicable to networking is applicable to interprocess communication. So, sort of understanding networking both in that session and in CFNetwork in depth, which I also highly recommend, you're going to get a much better understanding about how to write applications that don't block when they don't need to and perform really well on our platform. Writing threaded applications on Mac OS X.

Threads are really just kind of like another process. Sort of the same things are going to apply to sending a message to another thread. And then stump the experts tonight, which we'll all go to. So, here are the names of some of the technology people that you might want to contact.

The important thing here is the URL at the bottom where all this information ends up being anyway. So, I don't expect people to actually be able to write all these things down. Reference library. This should be interesting because probably the best book on Unix messaging is going to be the original Unix book by Kernighan and Pike. There's a URL you can go to to order that book. The Mach documentation, I don't think Apple produces any. The best documentation I found are some PostScript files on an FTP server at CMU. So, that's where you want to go to learn how to write Mach servers.

Inside Macintosh IEC talks about Apple Events. It's a little dated because, you know, it was written 12 years ago, but it's all still applicable. So if you want to understand Apple Events better, you should go there. And then the Web Services Documentation Online talks about the Jaguar APIs. There is no documentation up on the new server APIs yet. It's sort of self-documented by the header file, and it's pretty easy.