XPC to communicate Swift with C++

Hello! I'm new here, and probably this will sound weird but, I'm trying to write a C++ program using an XPC service to attempt to communicate with a macOS app I'm developing in Swift. At the macOS app side I think I'm successfully registering and connecting to the service, as launchd reports the following in its log:

2023-05-31 17:20:21.898621 (pid/15637 [On Air]) <Notice>: Service stub created for com.ruieduardolopes.onaird-join

At the C++ side I'm trying to get a simple example working, as follows, but currently without any success...

#include <xpc/xpc.h>

int main(int argc, const char* argv[])
{
    xpc_connection_t conn = xpc_connection_create_mach_service("com.ruieduardolopes.onaird.join", NULL, 0);
    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
    xpc_connection_set_event_handler(conn, ^(xpc_object_t object) {});
    xpc_connection_resume(conn);

    xpc_dictionary_set_string(message, "SS", "AAAAAA\n");
    xpc_connection_send_message_with_reply(conn, message, dispatch_get_main_queue(),
                                           ^(xpc_object_t object) {});

    return (EXIT_SUCCESS);
}

I get the following error in the logs:

2023-05-31 17:27:45.636894 (system/com.ruieduardolopes.onaird) <Notice>: internal event: WILL_SPAWN, code = 0
2023-05-31 17:27:45.636903 (system/com.ruieduardolopes.onaird) <Notice>: service state: spawn scheduled
2023-05-31 17:27:45.636904 (system/com.ruieduardolopes.onaird) <Notice>: service state: spawning
2023-05-31 17:27:45.637017 (system/com.ruieduardolopes.onaird) <Notice>: launching: ipc (mach)
2023-05-31 17:27:45.637256 (system/com.ruieduardolopes.onaird [15908]) <Notice>: xpcproxy spawned with pid 15908
2023-05-31 17:27:45.637264 (system/com.ruieduardolopes.onaird [15908]) <Notice>: internal event: SPAWNED, code = 0
2023-05-31 17:27:45.637265 (system/com.ruieduardolopes.onaird [15908]) <Notice>: service state: xpcproxy
2023-05-31 17:27:45.637269 (system/com.ruieduardolopes.onaird [15908]) <Notice>: internal event: SOURCE_ATTACH, code = 0
2023-05-31 17:27:45.648927 (system/com.ruieduardolopes.onaird [15908]) <Notice>: service state: running
2023-05-31 17:27:45.648940 (system/com.ruieduardolopes.onaird [15908]) <Notice>: internal event: INIT, code = 0
2023-05-31 17:27:45.648948 (system/com.ruieduardolopes.onaird [15908]) <Notice>: Successfully spawned onaird-join[15908] because ipc (mach)
2023-05-31 17:27:45.687251 (system/com.ruieduardolopes.onaird [15908]) <Notice>: exited due to SIGTRAP | sent by exc handler[15908]
2023-05-31 17:27:45.687261 (system/com.ruieduardolopes.onaird [15908]) <Notice>: service has crashed 1 times in a row
2023-05-31 17:27:45.687263 (system/com.ruieduardolopes.onaird [15908]) <Notice>: service state: exited
2023-05-31 17:27:45.687267 (system/com.ruieduardolopes.onaird [15908]) <Notice>: internal event: EXITED, code = 0
2023-05-31 17:27:45.687269 (system) <Notice>: service inactive: com.ruieduardolopes.onaird
2023-05-31 17:27:45.687281 (system/com.ruieduardolopes.onaird [15908]) <Notice>: service state: not running
2023-05-31 17:27:45.687290 (system/com.ruieduardolopes.onaird) <Notice>: Service only ran for 0 seconds. Pushing respawn out by 10 seconds.
2023-05-31 17:27:45.687339 (system/com.ruieduardolopes.onaird) <Notice>: internal event: WILL_SPAWN, code = 0
2023-05-31 17:27:45.687343 (system/com.ruieduardolopes.onaird) <Notice>: service state: spawn scheduled
2023-05-31 17:27:45.687344 (system/com.ruieduardolopes.onaird) <Notice>: service throttled by 10 seconds

The plist file I'm currently loading to /Library/LaunchDaemons is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>com.ruieduardolopes.onaird</string>
	<key>Program</key>
	<string>PATH_TO_ONAIR_JOIN_XPC_SERVICE_BINARY</string>
	<key>BuildMachineOSBuild</key>
	<string>22C65</string>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleDisplayName</key>
	<string>onaird-join</string>
	<key>CFBundleExecutable</key>
	<string>onaird-join</string>
	<key>CFBundleIdentifier</key>
	<string>com.ruieduardolopes.onaird-join</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>onaird-join</string>
	<key>CFBundlePackageType</key>
	<string>XPC!</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleSupportedPlatforms</key>
	<array>
		<string>MacOSX</string>
	</array>
	<key>CFBundleVersion</key>
	<string>1</string>
	<key>DTCompiler</key>
	<string>com.apple.compilers.llvm.clang.1_0</string>
	<key>DTPlatformBuild</key>
	<string></string>
	<key>DTPlatformName</key>
	<string>macosx</string>
	<key>DTPlatformVersion</key>
	<string>13.3</string>
	<key>DTSDKBuild</key>
	<string>22E245</string>
	<key>DTSDKName</key>
	<string>macosx13.3</string>
	<key>DTXcode</key>
	<string>1430</string>
	<key>DTXcodeBuild</key>
	<string>14E222b</string>
	<key>LSMinimumSystemVersion</key>
	<string>13.1</string>
	<key>MachServices</key>
	<dict>
		<key>com.ruieduardolopes.onaird.join</key>
		<true/>
	</dict>
	<key>XPCService</key>
	<dict>
		<key>ServiceType</key>
		<string>Application</string>
	</dict>
</dict>
</plist>

Can anybody help me?

Thanks in advance, Rui

Replies

I’m confused by your setup. You wrote:

I'm trying to write a C++ program using an XPC service to attempt to communicate with a macOS app I'm developing in Swift.

but then also wrote:

The plist file I'm currently loading to /Library/LaunchDaemons is as follows:

but these two things don’t gel. An app is not a launchd daemon and trying to run it as one will get not end well. Also, your launchd property list file looks like an amalgam of the right keys (per the launchd.plist man page man page), some XPC service keys (per the xpcservice.plist man page), and Info.plist keys (per Information Property List).


Can you take a step back and explain more about your high-level goal here? Is your app really meant to be an app, that is, something that the user would launch from the Finder? Are you expecting the user to run the command-line tool from Terminal in the same GUI login session as your app? Or from some other context? And what sort of IPC are you trying to enable?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

First of all, thanks for your answer!

Sorry for the confusion, as this is my first time looking over Apple frameworks to natively build an app I guess I still have some troubles describing my issues here 😅.

I have a library of mine, in C++, which uses other third-party libraries whose functionalities I cannot find alternatives using Swift/Objective-C and I want to attempt creating a macOS app using it. As I know you cannot integrate this C++ library into a Swift project directly, I tried some options I found from documentations: my first option was to create a bridging code with Objective-C to do this, but I was not being able to do it as I don't know Objective-C; my second option was to think of a daemon executable in C++, using my library, and exposing some IPC features in order other processes to communicate to and from it.

I chose to pursue the second option and my first attempt was to use UNIX domain sockets, but although I was able to connect both parties (my C++ daemon and a Swift command line tool), as soon as I implemented it in a macOS app, the communication was halted (I presumed it was due to some sandboxing features). Then I found that the path I was using for the socket (/var/tmp/onaird.sock) could not be reached from the macOS app, and I thought I'd better be looking for an IPC alternative.

Then I found XPC and I was interested to know more about. I found it has both an Objective-C/Swift API and a low-level C API which I found to be quite good for me.

Relatively to the app itself, my idea is that a user can simply press a button on a window and that a message could be sent from the macOS app to my C++ daemon, delegating it to perform a corresponding action. Some of these actions also need the C++ daemon to be able to message back some information to be presented to the user. The system could be seen as something close to a chat app in which a user sends an action to a controller (daemon), which does something with it; and if a controller wants to notify a user regarding another action it should be able to send such data to it (to the macOS app).

I hope this explanation is right and sorry if this content seems weird to you 🤦‍♂️. I'm really new into this new environment and still getting used to it 😅.

Thanks in advance, Rui

Thanks for the explanation. That’s much clearer.

You wrote:

As I know you cannot integrate this C++ library into a Swift project directly

Just FYI, that feature is coming, and coming soon. While I can’t discuss Apple’s plans specifically, the work is being done in the open under the aegis of the Swift C++ Interoperability Workgroup. So, if you wait a bit, this problem might just go away.

OTOH, this sounds like a fun project so you might want to continue working on it anyway (-:

my second option was to think of a daemon executable in C++

Daemon is an overloaded term. When I use it, I’m talking about a launchd daemon, a program that runs with elevated privileges in the global execution context [1]. In your case, however, I suspect that you’re using it to mean something less specific, namely a program that runs in the background. That changes the story considerably.

Does your C++ code need elevated privileges?

Does your C++ code need to continue executing after the user has quit your app?

Does your C++ code need to be invoked by any program other than your app?

If the answer to all of these is “No”, I think I can send you down a nice path (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] For more info on macOS execution contexts, see Technote 2083 Daemons and Agents. It’s very old, and thus subject to some bit rot, but the core concepts are still valid.

Thank you for your help!

Wow, that's cool! Well, I will also wait for that answer on the interoperability of Swift with C++, but now I am also curious on what could be done alternatively.

In fact I also consider the concept of a daemon such as you've mentioned it, but don't know why in this project I'm constantly calling daemon to it: what I mean is, in fact, just a background running program.

Answering to your questions, this C++ code does not need elevated privileges, as it does not also need to continue execution after the user has quit, and there is also no need to be invoked by any other program in the system.

Again, thank you so much for your help and availability

Rui Lopes

… just a background running program

… this C++ code does not need elevated privileges

… does not also need to continue execution after the user has quit, and there is also no need to be invoked by any other program in the system

Cool. In that case I’ll recommend an XPC service. An XPC service is a small bundled program that you embed within your app. Your app, and only your app, can connect to it via XPC. When you do that, the system starts the service and off you’re off to the races.

The system manages the lifecycle of your XPC service. It’ll terminate the service when the app ends but it may also terminate the service when the system is under memory pressure. It’ll avoid doing that if there’s a pending transaction.

The archived, but still very relevant, Daemons and Services Programming Guide has a bunch more info about XPC services. There are also low-level details in the xpcservice.plist man page. Finally, Xcode has a target template for this, File > New > Target > macOS > XPC Service.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"