Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to free the memory of a string returned from Rust in C#?

I have these two functions exposed from Rust

extern crate libc;

use std::mem;
use std::ffi::{CString, CStr};

use libc::c_char;

pub static FFI_LIB_VERSION: &'static str = env!("CARGO_PKG_VERSION"); // '

#[no_mangle]
pub extern "C" fn rustffi_get_version() -> *const c_char {
    let s = CString::new(FFI_LIB_VERSION).unwrap();
    let p = s.as_ptr();
    mem::forget(s);

    p as *const _
}

#[no_mangle]
pub extern "C" fn rustffi_get_version_free(s: *mut c_char) {
    unsafe {
        if s.is_null() {
            return;
        }

        let c_str: &CStr = CStr::from_ptr(s);
        let bytes_len: usize = c_str.to_bytes_with_nul().len();
        let temp_vec: Vec<c_char> = Vec::from_raw_parts(s, bytes_len, bytes_len);
    }
}

fn main() {}

They are imported by C# as below

namespace rustFfiLibrary
{
    public class RustFfiApi
    {
        [DllImport("rustffilib.dll", EntryPoint = "rustffi_get_version")]
        public static extern string rustffi_get_version();

        [DllImport("rustffilib.dll", EntryPoint = "rustffi_get_version_free")]
        public static extern void rustffi_get_version_free(string s);

    }
}

The memory of the string returned from rustffi_get_version is not managed by Rust anymore as mem::forget has been called. In C#, I want to call the get version function, get the string, and then pass it back to Rust for memory deallocation like below.

public class RustService
{
    public static string GetVersion()
    {
        string temp = RustFfiApi.rustffi_get_version();
        string ver = (string)temp.Clone();

        RustFfiApi.rustffi_get_version_free(temp);

        return ver ;
    }
}

But the C# program crashes when it runs rustffi_get_version_free(temp). How to free the forgotten string memory in C#? What should be passed back to Rust for deallocation?

Instead of defining string as the argument in the C# extern, I changed it to pointer.

[DllImport("rustffilib.dll", EntryPoint = "rustffi_get_version")]
public static extern System.IntPtr rustffi_get_version();

[DllImport("rustffilib.dll", EntryPoint = "rustffi_get_version_free")]
public static extern void rustffi_get_version_free(System.IntPtr s);
public static string GetVersion()
{
    System.IntPtr tempPointer = RustFfiApi.rustffi_get_version();
    string tempString = Marshal.PtrToStringAnsi(tempPointer);

    string ver = (string)tempString.Clone();

    RustFfiApi.rustffi_get_version_free(tempPointer);

    return ver ;
}

The IntPtr from rustffi_get_version can be successfully converted to a C# managed string type. tempString and ver are good.

When rustffi_get_version_free(tempPointer) runs, it throws an exception saying stack unbalanced:

A call to PInvoke function 'rustFfiLibrary!rustFfiLibrary.RustFfiApi::rustffi_get_version_free' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.

sizeof(IntPtr) and sizeof(char *) are both 4 on my system. Plus, IntPtr works for return value; why doesn't it work as an input parameter?

like image 244
palazzo train Avatar asked Feb 09 '23 01:02

palazzo train


1 Answers

extern "C" fn in Rust means the function uses the C calling convention. C# expects P/Invoke functions to use the stdcall calling convention by default.

You can tell C# to use the C calling convention:

[DllImport("rustffilib.dll", CallingConvention = CallingConvention.Cdecl)]

Alternatively, you could use extern "stdcall" fn on the Rust side.

like image 122
Daniel Avatar answered Feb 16 '23 02:02

Daniel