WiresmithTech / Rust-LabVIEW-Interop

A crate for creating libraries in Rust that are called from LabVIEW
MIT License
7 stars 4 forks source link

Owned Handles #6

Closed JamesMc86 closed 4 months ago

JamesMc86 commented 1 year ago

Right now we have a Handle type which assumes no ownership - just a wrapper for the FFI layer.

However what if we need to create handles or take ownership of handles i.e. we need to clean them up.

I suggest an OwnedHandle<T> type. This would have:

jkneer commented 6 months ago

Just chiming in: I would love to see this implemented.

JamesMc86 commented 6 months ago

Thanks for letting me know.

What are you hoping to use them for? I'm trying to understand the ownership a bit.

Would this be for LabVIEW calling Rust or Rust calling LabVIEW? If you can share the flow of data you are looking for it would help with the design.

jkneer commented 6 months ago

I have a hardware device that takes callbacks and fires them for specific events. I would like to have the callbacks send a LVUserEvent with a Cluster(int,LStr).

Current setup LV init: call init function, hand over 1. value/pointer to cluster type, 2. UserEvent Ref

Rust: init function defines a callback closure and needs to move the received data into the closure.

But, I can't get this to work:

So I think what may work, is owning the datacluster and instantiating it in rust. Having a copy trait would offer another alternative.

Here is some exemplary rust code (does not compile) to support the discussion. The simple version with the event should work, but I cannot get the more complex version of to work, even if I hand over the cluster by value from LV.

// Include necessary dependencies
use labview_interop::{labview_layout, sync::LVUserEvent, types::string::LStrHandle};
use std::{
    sync::{Arc, Mutex},
    thread,
    time::Duration,
};

// Define MyCluster with the labview_layout macro
labview_layout! {
    struct MyCluster {
        int_val: i32,
        str_val: LStrHandle,
    }
}

// Type alias for the closure
type Callback = Arc<Mutex<Option<Box<dyn FnMut(i32) + Send>>>>;

lazy_static::lazy_static! {
    static ref CALLBACK: Callback = Arc::new(Mutex::new(None));
}

// Extern C setup function for simple callback
#[no_mangle]
pub extern "C" fn setup_simple(event: LVUserEvent<i32>) {
    let mut callback = CALLBACK.lock().unwrap();
    *callback = Some(Box::new(move |mut value: i32| {
        // Send value to LabVIEW
        event.post(&mut value).unwrap();
    }));
}

// Extern C setup function for cluster callback
#[no_mangle]
pub extern "C" fn setup_cluster(event: LVUserEvent<MyCluster>) {
    let mut callback = CALLBACK.lock().unwrap();
    *callback = Some(Box::new(move |value: i32| {

        // Create MyCluster instance -- not supported
        let cluster = MyCluster {
            int_val: value,
            str_val: LStrHandle::new("Hello from Rust!"),
        };
        // Send MyCluster to LabVIEW
        event.post(&mut cluster).unwrap();
    }));
}

// Extern C spin function to fire the callback every 0.5 seconds
#[no_mangle]
pub extern "C" fn spin() {
    let callback = CALLBACK.clone();
    thread::spawn(move || {
        loop {
            {
                let mut cb = callback.lock().unwrap();
                if let Some(ref mut cb) = *cb {
                    cb(42); // Example value
                }
            }
            thread::sleep(Duration::from_millis(500));
        }
    });
}

// ...
JamesMc86 commented 4 months ago

Closing this issue as I think we have all of the pieces in place now.