Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I create "C Blocks" when using FFI?

Tags:

rust

ffi

I'm working with the CoreFoundation framework on OS X, but I don't know how to map this function in Rust:

void CFRunLoopPerformBlock(CFRunLoopRef fl, CFTypeRef mode, void (^block)(void));

The last parameter is void(^block)(void) — how can I create arguments of this type?

like image 857
Nozama Avatar asked Apr 17 '15 07:04

Nozama


1 Answers

Short, probably helpful answer: there's the block crate, which looks like it might do the job.

Short, unhelpful answer: Insofar as I am aware, Rust doesn't have any support for Apple's block extension. There is no equivalent Rust type, assuming you want to call an API that expects a block.

Longer, marginally less unhelpful answer: From what I can gather from some Clang documentation on the Apple Block ABI, void(^)(void) would be the same size as a regular pointer.

As such, my advice is as follows: treat blocks as opaque, pointer-sized values. To invoke one, write a function in C which calls it for you.

The following is untested (I don't have a Mac), but should at least get you going in the right direction. Also, I'm marking this community wiki so anyone who can test it can fix it if need-be.

In Rust:

// These are the "raw" representations involved.  I'm not using std::raw
// because that's not yet stabilised.
#[deriving(Copy, Clone)]
struct AppleBlock(*const ());

#[deriving(Copy, Clone)]
struct RustClosure(*const(), *const());

// Functions that we need to be written in C:
extern "C" {
    fn rust_closure_to_block(closure_blob: RustClosure) -> AppleBlock;
    fn block_release(block_blob: AppleBlock);
}

// The function that the C code will need.  Note that this is *specific* to
// FnMut() closures.  If you wanted to generalise this, you could write a
// generic version and pass a pointer to that to `rust_closure_to_block`.
extern "C" fn call_rust_closure(closure_blob: RustClosure) {
    let closure_ref: &FnMut() = unsafe { mem::transmute(closure_blob) };
    closure_ref();
}

// This is what you call in order to *temporarily* turn a closure into a
// block.  So, you'd use it as:
//
//     with_closure_as_block(
//         || do_stuff(),
//         |block| CFRunLoopPerformBlock(fl, mode, block)
//     );
fn with_closure_as_block<C, B, R>(closure: C, body: B) -> R
where C: FnMut(), B: FnOnce(block_blob) -> R {
    let closure_ref: &FnMut() = &closure;
    let closure_blob: RustClosure = unsafe { mem::transmute(closure_ref) };
    let block_blob = unsafe { rust_closure_to_block(closure_blob) };
    let r = body(block_blob);
    unsafe { block_release(block_blob) };
    r
}

In C:

typedef struct AppleBlock {
    void *ptr;
} AppleBlock;

typedef struct RustClosure {
    void *ptr;
    void *vt;
} RustClosure;

void call_rust_closure(RustClosure closure_blob);

AppleBlock rust_closure_to_block(RustClosure closure_blob) {
    return (AppleBlock)Block_copy(^() {
        call_rust_closure(closure_blob);
    });
}

// I'm not using Block_release directly because I don't know if or how
// blocks change name mangling or calling.  You might be able to just
// use Block_release directly from Rust.
void block_release(AppleBlock block) {
    Block_release((void (^)(void))block);
}
like image 164
2 revs Avatar answered Sep 29 '22 13:09

2 revs