Search code examples
rustlabview

LabVIEW crashed when Rust deallocates CString


I am playing with LabVIEW 2019 64bit and Rust 1.64, but LabVIEW crashed on a very simple code.

What I am trying to do is passing a Hello from Rust string to LabVIEW and then deallocate it back in Rust.

use libc::c_char;
use std::ffi::CString;

#[no_mangle]
pub extern "cdecl" fn hello() -> *const c_char {
    CString::new("Hello from Rust")
        .expect("failed to create c string")
        .into_raw()
}

#[no_mangle]
pub extern "cdecl" fn bye(s: *mut c_char) {
    unsafe {drop(CString::from_raw(s));}
}

enter image description here

LabView can display correct string but freezes and then crashed when calling bye(s).

enter image description here

However, if I manually disable bye(s) function call, everything is all right, but it violates Rust suggestion:

alloc::ffi::c_str::CString
pub fn into_raw(self) -> *mut c_char
Consumes the CString and transfers ownership of the string to a C caller.

The pointer which this function returns must be returned to Rust and reconstituted using CString::from_raw to be properly deallocated. Specifically, one should not use the standard C free() function to deallocate this string.

Failure to call CString::from_raw will lead to a memory leak.

What is the correct way to perform this simple task?


Thanks to @srm , LabVIEW is working fine now. Here is the working block diagram:

enter image description here


Solution

  • The problem you have is that LabVIEW is receiving the C string and converting it to an L string (very different memory signature). The pink wire in LabVIEW is a pointer to a char[N] where N is the length of the string + 4 bytes... the first 4 bytes of the array is the length of the string. It's similar to a Pascal string but with 4 byte length instead of 1 byte length. The data structure that LV is consuming isn't the same one that you're passing out to bye(). You never get your hands on that raw pointer... with your current Call Library setup. So let's change your setup.

    Background: Remember that LabVIEW has, essentially, the same rules for memory management that Rust has -- namely that LabVIEW knows to allocate and deallocate data on a fixed schedule, and it expects to be in control of the memory management. LabVIEW does NOT have a garbage collector. It analyzes the dataflow and makes decisions about where to deterministically release data. But it also doesn't assume that data goes out of scope -- there is no "scope" for LabVIEW VIs. Ideally for LabVIEW, that pink wire will remain allocated for the next call of the VI on the presumption that the next call will also need the same string buffer.

    Solution: If you want Rust to allocate the string, you need to tell LabVIEW that what is being returned is just an address. So tell LabVIEW the return type is a pointer-size integer, not a string. Also return the length of the string.

    On the diagram, call your hello() function to return the integer and the length of the string. Then have another Call Library function to invoke the built-in function MoveBlock. https://www.ni.com/docs/en-US/bundle/labview/page/lvexcode/moveblock.html Set the library name to "LabVIEW" -- no file extension. Then make the first parameter be your pointer-sized integer. The second parameter is a LabVIEW array of bytes that you call Initialize Array and init to N characters where N is the length of the string -- configure the Call Library node to do arrays by pointer. The third parameter is the length of the string.

    MoveBlock will copy the bytes out of the C string allocated by Rust into the L string allocated by LabVIEW. You can then pass the pointer-sized integer into your bye() function.