LoginSignup
14
7

More than 5 years have passed since last update.

C FFIでのコールバックの扱い(RustにおけるNullableな関数ポインタ)

Posted at

Cの関数ポインタとRustの関数ポインタ

#include <stdio.h>

typedef void (*rust_callback)(int);
rust_callback cb;

int register_callback(rust_callback callback) {
    cb = callback;
    return 1;
}

void trigger_callback() {
  if (cb != NULL) {
    cb(7);
  } else {
    printf("callback function is not set!");
  }
}

(他言語関数インターフェイスのものを改変)
このようなコールバックを扱うCのライブラリを呼び出すRustのFFIを作りましょう。

他言語関数インターフェイスでは以下のように作るように書いてあります:

extern fn callback(a: i32) {
    println!("I'm called from C with value {0}", a);
}

#[link(name = "ext")]
extern {
   fn register_callback(cb: extern fn(i32)) -> i32;
   fn trigger_callback();
}

fn main() {
    unsafe {
        register_callback(callback);
        trigger_callback(); // Triggers the callback
    }
}

これは勿論動作します。しかし、これは本当にCのAPIを正しく踏襲できているでしょうか?

元のCのプログラムにおいて、cbにはNULLを入れてもいいはずです。CのAPIでは、コールバックが必要ない場合は関数ポインタとしてNULLを入れることはよく行われます。

register_callback(NULL);  // callbackは登録しない

さてここで問題になるのはRust側のregister_callbackの定義です。NULLに相当するのはstd::ptr::null_mut()なのでこれを代入してみましょう:

use std::ptr::null_mut;
extern "C" {
    fn register_callback(cb: extern "C" fn(i32)) -> i32;
}
fn main() {
    unsafe { register_callback(null_mut()) };
}

これはコンパイルできません:

   |
12 |     unsafe { register_callback(null_mut()) };
   |                                ^^^^^^^^^^ expected fn pointer, found *-ptr
   |
   = note: expected type `extern "C" fn(i32)`
              found type `*mut _`

これはasで明示的に変換しようとしてもできません。

Nullableな関数ポインタ

ところでCからRustのバインディングを生成できるrust-bindgenはどうやっているのでしょうか?上のCのコードをbindgenしてみましょう

pub type rust_callback = ::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
extern "C" {
    #[link_name = "\u{1}cb"]
    pub static mut cb: rust_callback;
}
extern "C" {
    pub fn register_callback(callback: rust_callback) -> ::std::os::raw::c_int;
}

#include<stdio.h>があるので余計な部分がたくさん出てきますが、該当部分はこれだけです。
Option<extern fn(i32)>を使えば良いようですね。

extern "C" fn callback(a: i32) {
    println!("I'm called from C with value {0}", a);
}

#[link(name="ext")]
extern "C" {
    fn register_callback(cb: Option<extern "C" fn(i32)>) -> i32;
    fn trigger_callback();
}

fn main() {
    unsafe {
        register_callback(Some(callback));
        trigger_callback();
    }
    unsafe {
        register_callback(None);
        trigger_callback();
    }
}
$ cargo run
I'm called from C with value 7
callback function is not set!

上手くできました(/・ω・)/

参考

以上のソースコードは https://github.com/termoshtt/nullable_fn_ptr にあります

14
7
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
7