Search code examples
ipcxpcnsxpcconnection

Fast shared memory on macOS using XPC


I have two GUI applications which use shared memory (8 memory instances, each ~100MB) with frequent read/write operations where one application ("server") writes to the memory, and the other reads from it. On Windows the "server" application creates a shared memory using CreateFileMappingA function, while the other app reads from it using OpenFileMappingA. The buffer is accessed via MapViewOfFile. Locking is done via named mutexes.

Now I am looking for a way to implement this on macOS. XPC (especially xpc_shmem_map) seems to be a good fit but I do not entirely understand how it works. From the documentation and other sources (https://developer.apple.com/forums/thread/126716) it looks as if we always (?) have to create a service which acts as a bridge between the two applications? Or can one application just act as a "server" (like on Windows)?

What is the best way to share memory between two applications with as little copying on macOS (non-sandboxed)?

Regards,


Solution

  • Two applications by themselves cannot directly talk to one another via XPC. If you need to securely share memory between two applications on macOS, XPC likely is your best option - it will just involve some additional setup.

    The core concept to understand here is that there are only two ways to establish an XPC connection:

    1. Connecting to a named service managed by launchd.
    2. Connecting to an XPC endpoint. However, the only way for a process to get access to an XPC endpoint that is associated with a listener connection in another service is over another XPC connection.

    The reason you can't have one GUI application directly connection to another over XPC is neither of your GUI applications are going to be managed by launchd. And if you're wondering, well can my GUI application be managed by launchd? The answer is no; launchd only manages services (which are dynamically started and can be terminated if system resources are needed).

    The way you'll need to do this is:

    1. Create a Launch Agent. (Theoretically you could instead create a Launch Daemon, but there's no good reason to — you don't need root permission for what you're trying to do.)
    2. Have your Launch Agent vend a XPC Mach service. (This corresponds with #1 above when I was describing the core concept.)
    3. Have one of your GUI applications create an anonymous listener connection. Then get that listener's endpoint.
    4. Have that GUI application send your Launch Agent its endpoint.
    5. Have your other GUI application ask your Launch Agent for the endpoint.
    6. Have the other GUI application create a connection using that endpoint.

    At this point your two GUI applications can directly talk to one another.

    What I described is a bit oversimplified. For example you may way to have a design where both of them create an anonymous listener connection, send it to the Launch Agent, and if one already exists from the other application then it can return that. To avoid race conditions inside the Launch Agent you can DispatchQueue to serialize its processing of requests from the GUI applications.

    FYI I linked to the Objective-C versions of the API because your post tagged nsxpcconnection. However, there are also C API equivalents for all of this. If you have a cross-platform codebase, you may find the C API much easier to integrate.