Skip to content

Instantly share code, notes, and snippets.

@ChunMinChang
Last active September 28, 2018 23:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ChunMinChang/25f3608c285f1abf2a5c289d5f758427 to your computer and use it in GitHub Desktop.
Save ChunMinChang/25f3608c285f1abf2a5c289d5f758427 to your computer and use it in GitHub Desktop.
Using tuple struct to wrap native C types
// 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);
}
// 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!
}
#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);
}
}
#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
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
// 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);
}
// 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