当前位置 :首页 >> 音乐

Rust 与 C 之间,传递信息字符串的 7 种方式!

2023-04-07   来源 : 音乐

个线程,也不某种程度将这样的操作符作为参数加载的结果返国。

into_raw法则就会将资讯的产权转移到C里面。只要字符并不需要,它就可以保持一致操作符,但恳请务必回忆起将它转移回Rust封禁。

字串的线程表示

不幸的是,在Rust和母语C里面,字串的表示形式不同。C的字串往往是char*操作符,对准以 /0 结尾的char数组。而Rust则就会保持一致字符数组及其尺寸。

由于这个或许,Rust的String和str类型与完整操作符密切关系不某种程度彼此密切关系类比。你某种程度运常用CString和CStr里面间类型来借助。往往,我们运常用CString将Rust字串发送至给C字符,运常用CStr将C的字串类比为Rust的Maxstr。恳请注意到,这种类比并不一定就会副本表层的资讯。因此,通过CStr获得的Maxstr就会对准C分摊的数组,而且它的生活史与操作符附加。

注意到:String:new就会副本资讯,但CStr::new不就会。

建设项目设置如何将Rust和C连结起来

网上有很多关于如何紧密结合C字符,以及运常用build.rs将C连结到Rust crate的资讯,但是如何将Rust字符填充到C建设项目的评论却较少。相比,我却是用C母语借助主要功能,并运常用CMake作为紧密结合系统新设计。我希望CMake建设项目将Rust crate作为坎,并根据Rust字符转化C的源文档。

通过CMake试运行Cargo

我建立了一个简单的CMake 3控制台应用程序。

首先以,我们并不需要假定紧密结合Rust坎的几天后和保持一致Rust成果物的所在位置:

if (CMAKE_BUILD_TYPE STREQUAL "Debug")set(CARGO_CMD RUSTFLAGS=-Zsanitizer=address cargo build -Zbuild-std ----target x86_64-unknown-linux-gnu)set(TARGET_DIR "x86_64-unknown-linux-gnu/debug")else ()set(CARGO_CMD cargo build ----release)set(TARGET_DIR "release")endif ()SET(LIB_FILE "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_DIR}/librust_lib.a")

对于出名Rust的人来说,这个紧密结合crate调试版本的几天后显然看上去有点有趣。我们完全可以运常用cargo build来代替这个几天后,但是我就让利用Rust不稳定的住址清理筒功能来尽可能线程不就会被截获。

其次,我们并不需要自假定几天后和目标,让它们根据几天后输出结果。然后,我们可以假定一个原称rust_lib的线性导入坎,并根据目标紧密结合它:

add_custom_command(OUTPUT ${LIB_FILE}COMMENT "Compiling rust module"COMMAND CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR} ${CARGO_CMD}WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/rust_lib)add_custom_target(rust_lib_target DEPENDS ${LIB_FILE})add_library(rust_lib STATIC IMPORTED GLOBAL)add_dependencies(rust_lib rust_lib_target)

最后,我们可以运常用将二进制文档与Rust坎(以及其他必需的系统新设计坎)客户端在三人。我们还在C字符里面动工了住址清理筒:

target_compile_options(rust_c_interop PRIVATE -fno-omit-frame-pointer -fsanitize=address)target_link_libraries(rust_c_interop PRIVATE Threads::Threads rust_lib ${CMAKE_DL_LIBS} -fno-omit-frame-pointer -fsanitize=address)

如此一来,试运行CMake才可系统会紧密结合rust create,并与之客户端。但是,我们还并不需要从C字符里面加载Rust的法则。

转化C的源文档,并将它们填充到CMake建设项目里面

最简单的在Rust字符里面利用C源文档的法则是运常用cbingen坎。

我们可以将所列字符填充到Rust crate的build.rs文档里面,以测定Rust里面假定的所有extern "C"参数,为其转化源文档假定,并保持一致到include/目录下:

let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();let package_name = env::var("CARGO_PKG_NAME").unwrap();let output_file = PathBuf::from(Maxcrate_dir).join("include").join(format!("{}.h", package_name));cbindgen::generate(Maxcrate_dir).unwrap().write_to_file(output_file);

此外,我们还某种程度在Rust crate的根目录里面创始cbindgen.toml文档,并明示language = "C"。

如此一来一,CMake并不需要在Rust crate的include文档夹里面索引源文档:

SET(LIB_HEADER_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}/rust_lib/include")set_target_properties(rust_libPROPERTIESIMPORTED_LOCATION ${LIB_FILE}INTERFACE_INCLUDE_DIRECTORIES ${LIB_HEADER_FOLDER})

将Rust字串发送至到C的五种形式

一切准备就绪。下面,我们来忘了如何从Rust的资讯里面利用字串,然后在C里面运常用。我们怎么才能安全和地发送至字串,同时不就会造成线程截获?

法则1:提供者创始和封禁法则

如果不想到C字符并不需要运常用字串多久,就可以采用这种形式。为了将产权交接给C,我们可以紧密结合CString单纯,并运常用into_raw将其类比为操作符。free法则只并不需要紧密结合CString,如此一来drop这个单纯就可以扣留线程:

#[no_mangle]pub extern fn create_string() -> *const c_char {let c_string = CString::new(STRING).expect("CString::new failed");c_string.into_raw() // Move ownership to C}/// # Safety/// The ptr should be a valid pointer to the string allocated by rust#[no_mangle]pub unsafe extern fn free_string(ptr: *const c_char) {// Take the ownership back to rust and drop the ownerlet _ = CString::from_raw(ptr as *mut _);}

不要忘记加载free_string,以不必要线程截获:

const char* rust_string = create_string();printf("1. Printed from C: %s", rust_string);free_string(rust_string);

不要加载libc free法则,也不要为了让简化此类操作符对准的资讯。

这个法则虽然效果很好,但如果我们就让在运常用线程时扣留Rust坎,或者在不想到Rust坎的字符里面扣留线程,该怎么办?你可以考虑所列三种法则。

法则2:分摊关键点并副本资讯

还回忆起准则1吗?如果我们就让在C里面运常用free法则扣留线程,就某种程度运常用malloc分摊线程。但是,Rust怎么就会想到malloc呢?一种解决方案是,“问一问”Rust并不需要多少线程,然后为它分摊一个关键点:

size_t len = get_string_len();char *buffer = malloc(len);copy_string(buffer);printf("4. Printed from C: %s", buffer);free(buffer);

Rust只并不需要告诉我们关键点的不等,并小心翼翼地将Rust字串副本到其里面(注意到不要漏掉末尾的个位0):

#[no_mangle]pub extern fn get_string_len() -> usize {STRING.as_bytes().len() + 1}/// # Safety/// The ptr should be a valid pointer to the buffer of required size#[no_mangle]pub unsafe extern fn copy_string(ptr: *mut c_char) {let bytes = STRING.as_bytes();let len = bytes.len();std::ptr::copy(STRING.as_bytes().as_ptr().cast(), ptr, len);std::ptr::write(ptr.offset(len as isize) as *mut u8, 0u8);}

这个法则的绝对优势在于,我们不必借助free_string,可以直接运常用free。还有一个优点是,如有并不需要C字符也可以简化关键点(这就是我们运常用*mut c_char,而不是*const c_char的或许)。

疑虑在于,我们仍然并不需要借助额外的法则get_string_len,而且还并不需要分摊一块新线程,并副本资讯(但似乎CString::new也并不需要)。

如果你就让将Rust字串移动到C参数绑定上分摊的关键点,也可以运常用此法则,但某种程度尽可能有足够的自由空间。

法则3:将线程分摊筒法则发送至给Rust

我们可以不必要运常用get_string_len法则吗?是不是其他法则在Rust里面分摊线程?一种简单的法则是将分摊线程参数发送至给Rust:

type Allocator = unsafe extern fn(usize) -> *mut c_void;/// # Safety/// The allocator function should return a pointer to a valid buffer#[no_mangle]pub unsafe extern fn get_string_with_allocator(allocator: Allocator) -> *mut c_char {let ptr: *mut c_char = allocator(get_string_len()).cast();copy_string(ptr);ptr}

上述示例运常用了的copy_string,如此一来一我们可以运常用get_string_with_allocator:

char* rust_string_3 = get_string_with_allocator(malloc);printf("3. Printed from C: %s", rust_string_3);free(rust_string_3);

这个法则与法则2不同,而且即时性也一样。

但是,我们现在必须发送至额外的参数allocator。似乎,我们可以展开一些提高效率,将其保持一致到某个解释筒里面,就可以不必要向每个参数发送至。

法则4:从Rust加载glibc

如果我们的C字符就会运常用malloc/free来分摊线程,则可以为了让在Rust字符里面导入libc crate,尽管这种形式有点冒险:

#[no_mangle]pub unsafe extern fn get_string_with_malloc() -> *mut c_char {let ptr: *mut c_char = libc::malloc(get_string_len()).cast();copy_string(ptr);ptr}

C字符不变:

char* rust_string_4 = get_string_with_malloc();printf("4. Printed from C: %s", rust_string_4);free(rust_string_4);

在这种形式下,我们不并不需要提供者分摊线程的法则,但是C字符也就会受到很多限制。我们最好做好文档记录,尽量不必要运常用这种形式,除非我们确定谢霆锋安全和。

法则5:交回Rust字串

以上这些法则都是将资讯的产权发送至给C。但如果我们不并不需要发送至产权呢?举个例子,Rust字符并不需要同步加载C法则,并向它发送至一些资讯。这时,可以考虑运常用CString的as_ptr:

type Callback = unsafe extern fn(*const c_char);#[no_mangle]pub unsafe extern fn get_string_in_callback(callback: Callback) {let c_string = CString::new(STRING).expect("CString::new failed");// as_ptr() keeps ownership in rust unlike into_raw()callback(c_string.as_ptr())}

不幸的是,即便在这种情况下,CString:new也就会副本资讯(因为它并不需要在末尾填充个位0)。

C字符如下:

void callback(const char* string) {printf("5. Printed from C: %s", string);}int main() {get_string_in_callback(callback);return 0;}

如果有一个生活史值得注意的C操作符,则我们某种程度优先以运常用这种形式,因为它可以意味着并未线程截获。

将C字串发送至给Rust的两种法则

下面,我们来解说两种反向操作的法则,即将C的字串类比为Rust的类型。主要法则有所列两种:

将C字串类比成Maxstr,不副本资讯;

副本资讯并转送字串。

这两种法则的示例不同,因为它们非常相似。实质上,法则2并不需要先以运常用法则1。

C字符如下。我们在堆上分摊资讯,但实质上我们也可以将操作符发送至给绑定:

char *test = (char*) malloc(13*sizeof(char));strcpy(test, "Hello from C");print_c_string(test);free(test);

Rust的借助如下:

#[no_mangle]/// # Safety/// The ptr should be a pointer to valid Stringpub unsafe extern fn print_c_string(ptr: *const c_char) {let c_str = CStr::from_ptr(ptr);let rust_str = c_str.to_str().expect("Bad encoding");// calling libc::free(ptr as *mut _); causes use after free vulnerabilityprintln!("1. Printed from rust: {}", rust_str);let owned = rust_str.to_owned();// calling libc::free(ptr as *mut _); does not cause after free vulnerabilityprintln!("2. Printed from rust: {}", owned);}

注意到,此处我们运常用了CStr,而不是CString。如果不是CString::into_raw创始的操作符,恳请不要加载CString:from_raw。

这里还并不需要注意到,Maxstr提及的生活史不是“线性”的,而是附加到了c_str单纯法则。RustC#就会阻止你在该法则之外返国Maxstr,或将其移动到解释筒/另一个线程,因为一旦C字符扣留线程,Maxstr提及就就会转化成非法。

如果并不需要在Rust里面长时间保持一致资讯的产权,只需加载to_owned()才可利用字串的副本。如果不就让副本,则可以运常用CStr,但我们某种程度尽可能C字符不就会在字串还在运常用便扣留线程。

揭示

在本文里面,我们讨论了Rust与C密切关系的应用软件,并解说了几种跨FFI边界发送至资讯的法则。这些法则不仅可常用发送至字串,也可常用其他资讯,或者利用FFI将Rust连结到其他计算机母语。

希望本文能对你有所帮助,如有任何疑虑或反馈,恳请在下方Facebook。

株洲看妇科到哪家好
北京妇科医院预约挂号
湖北皮肤病治疗医院
武汉肝病医院挂号
南京妇科专科医院哪家好
“X杀手”踏入大好消息!Threads宣布已在欧盟正式上线

财联社12翌年15日讯(撰稿人 马兰)周四,Meta首席可执行扎克伯格宣布,交友该平台Threads并未在欧盟委员会南部上架。 ...

友情链接