Examples of creating Rust bindings

Couple of months ago I developed two SDR tools called doppler and demod. These tools were using unix pipe to transfer IQ data stream from one process to another and they were written in C that time.

I have read a lot of blog posts about Rust from the beginning of the year 2015 and I decided to give it a try. The best way to learn a new language is to use it, therefore I decided to rewrite those tools in Rust. This post mostly shows how to use Foreign Function interface - FFI to create Rust bindings.

 Doppler

Firstly libgpredict was improved to use cmake for building and installing proper library. It is much easier to deal with Rust FFI if library is used instead of just bunch of C files. For example C version of doppler statically linked needed files from git submodule, which was fine for C version.

I could not use rust-bindgen for generating Rust functions from C because it did not work at this time. I was using Rust 1.0 beta and rust-bindgen worked with nightly or did not work at all. Therefore I had to do bindings manually. Here are some examples.

C

typedef struct {
    char*           name;
    char*           nickname;
    char*           website;
    tle_t           tle;       /*!< Keplerian elements */
    int             flags;     /*!< Flags for algo ctrl */
    sgpsdp_static_t sgps;
    ...
} sat_t;

Rust

#[repr(C)]
pub struct sat_t {
    pub name:       *const c_char,
    pub nickname:   *const c_char,
    pub website:    *const c_char,
    pub tle:        tle_t,          // Keplerian elements
    pub flags:      c_int,          // Flags for algo ctrl
    pub sgps:       sgpsdp_static_t,
    ...
}

This struct is rather straight forward and as you can see Rust libc provides all necessary C types (*const c_char etc).

 Dealing with raw pointers

Lets see how function bindings are made. Here is example from function that parses TLE files:

C

int Get_Next_Tle_Set( char lines[3][80], tle_t *tle );

Rust

#[link(name = "gpredict")]
extern {
    pub fn Get_Next_Tle_Set(line: *const c_char, tle: *mut tle_t) -> c_int;
}

Notice how char array and tle reference is translated to Rust. It was quite easy to translate from C function to Rust however it was rather difficult to use this function compared to C version.

use std::ffi::{CString};
use libc::{c_char};
use std::{cmp, ptr};

fn copy_memory(src: &[u8], dst: &mut [u8]) -> usize {
    let len = cmp::min(src.len(), dst.len());
    unsafe {
        ptr::copy_nonoverlapping(&src[0], &mut dst[0], len);
    }
    len
}

pub fn create_tle_t(tle: Tle) -> ffipredict::tle_t {
    let mut tle_t = ffipredict::tle_t {
        epoch: 0.0,
        epoch_year: 0,
        // some init details missing
    };

    let name = CString::new(tle.name).unwrap();
    let line1 = CString::new(tle.line1).unwrap();
    let line2 = CString::new(tle.line2).unwrap();
    let mut buf = [[0u8; 80]; 3];

    copy_memory(name.as_bytes_with_nul(), &mut buf[0]);
    copy_memory(line1.as_bytes_with_nul(), &mut buf[1]);
    copy_memory(line2.as_bytes_with_nul(), &mut buf[2]);


    unsafe { ffipredict::Get_Next_Tle_Set(transmute::<&u8, *const c_char>(&buf[0][0]), &mut tle_t)};

    tle_t
}

Notice how transmute is used to cast Rust array to raw pointer.

 Demod

Porting demod to Rust was much easier, because it was possible to use rust-bindgen this time. Actually later I took out liquid-dsp parts from demod and made a separate wrapper library called rust-liquid-dsp.

NB: to build rust-bindgen following command must be executed on Mac OS X before building it:

echo export DYLD_LIBRARY_PATH=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/:$DYLD_LIBRARY_PATH >> ~/.profile

Then just use cargo to build rust-bindgen:

cargo build

Rust does not have native support for complex numbers. Therefore liquid.h must be little bit changed to create custom struct for complex numbers.

Original snippet from liquid.h file:

#if LIQUID_USE_COMPLEX_H==1
#   include <complex.h>
#   define LIQUID_DEFINE_COMPLEX(R,C) typedef R _Complex C
#elif defined _GLIBCXX_COMPLEX || defined _LIBCPP_COMPLEX
#   define LIQUID_DEFINE_COMPLEX(R,C) typedef std::complex<R> C
#else
#   define LIQUID_DEFINE_COMPLEX(R,C) typedef struct {R real; R imag;} C;
#endif

Remove those lines except this one:

#define LIQUID_DEFINE_COMPLEX(R,C) typedef struct {R real; R imag;} C;

Now the following command generates Rust bindings file ffiliquid.rs from liquid-dsp C files:

./target/debug/bindgen -l liquid -match liquid.h -o ~/Development/rust-liquid-dsp/src/ffiliquid.rs ~/Downloads/liquid-dsp/include/liquid.h

Here is an example how liquid-dsp fredem object is wrapped to Rust.

freqdem.rs

use ffiliquid;
use super::{Complex32};

pub struct Freqdem {
     object: ffiliquid::freqdem,
}

impl Freqdem {

    /// create freqdem object (frequency demodulator)
    ///  _kf      :   modulation factor
    pub fn new(_kf: f32) -> Freqdem {
        let demod: ffiliquid::freqdem = unsafe{ffiliquid::freqdem_create(_kf)};
        Freqdem{object: demod}
    }

    /// demodulate sample
    ///  _r      :   received signal r(t)
    ///  _m      :   output message signal m(t)
    pub fn demodulate(&self, _r: Complex32, _m: *mut f32) {
        unsafe{ffiliquid::freqdem_demodulate(self.object, _r, _m)};
    }

    /// demodulate block of samples
    ///  _r      :   received signal r(t) [size: _n x 1]
    ///  _n      :   number of input, output samples
    ///  _m      :   message signal m(t), [size: _n x 1]
    pub fn demodulate_block(&self, _r: &mut [Complex32], _n: u32, _m: &mut [f32]) {
        unsafe{ffiliquid::freqdem_demodulate_block(self.object, _r.as_mut_ptr(), _n, _m.as_mut_ptr())};
    }
}

impl Drop for Freqdem {
    fn drop(&mut self) {
        unsafe{ffiliquid::freqdem_destroy(self.object)};
    }
}
 
11
Kudos
 
11
Kudos

Now read this

Using GNU Radio to show I/Q wav file on waterfall plot

Installing GNU Radio Finally I managed to get GNU Radio working on Mac OS X. Firstly I used Homebrew repository from here https://github.com/robotastic/homebrew-hackrf to install GNU Radio and HackRF tools. However it did not went well.... Continue →