What's the proper way to assign a UnsafeMutablePointer<OpaquePointer>

Hi all,

I have a following opaque type defined in C code

my_client.h

typedef struct MyClient MyClient;

// to init
void init_my_client(struct MyClient** client_ptr) {
    MyClient* client = init_impl()

    // when calling in swift, this doesn't work
    *client_ptr = client
}

// to destroy
void destroy(struct MyClient* client_ptr) {
    free(client_ptr)
}

When I imported this header file into swift, it infers type struct MyClient** into UnsafeMutablePointer<OpaquePointer?>?

So I tried the following way to init it, when I assign value to this pointer in C, it reports Exit code 11...

MyClient.swift

public class MyClient {
    let clientPtr: UnsafeMutablePointer<OpaquePointer?>?

    init() {
        init_my_client(clientPtr)
        // the unexpected exit happens inside this C function, during assigning my client pointer to clientPtr
    }

    deinit() {
        if let ptr = clientPtr {
            if let innerPtr = ptr.pointee {
                destroy(innerPtr)
                clientPtr = nil
            }
        }
    }
}

I am very new to swift, could anyone here help to point out what's the problem exactly?

I have a felling on that, before passing clientPtr into init_my_client, it's not been initialized (with value nil?), but I am not aware of the right way to do so.

Thanks

Well, 2 minutes after I posted this question, I figured it out.

the solution is: changing clientPtr type to be OpaquePointer?, and passing &clientPtr into the init_my_client function.

Accordingly, deinit should be changed as well.

The final swift code is:

public class MyClient {
    let clientPtr: OpaquePointer?

    init() {
        init_my_client(&clientPtr)
    }

    deinit() {
        if let inner = clientPtr {
            destroy(innerPtr)
            clientPtr = nil
        }
    }
}
1 Like

I'd do this in C header:

typedef struct ClientStruct *Client;
Client newClient(void);
void deleteClient(Client);

and this in swift:

class SwiftClient {
    let client /* : Client! */ = newClient()

    deinit {
        if let client = client {
            deleteClient(client)
        }
    }
}

Thanks for the reply.

This seems to work, but the reason why I'd passing in the pointer object is that, in the real implementation, there is a return code on both newClient and deleteClient indicating if the operation was done correctly.

That you can pass as BOOL foo(...., NSError**) from C and it would be converted into try/catch in swift. (Updated below.)

If I understood correctly, the c code needs to be modified (adding NSError** param) to achieve this?

The fact is this c library is designed to be multi-language compatible not only for swift..

or you mean, by only changing the return type to boolean on C function, swift can then perform some magic to implement try .. catch ?

Actually scratch the above, unless it works for C methods (vs obj-c methods), which is probably not the case.

This may work for you, C header:

typedef struct ClientStruct *Client;
Client newClient(NSInteger* errorCode);
void deleteClient(Client, NSInteger* errorCode);

swift:

class SwiftClient {
    let client: Client!
    
    init() {
        var errorCode = 0
        client = newClient(&errorCode)
    }

    deinit {
        if let client = client {
            var errorCode = 0
            deleteClient(client, &errorCode)
        }
    }
}

still no "opaque" stuff.

Yep, just I don't know if there is a way to express this machinery in pure C. If to do this via Obj-C bridge that would be:

@interface IClient: NSObject
+ (Client)newClient:(NSError**)error;
+ (BOOL)deleteClient:(Client)client error:(NSError**)error;
@end

@implementation IClient
+ (Client)newClient:(NSError**)error {
    *error = [NSError errorWithDomain:@"domain" code:-1 userInfo:nil];
    return nil;
}
+ (BOOL)deleteClient:(Client)client error:(NSError**)error {
    *error = [NSError errorWithDomain:@"domain" code:-1 userInfo:nil];
    return NO;
}
@end

It shows how you return error. On success assign error to nil and return client/YES correspondingly.

swift side becomes:

client = try! IClient.newClient()
....
try! IClient.delete(client)

or:

do {
    client = try IClient.newClient()
} catch {
    print(error)
}

Looks cool, thanks a lot tera, I ll take it as my reference!