swift zeromq c bridging header - cannot find types

343 views Asked by At

I'm trying to build Swift bindings for zeromq using czmq.

I've configured a bridging header in xcode and it seems to understand that, but I still get this error:

/Users/jjl/code/swiftzmq/src/zsock.swift:47:37: error: use of undeclared type 'zsock_t'
typealias zsock_ptr = UnsafePointer<zsock_t>

Using this in C, I would have this type available if I did this (which is the exact contents of my bridging header):

#include <czmq.h>

I know it's being included because it was complaining about something in one of the header files until I was linking against the correct library path.

I don't know where to go from here. Any help you can offer would be appreciated

My project is at https://github.com/jjl/swiftzmq

1

There are 1 answers

0
jjl On

I figured out what was going on. The swift interop layer isn't smart enough to understand the tricks the author of czmq plays on the compiler toolchain in the name of better code, so we have to do some C wrapping to hide it from swift.

The easy bits:

  • Remove the #include <czmq.h> line from your bridging header
  • Add a line importing another header of your choice (I chose szsocket.h because it's the swift zsocket wrapper)
  • Create a corresponding .c file and #import <czmq.h>

The harder bits:

You need to recreate any structures in your header that are used by the library. In my case the zsock_t structure which was defined as follows:

typedef struct zsock_t {
    uint32_t tag;               //  Object tag for runtime detection
    void *handle;               //  The libzmq socket handle
    char *endpoint;             //  Last bound endpoint, if any
    char *cache;                //  Holds last zsock_brecv strings
    int type;                   //  Socket type
    size_t cache_size;          //  Current size of cache
} zsock_t;

We create a simple wrapper like so:

typedef struct szsock_t {
    uint32_t tag;               //  Object tag for runtime detection
    void *handle;               //  The libzmq socket handle
    char *endpoint;             //  Last bound endpoint, if any
    char *cache;                //  Holds last zsock_brecv strings
    int type;                   //  Socket type
    size_t cache_size;          //  Current size of cache
} szsock_t;

You will also need to recreate any typedefs used in those structures. In this case, handily no others. These all go in the new header (.h) file

You then need to wrap every function in the library that accepts one of these structures. We take zsock_new function as an example.

First we need to predeclare our version in the header to avoid any zsock types. We just replace every occurrence of zsock with szsock (emacs can help with this):

szsock_t *szsock_new (int type, const char *filename, size_t line_nbr);

Next we need to create the wrapper function in the .c file:

szsock_t *szsock_new (int type, const char *filename, size_t line_nbr) {
    return (szsock_t *) zsock_new(type, filename, line_nbr);
}

Notice how we cast between zsock_t and szsock_t and use the internal zsock functions. It's safe to do so because the swift compiler won't be reading it, just the c compiler.

Next up, there were a bunch of varargs functions. This worked for me:

int szsock_bind (szsock_t *self, const char *format, ...) {
    int ret;
    va_list args;
    va_start(args, format);
    ret = zsock_bind( (zsock_t *) self, format, args);
    va_end(args);
    return ret;
}

Good luck to anyone reading wrapping a library in swift!