How to pass an array of C-structs to an XPC service?

I am trying to pass an array of C-structs to an XPC Service, but the service receives only the first element. Following is the C struct

struct MyStruct
{
    char *name;
    unsigned char v1;
    unsigned char v2;
    Status status;   // a c-style enum
};

and I am using it like this

struct MyStruct structs[3] = {{"name1", 0, 0, success}, {"name2", 1,1, success}, {0}};

[[_connectionToService remoteObjectProxy] doSomething:structs];

and doSomething is declared as

- (void)doSomething: (struct MyStruct[]) structs;

The document Creating XPC Services mentions that C structures and arrays containing only the types listed above are supported, but I am unable to get it to work even for an array of c-strings or ints.

Also, there's an API for making synchronous RPC calls but there is no documentation available for it.

- (id)synchronousRemoteObjectProxyWithErrorHandler:(void (^)(NSError *error))handler

It does seem to block but only if the remote method has a reply block. Is this the expected behaviour? And is it safe to cache the proxy object returned by this method?

Accepted Reply

Filed FB9991036.

Thanks.


It does seem to be able to serialize the name field

Oh, you’re right. Wow, that’s smarter than I thought.

only the first struct in the array gets serialized.

Right. This boils down to how arrays are passed in C. When you declare a method like this:

- (void)doSomething:(struct MyStruct[])structs;

that’s equivalent to this:

- (void)doSomething:(struct MyStruct *)structs;

The parameter is passed as a pointer to the base of the array and there’s no info about its length.

C programmer usually get around this in one of two ways:

  • They give the array a terminator. For example, in the C string case, the array of char is terminated by a nul.

  • They pass a second parameter with the count.

The first option is a non-starter for you. It’s possible that NSXPCConnection is smart enough to interpret a count parameter next to the array as the array’s length. Honestly, I’ve never looked into it at that level of detail.

Based on the other details in your response I suspect that you’ll need to put a lot of work into your serialisation strategy, and thus you can deal with this at that time.

what does the documentation mean by C structures and arrays containing only the types listed above as acceptable parameters

I suspect it’s referring to fixed-size arrays within a C structure. For example:

struct Foo {
    int a[5];
};

In this case there is compile-time knowledge about the length of the array.


Would any of these options help with function pointers?

Function pointers? Like this:

typedef void (*MyFunc)(void);

struct Foo {
    MyFunc f;
};

No, nothing is going to help with those. Function pointers only make sense in the scope of a single process.

Some APIs in the library return some weird types involving pointers like a linked list where each node is itself a list of function pointers.

Yeah, that’s gonna be tricky. Serialising a linked list is easy enough but serialising a function pointer is impossible in the general case. The only situation where it might make sense is if the function pointer round trips. That is:

  1. The service passes the function pointer to the app.

  2. The app doesn’t do anything with the function pointer itself.

  3. The app passes the function pointer back to the service.

In that situation you can serialise it by replacing the function pointer with a unique ID, maintaining a table that maps function pointers to unique IDs and another table for the reverse.

There’s a potential for serious security vulnerabilities here. Fortunately, the fact that you’re creating an app-specific XPC Service means that you don’t have to worry too much about this: Only your app can connect to this service.


Ideally for the lifetime of the application. The XPC service needs to run as long as the app is running and maintain state, the list of function pointers.

OK. Keeping a proxy around won’t help you achieve that goal. An existing proxy does not prevent the system from ‘garbage collecting’ your XPC Service.

What you need to do here is have the service maintain an open transaction while it holds in-memory state that’s relevant to the app. XPC automatically opens these transactions when the app sends a message to which the service hasn’t replied. However, in situations like this, where the service maintains state across requests and it’s not feasible to persist that state to disk, the service can arrange to stay in memory by opening a transaction and leaving it open. Do this using xpc_transaction_begin.

IMPORTANT This doesn’t guarantee that the system won’t terminate the service. If things get dire, the system reserves the right to terminate services with open transactions. However, that’s not something that comes up in practice.

Share and Enjoy

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

Replies

With regards your main issue, there are two problems:

  • Your C struct can’t be sent over XPC because NSXPCConnection has no idea how to serialise the name field.

  • Sending an array of them won’t work because NSXPCConnection has no idea how long that array is.

You have a few options here:

  • You could manually serialise that array on the send, transport that as an NSData, and then manually deserialise it on the receive. If you do this, be very careful to write your deserialisation code securely.

  • You could create an NSObject subclass with properties similar MyStruct, convert your MyStruct to that, and send an NSArray of those. And then, on the receive side, do the reverse.

    For this to work your NSObject subclass must conform to NSSecureCoding.

  • You could abandon your C struct entirely and use this NSObject class everywhere.


Also, there's an API for making synchronous RPC calls but there is no documentation available for it.

Indeed. I’d appreciate you filing a bug about that. Please post your bug number, just for the record.

In the meantime, check out the comments next to that method in <Foundation/NSXPCConnection.h>.

It does seem to block but only if the remote method has a reply block. Is this the expected behaviour?

Honestly I’m surprised it doesn’t crash in that case (-: Synchronous proxies only make sense if the protocol method includes a reply. Using a synchronous proxy for a one-way message makes no sense. Just use a standard proxy.

And is it safe to cache the proxy object returned by this method?

To what end?

Share and Enjoy

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

NSXPCConnection has no idea how to serialise the name field.

It does seem to be able to serialize the name field, but only the first struct in the array gets serialized.

because NSXPCConnection has no idea how long that array is

I was also not expecting it to work due to the same reason, but what does the documentation mean by C structures and arrays containing only the types listed above as acceptable parameters for methods and reply blocks?

You have a few options here:

For simple structs, I am using JSON to serialize/deserialize as I am not too familiar with Objective-C. Would any of these options help with function pointers? For more context, I am working on porting an application to Apple Silicon, but don't have the source code for some decade-old C libraries. So creating an XPC service that loads those libraries under Rosetta while the rest of the app runs natively on Apple Silicon. Some APIs in the library return some weird types involving pointers like a linked list where each node is itself a list of function pointers.

I’d appreciate you filing a bug about that

Filed FB9991036.

To what end?

Ideally for the lifetime of the application. The XPC service needs to run as long as the app is running and maintain state, the list of function pointers.

Filed FB9991036.

Thanks.


It does seem to be able to serialize the name field

Oh, you’re right. Wow, that’s smarter than I thought.

only the first struct in the array gets serialized.

Right. This boils down to how arrays are passed in C. When you declare a method like this:

- (void)doSomething:(struct MyStruct[])structs;

that’s equivalent to this:

- (void)doSomething:(struct MyStruct *)structs;

The parameter is passed as a pointer to the base of the array and there’s no info about its length.

C programmer usually get around this in one of two ways:

  • They give the array a terminator. For example, in the C string case, the array of char is terminated by a nul.

  • They pass a second parameter with the count.

The first option is a non-starter for you. It’s possible that NSXPCConnection is smart enough to interpret a count parameter next to the array as the array’s length. Honestly, I’ve never looked into it at that level of detail.

Based on the other details in your response I suspect that you’ll need to put a lot of work into your serialisation strategy, and thus you can deal with this at that time.

what does the documentation mean by C structures and arrays containing only the types listed above as acceptable parameters

I suspect it’s referring to fixed-size arrays within a C structure. For example:

struct Foo {
    int a[5];
};

In this case there is compile-time knowledge about the length of the array.


Would any of these options help with function pointers?

Function pointers? Like this:

typedef void (*MyFunc)(void);

struct Foo {
    MyFunc f;
};

No, nothing is going to help with those. Function pointers only make sense in the scope of a single process.

Some APIs in the library return some weird types involving pointers like a linked list where each node is itself a list of function pointers.

Yeah, that’s gonna be tricky. Serialising a linked list is easy enough but serialising a function pointer is impossible in the general case. The only situation where it might make sense is if the function pointer round trips. That is:

  1. The service passes the function pointer to the app.

  2. The app doesn’t do anything with the function pointer itself.

  3. The app passes the function pointer back to the service.

In that situation you can serialise it by replacing the function pointer with a unique ID, maintaining a table that maps function pointers to unique IDs and another table for the reverse.

There’s a potential for serious security vulnerabilities here. Fortunately, the fact that you’re creating an app-specific XPC Service means that you don’t have to worry too much about this: Only your app can connect to this service.


Ideally for the lifetime of the application. The XPC service needs to run as long as the app is running and maintain state, the list of function pointers.

OK. Keeping a proxy around won’t help you achieve that goal. An existing proxy does not prevent the system from ‘garbage collecting’ your XPC Service.

What you need to do here is have the service maintain an open transaction while it holds in-memory state that’s relevant to the app. XPC automatically opens these transactions when the app sends a message to which the service hasn’t replied. However, in situations like this, where the service maintains state across requests and it’s not feasible to persist that state to disk, the service can arrange to stay in memory by opening a transaction and leaving it open. Do this using xpc_transaction_begin.

IMPORTANT This doesn’t guarantee that the system won’t terminate the service. If things get dire, the system reserves the right to terminate services with open transactions. However, that’s not something that comes up in practice.

Share and Enjoy

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