use core_foundation_sys::base::{ kCFAllocatorDefault, kCFAllocatorNull, Boolean, CFIndex, CFRange, CFRelease, }; use core_foundation_sys::string::{ kCFStringEncodingUTF8, CFStringCreateWithBytes, CFStringCreateWithBytesNoCopy, CFStringGetBytes, CFStringGetLength, CFStringRef, }; use std::ffi::CString; pub fn cfstringref_from_static_string(string: &'static str) -> coreaudio_sys::CFStringRef { // Set deallocator to kCFAllocatorNull to prevent the the memory of the parameter `string` // from being released by CFRelease. We manage the string memory by ourselves. let cfstringref = unsafe { CFStringCreateWithBytesNoCopy( kCFAllocatorDefault, string.as_ptr(), string.len() as CFIndex, kCFStringEncodingUTF8, false as Boolean, kCFAllocatorNull, ) }; cfstringref as coreaudio_sys::CFStringRef } pub fn cfstringref_from_string(string: &str) -> coreaudio_sys::CFStringRef { let cfstringref = unsafe { CFStringCreateWithBytes( kCFAllocatorDefault, string.as_ptr(), string.len() as CFIndex, kCFStringEncodingUTF8, false as Boolean, ) }; cfstringref as coreaudio_sys::CFStringRef } #[derive(Debug)] pub struct StringRef(CFStringRef); impl StringRef { pub fn new(string_ref: CFStringRef) -> Self { assert!(!string_ref.is_null()); Self(string_ref) } pub fn into_string(self) -> String { self.to_string() } pub fn to_cstring(&self) -> CString { unsafe { // Assume that bytes doesn't contain `0` in the middle. CString::from_vec_unchecked(utf8_from_cfstringref(self.0)) } } pub fn into_cstring(self) -> CString { self.to_cstring() } pub fn get_raw(&self) -> CFStringRef { self.0 } } impl Drop for StringRef { fn drop(&mut self) { use std::os::raw::c_void; unsafe { CFRelease(self.0 as *mut c_void) }; } } impl std::fmt::Display for StringRef { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let string = String::from_utf8(utf8_from_cfstringref(self.0)).expect("convert bytes to a String"); write!(f, "{}", string) } } fn utf8_from_cfstringref(string_ref: CFStringRef) -> Vec { use std::ptr; assert!(!string_ref.is_null()); let length: CFIndex = unsafe { CFStringGetLength(string_ref) }; if length == 0 { return Vec::new(); } // Get the buffer size of the string. let range: CFRange = CFRange { location: 0, length, }; let mut size: CFIndex = 0; let mut converted_chars: CFIndex = unsafe { CFStringGetBytes( string_ref, range, kCFStringEncodingUTF8, 0, false as Boolean, ptr::null_mut() as *mut u8, 0, &mut size, ) }; assert!(converted_chars > 0 && size > 0); // Then, allocate the buffer with the required size and actually copy data into it. let mut buffer = vec![b'\x00'; size as usize]; converted_chars = unsafe { CFStringGetBytes( string_ref, range, kCFStringEncodingUTF8, 0, false as Boolean, buffer.as_mut_ptr(), size, ptr::null_mut() as *mut CFIndex, ) }; assert!(converted_chars > 0); buffer } #[cfg(test)] mod test { use super::*; #[test] fn test_create_static_cfstring_ref() { const STATIC_STRING: &str = "static string for testing"; let stringref = StringRef::new(cfstringref_from_static_string(STATIC_STRING) as CFStringRef); assert_eq!(STATIC_STRING, stringref.to_string()); assert_eq!( CString::new(STATIC_STRING).unwrap(), stringref.into_cstring() ); // TODO: Find a way to check the string's inner pointer is same. } #[test] fn test_create_static_empty_cfstring_ref() { const STATIC_EMPTY_STRING: &str = ""; let stringref = StringRef::new(cfstringref_from_static_string(STATIC_EMPTY_STRING) as CFStringRef); assert_eq!(STATIC_EMPTY_STRING, stringref.to_string()); assert_eq!( CString::new(STATIC_EMPTY_STRING).unwrap(), stringref.into_cstring() ); // TODO: Find a way to check the string's inner pointer is same. } #[test] fn test_create_cfstring_ref() { let expected = "Rustaceans 🦀"; let stringref = StringRef::new(cfstringref_from_string(expected) as CFStringRef); assert_eq!(expected, stringref.to_string()); assert_eq!(CString::new(expected).unwrap(), stringref.into_cstring()); // TODO: Find a way to check the string's inner pointer is different. } #[test] fn test_create_empty_cfstring_ref() { let expected = ""; let stringref = StringRef::new(cfstringref_from_string(expected) as CFStringRef); assert_eq!(expected, stringref.to_string()); assert_eq!(CString::new(expected).unwrap(), stringref.into_cstring()); // TODO: Find a way to check the string's inner pointer is different. } }