IPC on macOS

IPC on macOS



August 17, 2019


  1. IPC on macOS

  2. Before we dive in The Berkeley Software Distribution (BSD) is

    an operating system based on early Unix.
  3. BSD became the basics for several open-source OS: FreeBSD, OpenBSD,

    NetBSD, DragonFly BSD, TrueOS and Darwin
  4. Darwin is an open-source Unix-like operating system first released by

    Apple in 2000
  5. Darwin forms the core set of components upon which macOS,

    iOS, watchOS, tvOS, iPadOS and audioOS are based
  6. None
  7. BSD supports POSIX standard, so POSIX APIs are avaliable to

    us on macOS.
  8. POSIX == Portable Operating System Interface

  9. Available IPC techniques

  10. BSD 1. BSD sockets 2. BSD pipes 3. BSD signals

  11. Other low level 1. Shared Memory + Semaphores for large

    resources (like movie)
  12. Darwin low-level 1. Mach Ports

  13. The Mach port object is the underlying primitive used for

    all interprocess communication in macOS
  14. High-level macOS-specific 1. Apple Events 2. High-performance counterpart of Apple

    Events — CFMessagePort and friends 3. Distributed Notifications 4. Distributed objects (NSConnection and friends) 5. XPC (C API and Objective-C API)
  15. Even higher level no explicit setup for communication 1. NSPasteboard

    2. Services API
  16. BSD Sockets You can implement inter-process communication using BSD sockets

    via: — POSIX API (socket() and friends) — Core Foundation API (CFSocket and friends; also used for networking)
  17. BSD Pipes One-way communication. 2 types: — Unnamed pipe (example:

    command line shell cat magic.txt | grep -e Gwendoyln sends the contents of magic.text to the grep command) — Named pipe is a special type of file without any content on filesystem
  18. BSD signals Usually a process terminates on receiving a signal.

    But it can implement a signal handler function to override this behaviour. Used by OS kernel to tell the process about an exception (like division by 0). Command line kill command is another example of using signals. The signal namespace is just one integer, so collisions are very possible. Not recommended to use signals for IPC.
  19. Shared Memory + Semaphores A process allocates a memory region,

    that can be read and written by other processes. Advantages: * data is never copied * any process with appropriate permission can read and write Disadvantage: * fragile—if it is corrupted, all processes are corrupted Access it from one process, use more conventional IPC.
  20. Mach Ports Available APIs: — Darwin C API — Core

    Foundation — CFMachPort and friends — Foundation — NSMachPort and friends
  21. Darwin C API: sending natural_t data; mach_port_t port; struct {

    mach_msg_header_t header; mach_msg_body_t body; mach_msg_type_descriptor_t type; } message; message.header = (mach_msg_header_t) { .msgh_remote_port = port, .msgh_local_port = MACH_PORT_NULL, .msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0), .msgh_size = sizeof(message) };
  22. message.body = (mach_msg_body_t) { .msgh_descriptor_count = 1 }; message.type =

    (mach_msg_type_descriptor_t) { .pad1 = data, .pad2 = sizeof(data) }; mach_msg_return_t error = mach_msg_send(&message.header); if (error == MACH_MSG_SUCCESS) { // ... }
  23. Darwin C API Not recommended to use directly: interfaces will

    change with kernel updates.
  24. CFMachPort & NSMachPort

  25. Apple Events

  26. Apple Events NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager]; [appleEventManager setEventHandler:self andSelector:@selector(handleAppleEvent:withReplyEvent:)

    forEventClass:kInternetEventClass andEventID:kAEGetURL];
  27. None
  28. CFMessagePort

  29. CFMessagePort High-performance counterpart of Apple Events

  30. private struct RemoteServerPortName { static let client = "com.hideez.port.client" static

    let server = "com.hideez.port.server" } private var inPort: CFMessagePort? func startListening() { guard nil == inPort else { return } inPort = CFMessagePortCreateLocal( nil, RemoteServerPortName.client as CFString!, getInMessageCallback(), nil, nil) let runLoopSource = CFMessagePortCreateRunLoopSource(nil, inPort, 0); CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, CFRunLoopMode.commonModes); } func getInMessageCallback() -> CFMessagePortCallBack { return {(port: CFMessagePort?, messageId:Int32, data: CFData?, _: UnsafeMutableRawPointer?) -> Unmanaged<CFData>? in RemoteServer.shared.handleInMessage(port: port, messageId: messageId, data: data) } }
  31. func postMessage(message: RemoteMessageOut, waitForResponse wait: Bool = false) -> RemoteServerResponse?

    { guard let outPort = CFMessagePortCreateRemote( nil, RemoteServerPortName.server as CFString!) else { return nil } var respData: Unmanaged<CFData>? = Unmanaged.passRetained(CFDataCreate(nil, nil, 0)!) CFMessagePortSendRequest( outPort, message.id.rawValue, (message.data ?? Data()) as CFData, 30.0, wait ? 30.0 : 0.0, wait ? CFRunLoopMode.defaultMode.rawValue : nil, &respData) let data = respData!.takeUnretainedValue() as Data CFMessagePortInvalidate(outPort) return data.isEmpty ? nil : NSKeyedUnarchiver.unarchiveObject(with: data) as? [String : Any] }
  32. Distributed Notifications

  33. [NSDistributedNotificationCenter default addObserver:self selector:@selector(_openPreferences) name:notificationName object:nil];

  34. distributedNotificationCenter.postNotificationName(@"NotificationName", object: nil, userInfo: userInfo, deliverImmediately: true)

  35. Distributed objects Deprecated and has !!!

  36. Distributed objects NSConnection and friends are deprecated and have !!!

  37. XPC Available APIs: — C API (use from C, C++

    code) — Objective-C API (use from Objective-C, Swift)
  38. Obj-C XPC — modern API — Protocol-oriented(!) — Available since

    10.8 (year of 2012) — API of choice for IPC on macOS
  39. None
  40. None
  41. None
  42. IPC workshop

  43. Thank you!