how to read arguments and return from a dll function in R

Viewed 99

I'm trying to load a dll into my R script. Dll is written in rust. I read in R Studio documentation that .Call passes integers as int * in C which i interpret as &i32 in rust (also assuming that mutability is just rust thing, and i don't have to make it &mut i32 if i don't intent to mutate it). However R kept on crashing the session, so i start doing the trial and error. Made this file and tried to load it (the base taken from this repo):

#![cfg(windows)]

use winapi::shared::minwindef;
use winapi::shared::minwindef::{BOOL, DWORD, HINSTANCE, LPVOID};
use winapi::um::consoleapi;

/// Entry point which will be called by the system once the DLL has been loaded
/// in the target process. Declaring this function is optional.
///
/// # Safety
///
/// What you can safely do inside here is very limited, see the Microsoft documentation
/// about "DllMain". Rust also doesn't officially support a "life before main()",
/// though it is unclear what that that means exactly for DllMain.
#[no_mangle]
#[allow(non_snake_case, unused_variables)]
extern "system" fn DllMain(
    dll_module: HINSTANCE,
    call_reason: DWORD,
    reserved: LPVOID)
    -> BOOL
{
    const DLL_PROCESS_ATTACH: DWORD = 1;
    const DLL_PROCESS_DETACH: DWORD = 0;

    match call_reason {
        DLL_PROCESS_ATTACH => demo_init(),
        DLL_PROCESS_DETACH => (),
        _ => ()
    }
    minwindef::TRUE
}

fn demo_init() {
    unsafe { consoleapi::AllocConsole() };
    println!("Hello, world!");
}

#[no_mangle]
extern "cdecl" fn seven_cdecl_u32() -> u32 {
    7
}

#[no_mangle]
extern "cdecl" fn seven_cdecl_u64() -> u64 {
    7
}

#[no_mangle]
extern "cdecl" fn seven_cdecl_i32() -> i32 {
    7
}

#[no_mangle]
extern "cdecl" fn seven_cdecl_i64() -> i64 {
    7
}





#[no_mangle]
extern "stdcall" fn seven_stdcall_u32() -> u32 {
    7
}

#[no_mangle]
extern "stdcall" fn seven_stdcall_u64() -> u64 {
    7
}

#[no_mangle]
extern "stdcall" fn seven_stdcall_i32() -> i32 {
    7
}

#[no_mangle]
extern "stdcall" fn seven_stdcall_i64() -> i64 {
    7
}





#[no_mangle]
extern "system" fn seven_system_u32() -> u32 {
    7
}

#[no_mangle]
extern "system" fn seven_system_i32() -> i32 {
    7
}

#[no_mangle]
extern "system" fn seven_system_u64() -> u64 {
    7
}

#[no_mangle]
extern "system" fn seven_system_i64() -> i64 {
    7
}





#[no_mangle]
extern "C" fn seven_c_u32() -> u32 {
    7
}

#[no_mangle]
extern "C" fn seven_c_i32() -> i32 {
    7
}

#[no_mangle]
extern "C" fn seven_c_u64() -> u64 {
    7
}

#[no_mangle]
extern "C" fn seven_c_i64() -> i64 {
    7
}
CWD = r"(C:\\Users\grass\Desktop\codes\R\dlload)"
dllname = paste(CWD,r"(\rdll.dll)", sep="")
print(getwd())

dyn.load(dllname)

#print(.Call("seven_cdecl_i32", pakage=dllname))
#print(.Call("seven_cdecl_u32", pakage=dllname))
#print(.Call("seven_cdecl_i64", pakage=dllname))
#print(.Call("seven_cdecl_u64", pakage=dllname))



#print(.Call("seven_stdcall_i32", pakage=dllname))
#print(.Call("seven_stdcall_u32", pakage=dllname))
#print(.Call("seven_stdcall_i64", pakage=dllname))
#print(.Call("seven_stdcall_u64", pakage=dllname))



#print(.Call("seven_system_i32", pakage=dllname))
#print(.Call("seven_system_u32", pakage=dllname))
#print(.Call("seven_system_i64", pakage=dllname))
#print(.Call("seven_system_u64", pakage=dllname))



#print(.Call("seven_c_i32", pakage=dllname))
#print(.Call("seven_c_u32", pakage=dllname))
#print(.Call("seven_c_i64", pakage=dllname))
#print(.Call("seven_c_u64", pakage=dllname))

I was commenting out line by line but it never worked. But the entry point did work, and the hello world was printed. When i try to print a value of integer i pass to function (7) i get some absolute garbage, which made me think that memory layout is different. I read that all values in R are vectors which changes the layout, but i assumed that .Call is designed with this in mind.

Finally the documentation in R Studio claims that for R unaware functions .C should be used, but i don't understand how to get return value from .C as it evaluates to a list of parameters and a package name.

If anyone can tell me how to properly get arguments in rust from R and return from rust to R I would be grateful.

1 Answers

So from the Rodrigo's comment I looked if i could mutate value passed instead of returning it. It seems that there are limited capabilities to pass opaque pointers, hence using this way to return structs is impossible. But I managed to take and mutate a string value, which is shown here:

use std::iter::{once, zip};

struct RString {
    base_ptr: *mut u8,
    len: usize,
}

impl From<*mut *mut u8> for RString {
    fn from(base_ptr: *mut *mut u8) -> Self {
        Self {
            base_ptr: unsafe { base_ptr.read() },
            len: {
                let mut off: isize = 0;
                while '\0' as u8 != unsafe { base_ptr.read().offset(off).read() } {
                    off += 1;
                }

                off as usize
            }
        }
    }
}

impl RString {
    pub fn value(&self) -> String {

        let mut buff: String = String::new();
        for off in 0..(self.len as isize) {
            buff.push(unsafe { self.base_ptr.clone().offset(off).read() } as char);
        }

        buff
    }

    pub fn edit(&mut self, new_value: String) {
        self.len = new_value.len();
        for (off, val) in (0..(self.len as isize)).zip(new_value.chars().chain(once(0_u8 as char))) {
            unsafe {self.base_ptr.clone().offset(off).write(val as u8)};
        }
    }
}


/// takes a single string argument <a>, returns "Hello <a>!"
#[no_mangle]
extern "system" fn meet_n_greet(nameptr: *mut *mut u8) {
    let mut rs: RString = RString::from(nameptr);
    println!("Hello {}!", rs.value());
    rs.edit(format!("Hello {}!", rs.value()));
}
CWD = r"(C:\\Users\grass\Desktop\codes\R\dlload)"
dllname = paste(CWD,r"(\rdll.dll)", sep="")

dyn.load(dllname)

print(.C("meet_n_greet", "Leroy Jenkins",package=dllname)[1])

dyn.unload(dllname)

The rust code is ugly and unsafe, but the example does work. This answer also does not solve the issue of opaque data so I'm just posting it to help people on their way.

Related