getditto/safer_ffi

Need a way to get a mutable char* argument in a callback function

Closed this issue · 2 comments

I am wondering how I get a C header that looks like this:

typedef struct callbacks {
    size_t (*display)(void const *, char *, size_t);
} callbacks_t;

as opposed to this:

typedef struct callbacks {
    size_t (*display)(void const *, int8_t *, size_t);
} callbacks_t;

Here is the Rust code:

#[ffi_export]
#[derive_ReprC]
#[repr(C)]
pub struct callbacks {
    display: extern "C" fn(*const c_void, *mut c_char, usize) -> usize,
}

Unfortunately

#[ffi_export]
#[derive_ReprC]
#[repr(C)]
pub struct callbacks {
    display: extern "C" fn(*const c_void, safer_ffi::char_p::char_p_raw, usize) -> usize,
}

produces a const buffer pointer

typedef struct callbacks {
    size_t (*display)(void const *, char const *, size_t);
} callbacks_t;

I am not sure if the better solution is to have a safer_ffi::char_p::char_p_raw_mut type, or if the best solution is to have a special case in the header generation code so that c_char gets mapped to char, rather than being reduced to its fundamental Rust type (i8). Or if I just missed some existing mechanism already in place to handle this.

I'm happy to do the implementation and whatever testing is required, but I want to know what you feel is the best solution.

Thank you.

This is related to this thread: https://users.rust-lang.org/t/safer-ffi-question-how-to-make-c-char-into-char-rather-than-int8-t/92929 although there isn't any substantial information in the thread that isn't also here.

Your update on the forum raised several important points. Agreed that relying on the C side to provide properly formatted UTF-8 is probably not a great idea. However, most C programmers can manage null-termination, and we kinda have to trust them not to overwrite the end of buffers. (often misplaced trust, but that's fundamental to C). So &mut [AsciiByte] is exactly what I'm hoping for.

char_p::Malloc sounds very useful, but orthogonal to the reasons I filed this issue. In my case, the buffer itself will be both allocated and dropped on the Rust side, so the allocator isn't my current hangup.

I'm looking for something a lot more like char_p::Ref (except mut) or char_p::Box (without ownership transfer), or perhaps even making the safer_ffi::c_char type public so I can expose a slice of them.

My use case follows a pattern like this:

pub struct Callbacks {
    display: extern "C" fn(*const c_void, *mut c_char, usize) -> usize,
}

struct MyType {
    callbacks: &'static Callbacks,
    payload: *mut c_void,
}

impl fmt::Display for MyType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut buffer = [0u8; 512];
        let bytes_written = (self.callbacks.display)(self.payload, buffer.as_mut_ptr().cast(), 512);
        let text = std::str::from_utf8(&buffer[0..bytes_written]).expect("Invalid UTF-8");
        write!(f, "{}", text)
    }
}

Thank you for all your help and for writing safer_ffi itself. If there is anything I can do to help with the implementation or testing please let me know.

Thanks again.

For the sake of speed, I've gone with exposing the c_char type, since it's a sensible thing to do anyways, and now you ought to be able to use *mut ::safer_ffi::c_char or c_slice::Mut<'_, ::safer_ffi::c_char> for your use case.

I'll still think about having a char_p::Mut<'_> variant would DerefMut to AsciiByte or something, it may just take more time to feature it