Last active
September 28, 2018 23:08
-
-
Save ChunMinChang/25f3608c285f1abf2a5c289d5f758427 to your computer and use it in GitHub Desktop.
Using tuple struct to wrap native C types
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A module containing all the converted types from external library. | |
mod sys { | |
use std::ffi::CString; | |
use std::os::raw::{c_char, c_void}; | |
pub type XString = *mut c_void; | |
extern "C" { | |
fn calloc(items: usize, size: usize) -> *mut c_void; | |
fn free(ptr: *mut c_void); | |
fn strcpy(dest: *mut c_char, src: *const c_char) -> *mut c_char; | |
} | |
pub fn get_xstring() -> XString { | |
let words = CString::new("💖 Hello, ⚙ Rustaceans 🦀!").unwrap(); | |
let size = words.as_bytes().len() + 1; // + 1 for '\0'. | |
let string = allocate_xstring(size); | |
copy_xstring(string, words.as_ptr()); | |
string | |
} | |
pub fn release_xstring(string: XString) { | |
if !string.is_null() { | |
unsafe { | |
free(string as *mut c_void); | |
} | |
} | |
} | |
pub fn allocate_xstring(size: usize) -> XString { | |
let ptr = unsafe { calloc(1, size) }; | |
ptr as XString | |
} | |
fn copy_xstring(dest: XString, src: *const c_char) { | |
unsafe { | |
strcpy(dest as *mut c_char, src); | |
} | |
} | |
} | |
// An adapter layer to call external library. | |
mod ext { | |
use super::sys; | |
use std::ffi::CString; | |
use std::os::raw::c_char; | |
pub fn get_string() -> sys::XString { | |
let string = sys::get_xstring(); | |
println!("[allocate: {:p}]", string); | |
string | |
} | |
pub fn release_string(string: sys::XString) { | |
println!("[release: {:p}]", string); | |
sys::release_xstring(string); | |
} | |
pub fn to_rust_string(string: sys::XString) -> String { | |
let cstring = unsafe { CString::from_raw(string as *mut c_char) }; | |
// In the implementation of `CString::from_raw()`, the `data` field of | |
// the `cstring` is just referred to the bytes of parameter `string`. | |
// After `release_string` above is called, the memory of `string` will | |
// be freed, hence the the `data` field of `cstring` will point to a | |
// freed memory chunk. To avoid that, we clone the whole memory in the | |
// converted rust string `rust_string`. Then we can make sure we have | |
// our own data. | |
// See implementation of `CString::from_raw()`: | |
// https://github.com/rust-lang/rust/blob/5a0d2961ce88f7db90b13771d1e8fc3b50ded7b1/src/libstd/ffi/c_str.rs#L408 | |
let rust_string = cstring.into_string().unwrap(); | |
rust_string.clone() | |
} | |
} | |
// In real world, this module may use the native `XString` type to call other | |
// FFI C APIs. | |
mod processor { | |
use super::{ext, sys}; | |
pub fn do_something(string: sys::XString) { | |
let rust_string = ext::to_rust_string(string); | |
// consume `string` here. | |
ext::release_string(string); | |
println!("consume \"{}\" here", rust_string); | |
} | |
} | |
fn main() { | |
let string = ext::get_string(); | |
let rust_string = ext::to_rust_string(string); | |
println!("Get a converted rust string: \"{}\"", rust_string); | |
processor::do_something(string); | |
// Undefined behavior! | |
let freed_string = string; | |
println!("(Use After Free) dangling pointer at {:p}", freed_string); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A module containing all the converted types from external library. | |
mod sys { | |
use std::ffi::CString; | |
use std::os::raw::{c_char, c_void}; | |
pub type XString = *mut c_void; | |
extern "C" { | |
fn calloc(items: usize, size: usize) -> *mut c_void; | |
fn free(ptr: *mut c_void); | |
fn strcpy(dest: *mut c_char, src: *const c_char) -> *mut c_char; | |
} | |
pub fn get_xstring() -> XString { | |
let words = CString::new("💖 Hello, ⚙ Rustaceans 🦀!").unwrap(); | |
let size = words.as_bytes().len() + 1; // + 1 for '\0'. | |
let string = allocate_xstring(size); | |
copy_xstring(string, words.as_ptr()); | |
string | |
} | |
pub fn release_xstring(string: XString) { | |
if !string.is_null() { | |
unsafe { | |
free(string as *mut c_void); | |
} | |
} | |
} | |
pub fn allocate_xstring(size: usize) -> XString { | |
let ptr = unsafe { calloc(1, size) }; | |
ptr as XString | |
} | |
fn copy_xstring(dest: XString, src: *const c_char) { | |
unsafe { | |
strcpy(dest as *mut c_char, src); | |
} | |
} | |
} | |
// An adapter layer to call external library. | |
mod ext { | |
use super::sys; | |
use std::ffi::CStr; | |
use std::fmt; | |
use std::os::raw::c_char; | |
#[derive(Debug)] | |
pub enum Error { | |
Empty, | |
} | |
// Creating a new type for XString provides the following benefits: | |
// 1. Remove all the dependencies from outer code to underlying native | |
// type XString and its relative native function calls. | |
// 2. Prevent dangling pointer from copying sys::XString values everywhere. | |
// The XString is integer type, so an function arguemnt with XString | |
// type will be copied instead of being moved into a function. However, | |
// they are actually memory address. To avoid geting an address pointing | |
// to a freed memory, introducing a new type can force the type to be | |
// moved rather than copied into a function. | |
pub struct XStringWrapper(sys::XString); | |
impl XStringWrapper { | |
pub fn new(string: sys::XString) -> Result<Self, Error> { | |
if string.is_null() { | |
Err(Error::Empty) | |
} else { | |
Ok(XStringWrapper(string)) | |
} | |
} | |
pub fn as_raw_type(&self) -> sys::XString { | |
self.0 | |
} | |
pub fn as_c_char_mut_ptr(&self) -> *mut c_char { | |
self.0 as *mut c_char | |
} | |
pub fn into_string(self) -> String { | |
self.to_string() | |
} | |
} | |
impl Drop for XStringWrapper { | |
fn drop(&mut self) { | |
release_string(self); | |
} | |
} | |
// This trait is automatically implemented for any type which implements the Display trait. | |
// See: https://doc.rust-lang.org/std/string/trait.ToString.html | |
// impl ToString for XStringWrapper { | |
// fn to_string(&self) -> String { | |
// unsafe { | |
// CStr::from_ptr(self.as_c_char_mut_ptr()).to_string_lossy().into_owned() | |
// } | |
// } | |
// } | |
// The ToString trait will be automatically implemented for XStringWrapper once we | |
// implement the Display trait. | |
impl fmt::Display for XStringWrapper { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
// Example to convert a raw c string to a rust string: | |
// https://doc.rust-lang.org/std/ffi/struct.CStr.html | |
// https://stackoverflow.com/questions/24145823/how-do-i-convert-a-c-string-into-a-rust-string-and-back-via-ffi | |
let s = unsafe { | |
CStr::from_ptr(self.as_c_char_mut_ptr()) | |
.to_string_lossy() | |
.into_owned() | |
}; | |
write!(f, "{}", s) | |
} | |
} | |
pub fn get_string() -> Result<XStringWrapper, Error> { | |
let string = sys::get_xstring(); | |
println!("[allocate: {:p}]", string); | |
XStringWrapper::new(string) | |
} | |
fn release_string(string: &XStringWrapper) { | |
println!("[release: {:p}]", string.as_raw_type()); | |
sys::release_xstring(string.as_raw_type()); | |
} | |
} | |
// In real world, this module may use the native `XString` type to call other | |
// FFI C APIs. | |
mod processor { | |
use super::ext::XStringWrapper; | |
pub fn do_something(wrapper: XStringWrapper) { | |
// consume `string` here by calling `into_string` | |
println!("consume \"{}\" here", wrapper.into_string()); | |
// `release_string` is called immediately. | |
// let moved_string = wrapper; // Compile error: value used here after move! | |
// `wrapper` is moved into this function, so another way to consume | |
// the `wrapper` is just to let it goes out the scope. Comment the | |
// `into_string` line above and uncomment the below to see when | |
// `release_string` is called. | |
// println!("consume \"{}\" here", wrapper.to_string()); | |
} | |
} | |
fn main() { | |
let wrapper = ext::get_string().unwrap(); | |
// let rust_string = wrapper.to_string(); | |
// println!("Get a converted rust string: \"{}\"", rust_string); | |
println!("Get a converted rust string: \"{}\"", wrapper); | |
processor::do_something(wrapper); | |
// let freed_string = wrapper; // Compile error: value used here after move! | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "ext.h" | |
#include <stdlib.h> // calloc, free | |
#include <string.h> // strcpy, strlen | |
// Private APIs | |
// ============================================================================ | |
XString allocate_xstring(size_t size) { | |
return (XString) calloc(1, size); | |
} | |
void copy_xstring(XString dest, const char* src) { | |
strcpy((char*)dest, src); | |
} | |
// Public APIs | |
// ============================================================================ | |
// In real word, you may get different XStrings by passing different parameters. | |
XString get_xstring() { | |
const char* words = "💖 Hello, ⚙ Rustaceans 🦀!"; | |
size_t size = strlen(words) + 1; // + 1 for '\0'. | |
XString str = allocate_xstring(size); | |
copy_xstring(str, words); | |
return str; | |
} | |
void release_xstring(XString str) { | |
if (str) { | |
free((void*) str); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef EXT_H | |
#define EXT_H | |
typedef void* XString; | |
// Returns a fixed string here. In real word, you may get different XStrings | |
// by passing different parameters. | |
XString get_xstring(); | |
// Free the memory occupied by the string. | |
void release_xstring(XString str); | |
#endif // EXT_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
all: | |
gcc -shared -fPIC ext.c -o libext.so | |
rustc problem.rs -L. | |
LD_LIBRARY_PATH=. ./problem | |
rustc solution.rs -L. | |
LD_LIBRARY_PATH=. ./solution | |
clean: | |
rm problem | |
rm solution | |
rm libext.so |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A module containing all the converted types from external library. | |
mod sys { | |
use std::os::raw::c_void; | |
pub type XString = *mut c_void; | |
#[link(name = "ext")] | |
extern "C" { | |
pub fn get_xstring() -> XString; | |
pub fn release_xstring(ptr: XString); | |
} | |
} | |
// An adapter layer to call external library. | |
mod ext { | |
use super::sys; | |
use std::ffi::CString; | |
use std::os::raw::c_char; | |
pub fn get_string() -> sys::XString { | |
let string = unsafe { sys::get_xstring() }; | |
println!("[allocate: {:p}]", string); | |
string | |
} | |
pub fn release_string(string: sys::XString) { | |
println!("[release: {:p}]", string); | |
unsafe { | |
sys::release_xstring(string); | |
}; | |
} | |
pub fn to_rust_string(string: sys::XString) -> String { | |
let cstring = unsafe { CString::from_raw(string as *mut c_char) }; | |
// In the implementation of `CString::from_raw()`, the `data` field of | |
// the `cstring` is just referred to the bytes of parameter `string`. | |
// After `release_string` above is called, the memory of `string` will | |
// be freed, hence the the `data` field of `cstring` will point to a | |
// freed memory chunk. To avoid that, we clone the whole memory in the | |
// converted rust string `rust_string`. Then we can make sure we have | |
// our own data. | |
// See implementation of `CString::from_raw()`: | |
// https://github.com/rust-lang/rust/blob/5a0d2961ce88f7db90b13771d1e8fc3b50ded7b1/src/libstd/ffi/c_str.rs#L408 | |
let rust_string = cstring.into_string().unwrap(); | |
rust_string.clone() | |
} | |
} | |
// In real world, this module may use the native `XString` type to call other | |
// FFI C APIs. | |
mod processor { | |
use super::{ext, sys}; | |
pub fn do_something(string: sys::XString) { | |
let rust_string = ext::to_rust_string(string); | |
// consume `string` here. | |
ext::release_string(string); | |
println!("consume \"{}\" here", rust_string); | |
} | |
} | |
fn main() { | |
let string = ext::get_string(); | |
let rust_string = ext::to_rust_string(string); | |
println!("Get a converted rust string: \"{}\"", rust_string); | |
processor::do_something(string); | |
// Undefined behavior! | |
let freed_string = string; | |
println!("(Use After Free) dangling pointer at {:p}", freed_string); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A module containing all the converted types from external library. | |
mod sys { | |
use std::os::raw::c_void; | |
pub type XString = *mut c_void; | |
#[link(name = "ext")] | |
extern "C" { | |
pub fn get_xstring() -> XString; | |
pub fn release_xstring(ptr: XString); | |
} | |
} | |
// An adapter layer to call external library. | |
mod ext { | |
use super::sys; | |
use std::ffi::CStr; | |
use std::fmt; | |
use std::os::raw::c_char; | |
#[derive(Debug)] | |
pub enum Error { | |
Empty, | |
} | |
// Creating a new type for XString provides the following benefits: | |
// 1. Remove all the dependencies from outer code to underlying native | |
// type XString and its relative native function calls. | |
// 2. Prevent dangling pointer from copying sys::XString values everywhere. | |
// The XString is integer type, so an function arguemnt with XString | |
// type will be copied instead of being moved into a function. However, | |
// they are actually memory address. To avoid geting an address pointing | |
// to a freed memory, introducing a new type can force the type to be | |
// moved rather than copied into a function. | |
pub struct XStringWrapper(sys::XString); | |
impl XStringWrapper { | |
pub fn new(string: sys::XString) -> Result<Self, Error> { | |
if string.is_null() { | |
Err(Error::Empty) | |
} else { | |
Ok(XStringWrapper(string)) | |
} | |
} | |
pub fn as_raw_type(&self) -> sys::XString { | |
self.0 | |
} | |
pub fn as_c_char_mut_ptr(&self) -> *mut c_char { | |
self.0 as *mut c_char | |
} | |
pub fn into_string(self) -> String { | |
self.to_string() | |
} | |
} | |
impl Drop for XStringWrapper { | |
fn drop(&mut self) { | |
release_string(self); | |
} | |
} | |
// This trait is automatically implemented for any type which implements the Display trait. | |
// See: https://doc.rust-lang.org/std/string/trait.ToString.html | |
// impl ToString for XStringWrapper { | |
// fn to_string(&self) -> String { | |
// unsafe { | |
// CStr::from_ptr(self.as_c_char_mut_ptr()).to_string_lossy().into_owned() | |
// } | |
// } | |
// } | |
// The ToString trait will be automatically implemented for XStringWrapper once we | |
// implement the Display trait. | |
impl fmt::Display for XStringWrapper { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
// Example to convert a raw c string to a rust string: | |
// https://doc.rust-lang.org/std/ffi/struct.CStr.html | |
// https://stackoverflow.com/questions/24145823/how-do-i-convert-a-c-string-into-a-rust-string-and-back-via-ffi | |
let s = unsafe { | |
CStr::from_ptr(self.as_c_char_mut_ptr()) | |
.to_string_lossy() | |
.into_owned() | |
}; | |
write!(f, "{}", s) | |
} | |
} | |
pub fn get_string() -> Result<XStringWrapper, Error> { | |
let string = unsafe { sys::get_xstring() }; | |
println!("[allocate: {:p}]", string); | |
XStringWrapper::new(string) | |
} | |
fn release_string(string: &XStringWrapper) { | |
println!("[release: {:p}]", string.as_raw_type()); | |
unsafe { | |
sys::release_xstring(string.as_raw_type()); | |
}; | |
} | |
} | |
// In real world, this module may use the native `XString` type to call other | |
// FFI C APIs. | |
mod processor { | |
use super::ext::XStringWrapper; | |
pub fn do_something(wrapper: XStringWrapper) { | |
// consume `string` here by calling `into_string` | |
println!("consume \"{}\" here", wrapper.into_string()); | |
// `release_string` is called immediately. | |
// let moved_string = wrapper; // Compile error: value used here after move! | |
// `wrapper` is moved into this function, so another way to consume | |
// the `wrapper` is just to let it goes out the scope. Comment the | |
// `into_string` line above and uncomment the below to see when | |
// `release_string` is called. | |
// println!("consume \"{}\" here", wrapper.to_string()); | |
} | |
} | |
fn main() { | |
let wrapper = ext::get_string().unwrap(); | |
// let rust_string = wrapper.to_string(); | |
// println!("Get a converted rust string: \"{}\"", rust_string); | |
println!("Get a converted rust string: \"{}\"", wrapper); | |
processor::do_something(wrapper); | |
// let freed_string = wrapper; // Compile error: value used here after move! | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment