From 8c240f514e17c9a935f5f392e6a87779ae1c09e8 Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Fri, 27 Aug 2021 14:49:22 +0200 Subject: [PATCH] Call Rust function from C: Complex cases Add three functions which take/return strings and string arrays. This creates some interesting problems how to combine the C semantics of returning a char** with the Rust semantics of freeing objects once they go out of scope. This now also relies on the Rust standard library, which needs a few system libraries. Link them. --- call-rust-from-c/Makefile | 3 ++- call-rust-from-c/src/lib.rs | 54 +++++++++++++++++++++++++++++++++++++ call-rust-from-c/src/main.c | 14 ++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/call-rust-from-c/Makefile b/call-rust-from-c/Makefile index 66a951a..672874b 100644 --- a/call-rust-from-c/Makefile +++ b/call-rust-from-c/Makefile @@ -1,7 +1,8 @@ RLIB = target/debug/libcall_rust_from_c.a +LIBS = -lpthread -ldl main: src/main.o $(RLIB) - $(CC) -Wall -o $@ $^ + $(CC) -Wall -o $@ $^ $(LIBS) src/main.o: src/rustlib.h diff --git a/call-rust-from-c/src/lib.rs b/call-rust-from-c/src/lib.rs index f5e4384..6a87bea 100644 --- a/call-rust-from-c/src/lib.rs +++ b/call-rust-from-c/src/lib.rs @@ -1,3 +1,4 @@ +use std::ffi::{CStr, CString}; use std::os::raw; /// Return The Answer. @@ -14,6 +15,54 @@ pub extern "C" fn answer() -> raw::c_int { return 42; } +#[no_mangle] +pub extern "C" fn r_strlen(s: *const raw::c_char) -> usize { + unsafe { CStr::from_ptr(s) }.to_bytes().len() +} + +/// Return a vector of pointers as C pointer array +/// +/// The memory of `vec` gets leaked, as otherwise Rust would free it once the vector +/// goes out of scope, and C would access invalid memory. +fn return_c_vec(mut vec: Vec) -> *mut T { + let p = vec.as_mut_ptr(); + // unref vector so that it does not get freed when going out of scope + std::mem::forget(vec); + p +} + +#[no_mangle] +pub extern "C" fn r_strlist() -> *mut *const raw::c_uchar { + let v = vec![ + "Hello\0".as_ptr(), + "World\0".as_ptr() + ]; + return_c_vec(v) +} + +fn impl_grep<'a>(needle: &str, haystack: &'a str) -> Vec<&'a str> { + haystack.lines() + .filter(|line| line.contains(needle)) + .collect() +} + +#[no_mangle] +pub extern "C" fn r_grep(needle: *const raw::c_char, haystack: *const raw::c_char) -> *mut *const raw::c_char { + let haystack_cstr = unsafe { CStr::from_ptr(haystack).to_str() }.unwrap(); + let strvec = impl_grep(unsafe { CStr::from_ptr(needle).to_str().unwrap() }, haystack_cstr); + + // Vec[str] -> Vec[const char*] + let p_vec: Vec<_> = strvec.into_iter() + .map(|s| { + let s = CString::new(s).unwrap(); + let p = s.as_ptr(); + std::mem::forget(s); + p + }) + .collect(); + + return_c_vec(p_vec) +} #[cfg(test)] mod tests { @@ -23,4 +72,9 @@ mod tests { fn test_answer() { assert_eq!(answer(), 42); } + + #[test] + fn test_grep() { + assert_eq!(impl_grep("ell", "Hello\nworld\ncan you tell?"), vec!["Hello", "can you tell?"]); + } } diff --git a/call-rust-from-c/src/main.c b/call-rust-from-c/src/main.c index b514433..a8f7dbc 100644 --- a/call-rust-from-c/src/main.c +++ b/call-rust-from-c/src/main.c @@ -3,6 +3,20 @@ int main() { + const char *hello = "hello"; + const char *haystack = "Hello\nworld\ncan you tell?"; + printf("The answer: %i\n", answer()); + printf("Length of '%s': %u\n", hello, r_strlen(hello)); + + const unsigned char **strs = r_strlist(); + for (const unsigned char **s = strs; *s; s++) + printf("returned string array element: %s\n", *s); + free (strs); + + const char **matches = r_grep("ell", "Hello\nworld\ncan you tell?"); + for (const char **m = matches; *m; m++) + printf("matched line: %s\n", *m); + free (matches); return 0; } -- 2.39.5