Multi-window application with each window being handled by a sub-process

I have a Mac Carbon project that currently exists as a monolithic application that fhas a single main window that is primarily an OpenGL view into which 2D content is being drawn. Obviously, I'm eager to get this converted into a modern 64-bit application. I would also like to transition to allowing multiple windows to be open, each running an instance of the code that the existing application is based on, so that multiple ongoing sessions can be running at the same time.


My plan is to remove all the UI code from the existing application, and to eradicate all Carbon dependencies so I can convert the code to 64-bit. Then I will create a wrapper application which handles the UI: the menus, the creation and management of the session windows, and so forth. Each time a new session is created or restored from a save file, I want the wrapper app to instantiate a copy of the main program, using an IOSurface and a shared memory object to allow the wrapper and the main program to share a memory buffer which is used as a texture for Metal to draw the graphics into the session's window.


The main program is more than just a "helper" in that it can't be allowed to be terminated automatically or anything like that. It needs to stay running until the wrapper is quit or the wrapper tells a given session to exit.


Right now, my main concern is figuring out the right way to create and manage the session processes. It sounds like in theory converting the main session program into an XPC service would be the right approach, except for a few concerns I have due to things I've read:


  1. It sounds like a given XPC service can only be run once. That would not suffice for my needs, since the whole point is to spawn multiple copies of it.
  2. It sounds like XPC services are really intended for processes that live a very short time, and for which there are limits on how much control you have over how long the service is permitted to run.
  3. If XPC isn't the right choice, what is? I know I'm not the first person to think of doing things this way...


The basic model is something like this:


┌──────────────────────────────────────────────────┐                    ┌───────────────────┐
│                                                  │                    │                   │
│                   Main Program                   │        ┌ ─ ─ ─ ─ ─>│    SessionApp     │
│ •••••••••••••••••••••••••••••••••••••••••••••••• │                    │                   │
│                                                  │        │           └───────────────────┘
│ ┌───────────────────┐          ┌───────────────┐ │                                         
│ │                   │    ┌────>│ SessionWindow │<│─ ─ ─ ─ ┘           ┌───────────────────┐
│ │                   │    │     └───────────────┘ │                    │                   │
│ │                   │    │                       │        ┌ ─ ─ ─ ─ ─>│    SessionApp     │
│ │    Main Menus     │    │     ┌───────────────┐ │                    │                   │
│ │ Window Management │────┼────>│ SessionWindow │<│─ ─ ─ ─ ┘           └───────────────────┘
│ │                   │    │     └───────────────┘ │                                         
│ │                   │    │                       │                    ┌───────────────────┐
│ │                   │    │     ┌───────────────┐ │                    │                   │
│ │                   │    └────>│ SessionWindow │<│─ ─ ─ ─ ─ ─ ─ ─ ─ ─>│    SessionApp     │
│ └───────────────────┘          └───────────────┘ │                    │                   │
└──────────────────────────────────────────────────┘                    └───────────────────┘
                                                     messages back and                       
                                                    forth; shared memory                     
                                                          for each                           
                                                      SessionWindow's                        
                                                         MTKView's                           
                                                    window-sized texture                     
                                                           buffer                            
                                                                                             
                                                                                            


Any suggestions or advice? Thanks!

Replies

If you strike out here, you might want to burn a support ticket w/DTS to see if they can help.

Also...textdog salutes your AG skills
       !
           O      ^__^
               o  (oo)\_______
                  (__)\       )\/\
                      ||----w | 
                      ||     ||    

It sounds like a given XPC service can only be run once.

That’s correct. Internally, XPC Services support the notion of multiple instances, but that support is not available for third-party use. If you think that’d be helpful in your situation, please file a bug report describing your requirements.

I’d appreciate your posting your bug number, just for the record.

It sounds like XPC services are really intended for processes that live a very short time …

I don’t think that’s a valid characterisation. For example, the XPC Services used by WebKit are very long-lived.

If XPC isn't the right choice, what is?

It’s a tradeoff. To implement this with XPC you have to jump through some hoops (more on that below) but you do get one key benefit, namely that the XPC API is much easier to use than most other IPC APIs. That’s especially important in your case, because it’s likely that you’ll want to pass an

IOSurface
over your IPC channel. That’s easy to do with XPC but tricky for other IPC mechanisms [1].

With regards your specific setup, here’s how I’d implement this:

  1. You set up an XPC Service for managing the rendezvous.

  2. The app talks to the XPC Service to get it to start a new worker.

  3. In response, the XPC Service allocates a UUID and associates that with the worker instance.

  4. It then launches the worker (using

    NSTask
    , say), passing it the UUID as an argument.
  5. The worker allocates an anonymous XPC listener.

  6. It then creates an endpoint for that.

  7. Finally, it checks in with the XPC Service, using the UUID to identify itself and passing it the endpoint.

  8. The XPC Service then responds to the app, passing it the endpoint.

  9. The app can then use that endpoint to open a connection directly to the worker’s anonymous listener.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

[1] The only other IPC mechanism that allows you to transfer an

IOSurface
is Mach messaging, and Mach messaging is horribly hard to get right.

Yeah, that's one of the approaches that has come up. I'm trying to make sure I find the least complex and most sturdy approach before I die in and start coding.


I have filed a bug report suggesting the notion of allowing multiple instances of an XPC service (https://feedbackassistant.apple.com/feedback/6142396). In there, I explain my situation and what I'm suggesting, and point out that for people like me trying to update old 32-bit Carbon apps to modern, secure 64-bit code before Catalina ships, this would potentially save an enormous amount of time and effort.

I've actually already put in a DTS request

Yeah, I thought this sounded familiar.

I have filed a bug report suggesting the notion of allowing multiple instances of an XPC service

Thanks. My recommendation is that you plan to implement your solution on the currently shipping infrastructure. While it’s hard to predict the future with 100% accuracy, past experience is that it’s quite unusual for us to add significant APIs between WWDC and GM.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Yes, that seemed obvious enough. Hopefully in the future I can simplify my code by just using multiple XPC service instances, but for now I'll have to create a service that creates instances, basically. OK. I'll see if I can implement something along those lines. FWIW it's something that would be really nice to see a DTS sample for, since upon searching around more I see this has been a topic of discussion in the past.


With that sorted out, the specifics of what the DTS request is about are clearer, hopefully. 🙂

Just a clarifying question before I start ripping apart my own app:


The XPC service you refer to in the first step, that has to be a login item/Helper/SMJobBless style service? I've tried to few simple experiments with an on-demand XPCService, but if I launch an NSTask from it the launched task cannot communicate with the XPCService. Unless there's a non-obvious trick I'm missing?

if I launch an

NSTask
from it the launched task cannot communicate with the XPC Service.

I just assumed that an embedded XPC Service would work, but I must admit to having not tested that case. Alas, I don’t have time to do that in the context of DevForums; if you’d like a definitive answer, you should open a DTS tech support incident and we can pick things up in that context.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"