3
votes

I'm wrapping a C API which allows the caller to set/get an arbitrary pointer via function calls. In this way, the C API allows a caller to associate arbitrary data with one of the C API objects. This data is not used in any callbacks, it's just a pointer that a user can stash away and get at later.

My wrapper struct implements the Drop trait for the C object that contains this pointer. What I'd like to be able to do, but am not sure it's possible, is have the data dropped correctly if the pointer is not null when the wrapper struct drops. I'm not sure how I would recover the correct type though from a raw c_void pointer.

Two alternatives I'm thinking of are

  1. Implement the behavior of these two calls in the wrapper. Don't make any calls to the C API.
  2. Don't attempt to offer any kind of safer interface to these functions. Document that the pointer must be managed by the caller of the wrapper.

Is what I want to do possible? If not, is there a generally accepted practice for these kinds of situations?

2

2 Answers

3
votes

A naive + fully automatic approach is NOT possible for the following reasons:

  • freeing memory does not call drop/deconstructors/...: the C API can be used from languages which can have objects which should be deconstructed properly, e.g. C++ or Rust itself. So when you only store a memory pointer you do not know you to call the proper function (you neither know which function not how the calling conventions look like).

  • which memory allocator?: memory allocation and deallocation isn't a trivial thing. your program needs to request memory from the OS and then manage this resources in an intelligent way to be efficient and correct. This is usually done by a library. In case of Rust, jemalloc is used (but can be changed). So even when you ask the API caller to only pass Plain Old Data (which should be easier to destruct) you still don't know which library function to call to deallocate memory. Just using libc::free won't work (it can but it could horrible fail).

Solutions:

  • dealloc callback: you can ask the API user to set an additional pointer to, let's say a void destruct(void* ptr) function. If this one is not NULL, you call that function during your drop. You could also use int as an return type to signal when the destruction went wrong. In that case you could for example panic!.

  • global callback: let's assume you requested your user to only pass POD (plain old data). To know which free function of the memory allocator to call, you could request the user to register a global void (*free)(void* ptr) pointer which is called during drop. You could also make that one optional.

2
votes

Although I was able to follow the advice in this thread, I wasn't entirely satisfied with my results, so I asked the question on the Rust forums and found the answer I was really looking for. (play)

use std::any::Any;

static mut foreign_ptr: *mut () = 0 as *mut ();

unsafe fn api_set_fp(ptr: *mut ()) {
    foreign_ptr = ptr;
}

unsafe fn api_get_fp() -> *mut() {
    foreign_ptr
}

struct ApiWrapper {}

impl ApiWrapper {
    fn set_foreign<T: Any>(&mut self, value: Box<T>) {
        self.free_foreign();
        unsafe {
            let raw = Box::into_raw(Box::new(value as Box<Any>));
            api_set_fp(raw as *mut ());
        }
    }

    fn get_foreign_ref<T: Any>(&self) -> Option<&T> {
        unsafe {
            let raw = api_get_fp() as *const Box<Any>;
            if !raw.is_null() {
                let b: &Box<Any> = &*raw;
                b.downcast_ref()
            } else {
                None
            }
        }
    }

    fn get_foreign_mut<T: Any>(&mut self) -> Option<&mut T> {
        unsafe {
            let raw = api_get_fp() as *mut Box<Any>;
            if !raw.is_null() {
                let b: &mut Box<Any> = &mut *raw;
                b.downcast_mut()
            } else {
                None
            }
        }
    }

    fn free_foreign(&mut self) {
        unsafe {
            let raw = api_get_fp() as *mut Box<Any>;
            if !raw.is_null() {
                Box::from_raw(raw);
            }
        }
    }
}

impl Drop for ApiWrapper {
    fn drop(&mut self) {
        self.free_foreign();
    }
}

struct MyData {
    i: i32,
}

impl Drop for MyData {
    fn drop(&mut self) {
        println!("Dropping MyData with value {}", self.i);
    }
}

fn main() {
    let p1 = Box::new(MyData {i: 1});
    let mut api = ApiWrapper{};
    api.set_foreign(p1);
    {
        let p2 = api.get_foreign_ref::<MyData>().unwrap();
        println!("i is {}", p2.i);
    }
    api.set_foreign(Box::new("Hello!"));
    {
        let p3 = api.get_foreign_ref::<&'static str>().unwrap();
        println!("payload is {}", p3);
    }
}