Skip to content

NickeZ/epics-sys

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Simplify rust usage in EPICS

The full example can be found on rust-in-epics

Step 0: Initialize project

Create new crate

cargo new
...

Update Cargo.toml

[package]
edition = "2018"

[lib]
crate-type = ["dylib", "staticlib"]

[dependencies]
epics-sys = "0.0.1"

[build-dependencies]
bindgen = "0.32.1"

Step 1: Generate bindings for the record

Switch EPICS to clang

Go into configure/os/CONFIG_SITE.Common.linux-x86_64 and uncomment

GNU         = NO
CMPLR_CLASS = clang
CC          = clang
CCC         = clang++

If compilerSpecific.h is missing you might want to add the following line into your RULES file:

USR_CPPFLAGS = -I${EPICS_BASE}/include/compiler/clang

Rebuild epics with clang:

make clean uninstall
make -j

Create build.rs script

extern crate bindgen;

use std::env;
use std::path::PathBuf;
use std::string::String;

fn main() {
    let epics_base = PathBuf::from(env::var("EPICS_BASE").unwrap_or("/usr/local/epics/base".into()));

    let mut epics_include = epics_base.clone();
    epics_include.push("include");

    let mut epics_include_comp = epics_include.clone();
    epics_include_comp.push("compiler");
    epics_include_comp.push("clang");

    let mut epics_include_os = epics_include.clone();
    epics_include_os.push("os");
    epics_include_os.push("Linux");

    let mut sub_record = epics_include.clone();
    sub_record.push("subRecord.h");

    let mut registry_function = epics_include.clone();
    registry_function.push("registryFunction.h");

    // The bindgen::Builder is the main entry point
    // to bindgen, and lets you build up options for
    // the resulting bindings.
    let bindings = bindgen::Builder::default()
        // The input header we would like to generate
        // bindings for.
        .header(sub_record.to_str().unwrap())
        .header(registry_function.to_str().unwrap())
        // The include directory
        .clang_arg(epics_include.to_str().unwrap())
        .clang_arg(String::from("-I") + epics_include_comp.to_str().unwrap())
        //.clang_arg(String::from("-I") + "-I/home/niklas/git/epics-base/include/os/default")
        .clang_arg(String::from("-I") + epics_include_os.to_str().unwrap())
        // long doubles cannot be converted with bindgen see #550 @ rust-lang-nursury/rust-bindgen
        .blacklist_type("max_align_t")
        .trust_clang_mangling(false)
        // Finish the builder and generate the bindings.
        .generate()
        // Unwrap the Result and panic on failure.
        .expect("Unable to generate bindings");

    // Write the bindings to the $OUT_DIR/bindings.rs file.
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}

Add the following to the top of your lib.rs file.

#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

Step 2: Create subRecord function and register it with EPICS

Add the following to your lib.rs file. The suffix of the function name must be _impl.

To read C strings in rust you need to use unsafe since there are no guarantees.

// Bring in epics_register attribute
use epics_sys::epics_register;

#[epics_register]
pub fn mySubProcess_impl(record: &mut subRecord) -> Result<(), ()> {
    match try_from(&record.name) {
        Ok(name) => println!("Hello from rust! name={}", name),
        _ => println!("Invalid UTF8 in name"),
    }
    println!("A={:.2}", record.a);
    record.val = quad(record.a);

    // Return Ok or Err
    Ok(())
}

fn quad(n: f64) -> f64 {
    (n as f64)*(n as f64)
}

use std::ffi::CStr;
use std::str::Utf8Error;

#[derive(Debug)]
enum Error {
    Utf8Error(Utf8Error),
    NoNullCharacter,
}

fn try_from(input: &[i8]) -> Result<&str, Error> {
    if ! input.contains(&0i8) {
        return Err(Error::NoNullCharacter);
    }
    unsafe {CStr::from_ptr(input.as_ptr())}.to_str().map_err(|e| Error::Utf8Error(e))
}

Step 3: Configure EPICS application

Modify Makefile to link to crate. In this example I've put the rust crate in the Application src folder.

<APPName>_LDFLAGS += -pthread
<APPName>_SYS_LIBS += dl
<APPName>_LIBS += <crate-name>

<crate-name>_DIR = ${TOP}/<APPName>/src/<crate-name>/target/release

$(<crate-name>_DIR)/lib<crate-name>.so $(<crate-name>_DIR)/lib<crate-name>.a: ${TOP}/<APPName>/src/<crate-name>/src/lib.rs
	cargo build --release --manifest-path ${TOP}/<APPName>/src/<crate-name>/Cargo.toml

Add dbd file with content:

function(mySubProcess)

Add db file using your function

record(sub, "HELLO") {
    field(SNAM, "mySubProcess")
}

About

Rust bindings and helpers for EPICS

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages