Mac OS X Internals: A Systems Approach

9.17. Core Foundation IPC

Core Foundation is an important Mac OS X framework that provides fundamental data types and several essential services, including a variety of IPC mechanisms.

9.17.1. Notifications

The Core Foundation (CF) framework provides the CFNotificationCenter data type, which, along with its associated functions, can be used for sending and receiving intraprocess and interprocess notifications. A CF notification is a message consisting of the following three elements:

  • A notification name (a CFStringRef), which must be non-NULL.

  • An object identifier, which can either be NULL or point to a value that identifies the object that posted the notification. For distributed (interprocess) notifications, the identifier must be a string (a CFStringRef).

  • A dictionary, which can either be NULL or contain arbitrary information that further describes the notification. For distributed notifications, the dictionary can contain only property list objects.

The CF notification API supports the following three types of notification centers (a process can have at most one of each type): local center, distributed center, and Darwin notify center. The local center is process-local, and the other two are for distributed notifications. The distributed center provides access to distnoted (see Section 9.16.1), whereas the Darwin notify center provides access to notifyd (see Section 9.16.2). A reference to any of these centers is obtained by calling the appropriate CFNotificationCenterGet* function, which returns a CFNotificationCenterRef data type.

// distributed notification center (/usr/sbin/notifyd) CFNotificationCenterRef CFNotificationCenterGetDarwinNotifyCenter(void); // distributed notification center (/usr/sbin/distnoted) CFNotificationCenterRef CFNotificationCenterGetDistributedCenter(void); // process-local notification center CFNotificationCenterRef CFNotificationCenterGetLocalCenter(void);

Once you have a reference to a notification center, you can add an observer, remove an observer, or post notifications. The same set of functions is used to perform these operations regardless of the notification center type. Figure 962 shows a program that posts notifications to the distributed center.

Figure 962. A program for posting Core Foundation distributed notifications

// CFNotificationPoster.c #include <CoreFoundation/CoreFoundation.h> #define PROGNAME "cfposter" int main(int argc, char **argv) { CFStringRef name, object; CFNotificationCenterRef distributedCenter; CFStringEncoding encoding = kCFStringEncodingASCII; if (argc != 3) { fprintf(stderr, "usage: %s <name string> <value string>\n", PROGNAME); exit(1); } name = CFStringCreateWithCString(kCFAllocatorDefault, argv[1], encoding); object = CFStringCreateWithCString(kCFAllocatorDefault, argv[2], encoding); distributedCenter = CFNotificationCenterGetDistributedCenter(); CFNotificationCenterPostNotification( distributedCenter, // the notification center to use name, // name of the notification to post object, // optional object identifier NULL, // optional dictionary of "user" information false); // deliver immediately (if true) or respect the // suspension behaviors of observers (if false) CFRelease(name); CFRelease(object); exit(0); }

Figure 963 shows a program that registers an observer of all distributed notifications, after which it runs in a loop, printing information about each received notification. When it receives a notification named cancel, it removes the observer and terminates the loop. Note that the observer program uses the concept of a run loop, which is discussed in Section 9.17.2.

Figure 963. A program for observing Core Foundation distributed notifications

// CFNotificationObserver.c #include <CoreFoundation/CoreFoundation.h> void genericCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) { if (!CFStringCompare(name, CFSTR("cancel"), kCFCompareCaseInsensitive)) { CFNotificationCenterRemoveObserver(center, observer, NULL, NULL); CFRunLoopStop(CFRunLoopGetCurrent()); } printf("Received notification ==>\n"); CFShow(center), CFShow(name), CFShow(object), CFShow(userInfo); } int main(void) { CFNotificationCenterRef distributedCenter; CFStringRef observer = CFSTR("A CF Observer"); distributedCenter = CFNotificationCenterGetDistributedCenter(); CFNotificationCenterAddObserver( distributedCenter, // the notification center to use observer, // an arbitrary observer-identifier genericCallback, // callback to call when a notification is posted NULL, // optional notification name to filter notifications NULL, // optional object identifier to filter notifications CFNotificationSuspensionBehaviorDrop); // suspension behavior CFRunLoopRun(); // not reached exit(0); }

Let us now test the poster and observer programs from Figures 962 and 963, respectively.

$ gcc -Wall -o cfposter CFNotificationPoster.c -framework CoreFoundation $ gcc -Wall -o cfobserver CFNotificationObserver.c -framework CoreFoundation $ ./cfobserver # another shell prompt $ ./cfposter system mach Received notification ==> <CFNotificationCenter 0x300980 [0xa0728150]> system mach (null) $ ./cfposter cancel junk Received notification ==> <CFNotificationCenter 0x300980 [0xa0728150]> cancel junk (null) $

As we noted in Section 9.16.1, an observer of all distributed notifications will receive notifications from other posters. If you let the cfobserver program run for some time, you will see a variety of notifications being sent by different parts of the operating system.

... Received notification ==> <CFNotificationCenter 0x300980 [0xa0728150]> com.apple.carbon.core.DirectoryNotification /.vol/234881027/244950 (null) ... Received notification ==> <CFNotificationCenter 0x300980 [0xa0728150]> com.apple.screensaver.willstop (null) (null) ... Received notification ==> <CFNotificationCenter 0x300980 [0xa0728150]> com.apple.Cookies.Synced 448 (null) ...

The object identifier 448 in the notification named com.apple. Cookies.Synced is the process ID of the Safari application. The iTunes application is a poster of interesting notifications that contain dictionaries with detailed song informationsuch as details of a new song as it starts to play.

9.17.2. The Run Loop

A run loop is an event loop that monitors sources of input to a task, and when an input source becomes ready for processing (i.e., the source has some activity), the run loop dispatches control to all entities that have registered interest in the sources. Examples of such input sources include user-input devices, network connections, timer events, and asynchronous callbacks.

A CFRunLoop is an opaque Core Foundation object that provides the run-loop abstraction. Carbon and Cocoa both use CFRunLoop as a building block to implement higher-level event loops. For example, the NSRunLoop class in Cocoa is implemented atop CFRunLoop.

The following points are noteworthy about run loops.

  • An event-driven application enters its main run loop after initializing.

  • Each thread has exactly one run loop, which is automatically created by Core Foundation. A thread's run loop cannot be created or destroyed programmatically.

  • An input source object is placed into (registered in) a run loop by calling the appropriate CFRunLoopAdd* function for that input source. Then, the run loop is typically run. If there are no events, the run loop blocks. When an event occurs because of an input source, the run loop wakes up and calls any callback functions that may be registered for that source.

  • Run-loop event sources are grouped into sets called modes, where a mode restricts which event sources the run loop will monitor. A run loop can run in several modes. In each mode, a run loop monitors a particular set of objects. Examples of modes include NSModalPanelRunLoopMode (used when waiting for input from a modal panel) and NSEventTrackingRunLoopMode (used in event-tracking loops). The default modekCFRunLoopDefaultModeis used for monitoring objects while the thread is idle.

Figure 964 shows the types of objects that can be placed into a run loop, the functions used for creating the objects, and the functions used for adding them to a run loop.

Figure 964. Creating and adding run-loop input sources

9.17.2.1. Run-Loop Observers

A run-loop observer (CFRunLoopObserver) is a run-loop input source that generates events at one or more specified locations, or activity stages, within the run loop. The stages of interest are specified as the bitwise OR of individual stage identifiers when the observer is created. For example, the kCFRunLoopEntry stage represents the entrance of the run loopit will be hit each time the run loop starts running as a result of either CFRunLoopRun() or CFRunLoopRunInMode().

Note that the term observer is somewhat confusing in that so far we have used the term to refer to entities that receive notifications; here, an observer generates notifications, although it is observing the run loop's activities in doing so.

9.17.2.2. Run-Loop Sources

A run-loop source (CFRunLoopSource) abstracts an underlying source of events, such as a Mach port (CFMachPort), a message port (CFMessagePort), or a network socket (CFSocket). The arrival of a message on one of these communication end points is an asynchronous event that represents input to a run loop. Core Foundation also allows custom input sources to be created.

Given an underlying primitive type supported by Core Foundation as an input source, a CFRunLoopSource object must be created before the source can be added to a run loop. For example, given a CFSocket, the CFSocketCreateRunLoopSource() function returns a reference to a CFRunLoopSource, which can then be added to a run loop using CFRunLoopAddSource().

Let us look at some properties of the input sources provided by Core Foundation.

CFMachPort

A CFMachPort is a wrapper around a native Mach port, but it allows the port to be used only for receiving messagesCore Foundation does not provide a function for sending messages. However, CFMachPortGetPort() retrieves the underlying native Mach port, which can then be used with the Mach APIs to send messages. Conversely, CFMachPortCreateWithPort() creates a CFMachPort from an existing native Mach port. If an existing port is not being used, CFMachPortCreate() can be used to create both a CFMachPort and the underlying native port. Both creation functions accept as an argument a callback function, which is called when a message arrives on the port. The callback is passed a pointer to the raw messagespecifically, the mach_msg_header_t structure. CFMachPortSetInvalidationCallBack() can be used to set another callback function that would be invoked when the port is invalidated.

CFMessagePort

A CFMessagePort is a wrapper around two native Mach portsunlike a CFMachPort, a CFMessagePort supports bidirectional communication. Like a CFMachPort, it can be used only for local (non-networked) intraprocess or interprocess communication since Mac OS X does not provide network-transparent Mach IPC.

In a typical use of CFMessagePort, a process creates a local port through CFMessagePortCreateLocal(), specifying a string name with which to register the port. The name can also be set later or changed by using CFMessagePortSetName(). Thereafter, another process can call CFMessagePortCreateRemote() with the same string name to create a CFMessagePort that is connected to the remote (to this process) port.

Let us look at an example of using a CFMessagePort. Figure 965 shows a server that creates a CFMessagePort and advertises its name. The server then creates a run-loop source from the port and adds it to the main run loop. On receiving a message, the only service the server provides is printing the contents of the messageno reply is sent.

Figure 965. A CFMessagePort server

// CFMessagePortServer.c #include <CoreFoundation/CoreFoundation.h> #define LOCAL_NAME "com.osxbook.CFMessagePort.server" CFDataRef localPortCallBack(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void *info) { printf("message received\n"); CFShow(data); return NULL; } int main(void) { CFMessagePortRef localPort; CFRunLoopSourceRef runLoopSource; localPort = CFMessagePortCreateLocal( kCFAllocatorDefault, // allocator CFSTR(LOCAL_NAME), // name for registering the port localPortCallBack, // call this when message received NULL, // contextual information NULL); // free "info" field of context? if (localPort == NULL) { fprintf(stderr, "*** CFMessagePortCreateLocal\n"); exit(1); } runLoopSource = CFMessagePortCreateRunLoopSource( kCFAllocatorDefault, // allocator localPort, // create run-loop source for this port 0); // priority index CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes); CFRunLoopRun(); CFRelease(runLoopSource); CFRelease(localPort); exit(0); }

Figure 966 shows the source for a client of the CFMessagePort server. The client uses the remote port's name (shared between the client and the server) to create a connection and sends a few bytes of data to the server.

Figure 966. A CFMessagePort client

// CFMessagePortClient.c #include <CoreFoundation/CoreFoundation.h> #define REMOTE_NAME "com.osxbook.CFMessagePort.server" int main(void) { SInt32 status; CFMessagePortRef remotePort; CFDataRef sendData; const UInt8 bytes[] = { 1, 2, 3, 4 }; sendData = CFDataCreate(kCFAllocatorDefault, bytes, sizeof(bytes)/sizeof(UInt8)); if (sendData == NULL) { fprintf(stderr, "*** CFDataCreate\n"); exit(1); } remotePort = CFMessagePortCreateRemote(kCFAllocatorDefault, CFSTR(REMOTE_NAME)); if (remotePort == NULL) { CFRelease(sendData); fprintf(stderr, "*** CFMessagePortCreateRemote\n"); exit(1); } status = CFMessagePortSendRequest( remotePort, // message port to which data should be sent (SInt32)0x1234, // msgid, an arbitrary integer value sendData, // data 5.0, // send timeout 5.0, // receive timeout NULL, // reply mode (no reply expected or desired) NULL); // reply data if (status != kCFMessagePortSuccess) fprintf(stderr, "*** CFMessagePortSendRequest: error %ld.\n", status); else printf("message sent\n"); CFRelease(sendData); CFRelease(remotePort); exit(0); }

Let us now test the CFMessagePort server and client programs.

$ gcc -Wall -o client CFMessagePortClient.c -framework CoreFoundation $ gcc -Wall -o server CFMessagePortServer.c -framework CoreFoundation $ ./server # another shell prompt $ ./client message sent message received <CFData 0x300990 [0xa0728150]>{length = 4, capacity = 4, bytes = 0x01020304}

CFSocket

A CFSocket is conceptually similar to a CFMessagePort with the key difference being that BSD sockets are used as the underlying communication channel. A CFSocket can be created in several ways: from scratch, from an existing native socket, or even from a native socket that is already connected.

A CFSocket supports callbacks for several types of socket activity, for example, when there is data to read (kCFSocketReadCallBack), when the socket is writable (kCFSocketWriteCallBack), when an explicitly backgrounded connection attempt finishes (kCFSocketConnectCallBack), and so on.

Figure 967 shows a client program that uses a CFSocket to connect to a well-known time server and retrieves the current time.

Figure 967. A CFSocket client

// CFSocketTimeClient.c #include <CoreFoundation/CoreFoundation.h> #include <netdb.h> #define REMOTE_HOST "time.nist.gov" void dataCallBack(CFSocketRef s, CFSocketCallBackType callBackType, CFDataRef address, const void *data, void *info) { if (data) { CFShow((CFDataRef)data); printf("%s", CFDataGetBytePtr((CFDataRef)data)); } } int main(int argc, char **argv) { CFSocketRef timeSocket; CFSocketSignature timeSignature; struct sockaddr_in remote_addr; struct hostent *host; CFDataRef address; CFOptionFlags callBackTypes; CFRunLoopSourceRef source; CFRunLoopRef loop; struct servent *service; if (!(host = gethostbyname(REMOTE_HOST))) { perror("gethostbyname"); exit(1); } if (!(service = getservbyname("daytime", "tcp"))) { perror("getservbyname"); exit(1); } remote_addr.sin_family = AF_INET; remote_addr.sin_port = htons(service->s_port); bcopy(host->h_addr, &(remote_addr.sin_addr.s_addr), host->h_length); // a CFSocketSignature structure fully specifies a CFSocket's // communication protocol and connection address timeSignature.protocolFamily = PF_INET; timeSignature.socketType = SOCK_STREAM; timeSignature.protocol = IPPROTO_TCP; address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&remote_addr, sizeof(remote_addr)); timeSignature.address = address; // this is a variant of the read callback (kCFSocketReadCallBack): it // reads incoming data in the background and gives it to us packaged // as a CFData by invoking our callback callBackTypes = kCFSocketDataCallBack; timeSocket = CFSocketCreateConnectedToSocketSignature( kCFAllocatorDefault, // allocator to use &timeSignature, // address and protocol callBackTypes, // activity type we are interested in dataCallBack, // call this function NULL, // context 10.0); // timeout (in seconds) source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, timeSocket, 0); loop = CFRunLoopGetCurrent(); CFRunLoopAddSource(loop, source, kCFRunLoopDefaultMode); CFRunLoopRun(); CFRelease(source); CFRelease(timeSocket); CFRelease(address); exit(0); } $ gcc -Wall -o timeclient CFSocketTimeClient.c -framework CoreFoundation $ ./timeclient <CFData 0x500fb0 [0xa0728150]>{length = 51, capacity = 51, bytes = 0x0a35333633322030352d30392d313920 ... 49535429202a200a} 53632 05-09-19 04:21:43 50 0 0 510.7 UTC(NIST) * <CFData 0x500b40 [0xa0728150]>{length = 0, capacity = 16, bytes = 0x}

CFRunLoopTimer

A CFRunLoopTimer is a special case of a run-loop source that can be set to fire at some time in the future, either periodically or one time only. In the latter case, the timer is automatically invalidated. A CFRunLoopTimer is created using CFRunLoopTimerCreate(), which takes a callback function as an argument. The timer can then be added to a run loop.

A run loop must be running to be able to process a timer. A timer can only be added to one run loop at a time, although it can be in multiple modes in that run loop.

Figure 968 shows a program that creates a periodic timer, adds it to the main run loop, and sets the run loop running for a given time. While the run loop is running, the timer gets processed and the associated callback is invoked.

Figure 968. Using a CFRunLoopTimer

// CFRunLoopTimerDemo.c #include <CoreFoundation/CoreFoundation.h> #include <unistd.h> void timerCallBack(CFRunLoopTimerRef timer, void *info); void timerCallBack(CFRunLoopTimerRef timer, void *info) { CFShow(timer); } int main(int argc, char **argv) { CFRunLoopTimerRef runLoopTimer = CFRunLoopTimerCreate( kCFAllocatorDefault, // allocator CFAbsoluteTimeGetCurrent() + 2.0, // fire date (now + 2 seconds) 1.0, // fire interval (0 or -ve means a one-shot timer) 0, // flags (ignored) 0, // order (ignored) timerCallBack, // called when the timer fires NULL); // context CFRunLoopAddTimer(CFRunLoopGetCurrent(), // the run loop to use runLoopTimer, // the run-loop timer to add kCFRunLoopDefaultMode); // add timer to this mode CFRunLoopRunInMode(kCFRunLoopDefaultMode, // run it in this mode 4.0, // run it for this long false); // exit after processing one source? printf("Run Loop stopped\n"); // sleep for a bit to show that the timer is not processed any more sleep(4); CFRunLoopTimerInvalidate(runLoopTimer); CFRelease(runLoopTimer); exit(0); } $ gcc -Wall -o timerdemo CFRunLoopTimerDemo.c -framework CoreFoundation $ ./timerdemo <CFRunLoopTimer ...>{locked = No, valid = Yes, interval = 1, next fire date = 148797186, order = 0, callout = 0x28ec, context = <CFRunLoopTimer context 0x0>} <CFRunLoopTimer ...>{locked = No, valid = Yes, interval = 1, next fire date = 148797187, order = 0, callout = 0x28ec, context = <CFRunLoopTimer context 0x0>} <CFRunLoopTimer ...>{locked = No, valid = Yes, interval = 1, next fire date = 148797188, order = 0, callout = 0x28ec, context = <CFRunLoopTimer context 0x0>} Run Loop stopped $

Категории