6

I have an external library (e.g. libcisland.so) with interface like this:

size_t lib_handle_size();
typedef void* handle;
int lib_init(handle h);
int lib_store(handle h, int value);
int lib_restore(handle h, int *pvalue);

The user of this library is expected to do following:

// allocate some buffer in client address space
handle h = malloc(lib_handle_size());
// pass this buffer to library for initialization
if (lib_init(h)) { /* handle errors */ }
// library initializes this handle by some opaque fashion
// then user uses it
lib_store(h,42);
int r;
lib_restore(h,&r);
// after all work is done, user frees this handle
free(h);

I can't figure out how to properly wrap this interface to Rust. This is what I ended up:

pub struct Island {
    h: Handle,
    v: Vec<u8>,
}

impl Island {
    pub fn new() -> Island {
        let len = unsafe { lib_handle_size() };
        let mut v: Vec<u8> = Vec::with_capacity(len);
        let h: Handle = v.as_mut_ptr();
        Island { v:v, h:h, }
    }

    pub fn store(&mut self, v: i32) {
        unsafe { lib_store(self.h, v); }
    }

    pub fn restore(&mut self) -> i32 {
        let mut v = 0;
        unsafe { lib_restore(self.h, &mut v); }
        v
    }
}

impl Drop for Island {
    fn drop(&mut self) {
        drop(&mut self.v);
    }
}

/// unsafe part
use libc::size_t;
pub type Handle = *mut u8;
#[link(name="cisland")]
extern {
    pub fn lib_handle_size() -> size_t;
    pub fn lib_init(h: Handle) -> i32;
    pub fn lib_store(h: Handle, value: i32) -> i32;
    pub fn lib_restore(h: Handle, pvalue: &mut i32) -> i32;
}

Is it Ok to use Vec(u8) for this purpose? Is this Drop trait implemented properly?

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • 2
    The `drop(&mut self.v);` creates a reference to `v` and then drops the reference, doing nothing at all. Drops are called recursively automatically, you do not need to do write anything unless you need to do something special. – rodrigo Oct 04 '19 at 08:12

2 Answers2

2

Is it Ok to use Vec(u8) for this purpose?

I think Vec<u8> is ok, but you should initialize it rather than using a zero-length vector, pointing at uninitialized memory. It would also be more robust to use Box<[u8]> because that will enforce that it can't be reallocated accidentally.

Is this Drop trait implemented properly?

It should not be necessary to implement Drop at all. The fields of Island each will drop correctly anyway.

Rather than store the handle, I would get it each time using a method. Then your struct is much simpler.

use libc::c_void;

pub struct Island {
    buf: Box<[u8]>,
}

impl Island {
    pub fn new() -> Island {
        let len = unsafe { lib_handle_size() };
        let v: Vec<u8> = vec![0; len];
        Island { buf: v.into_boxed_slice() }
    }

    pub fn store(&mut self, v: i32) {
        unsafe { lib_store(self.handle_mut(), v); }
    }

    pub fn restore(&mut self) -> i32 {
        let mut v = 0;
        unsafe { lib_restore(self.handle_mut(), &mut v); }
        v
    }

    fn handle_mut(&mut self) -> *mut c_void {
        self.buf.as_mut_ptr() as *mut c_void
    }
}

You don't need a Drop implementation because the Box will drop automatically when it goes out of scope (as would a Vec).

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
1

Is it Ok to use Vec(u8) for this purpose?

A vector is not mean to be use like that, even if your code should work this is not a good method.

To do it properly you need an experimental feature (this one is quite stable), you need to use System structure and Alloc trait. Unfortunately, your library doesn't give any alignment requirement for its handle so we must use 1.

pub type Handle = *mut u8; is incorrect according to your typedef void* handle; (by the way hide pointer is bad). It should be pub type Handle = *mut libc::c_void;.

#![feature(allocator_api)]
use std::alloc::{Alloc, Layout, System};

use std::ptr::NonNull;

pub struct Island {
    handle: NonNull<u8>,
    layout: Layout,
}

impl Island {
    pub fn new() -> Island {
        let size = unsafe { lib_handle_size() };
        let layout = Layout::from_size_align(size, 1).unwrap();
        let handle = unsafe { System.alloc(layout).unwrap() };
        unsafe {
            // can have error I guess ?
            lib_init(handle.as_ptr() as Handle);
        }
        Self { handle, layout }
    }

    pub fn store(&mut self, v: i32) -> Result<(), ()> {
        unsafe {
            lib_store(self.handle.as_ptr() as Handle, v);
        }
        Ok(())
    }

    pub fn restore(&mut self, v: &mut i32) -> Result<(), ()> {
        unsafe {
            lib_restore(self.handle.as_ptr() as Handle, v);
        }
        Ok(())
    }
}

impl Drop for Island {
    fn drop(&mut self) {
        unsafe { System.dealloc(self.handle, self.layout) }
    }
}

/// unsafe part
use libc::size_t;
pub type Handle = *mut libc::c_void;
#[link(name = "cisland")]
extern "C" {
    pub fn lib_handle_size() -> size_t;
    pub fn lib_init(h: Handle) -> i32;
    pub fn lib_store(h: Handle, value: i32) -> i32;
    pub fn lib_restore(h: Handle, pvalue: &mut i32) -> i32;
}

I change a little bit your store() and restore() function to return an Result. I bet your C function do the same.

Stargateur
  • 24,473
  • 8
  • 65
  • 91
  • The design of foreign library is beyond my control. Similar techniques are used in Windows Cryptography NextGeneration subsystem (CNG). Take a look at [BCryptCreateHash](https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptcreatehash) function for example. I need to obtain the object size beforehand by calling [BCryptGetProperty](https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgetproperty) function and allocate proper space at my side. – Vasily Olekhov Oct 04 '19 at 09:37
  • 2
    I have used `Vec` (and `std::vector` in C++) as a generic allocation function, and I see no problem with that, particularly while the `Alloc` trait is unstable. – rodrigo Oct 04 '19 at 10:00