How copy string to c-style WCHAR array?

To get WCHAR* pointer I can use:

fn to_vec(str: &str) -> Vec<u16> {
    return OsStr::new(str)
        .encode_wide()
        .chain(Some(0).into_iter())
        .collect();
}

fn to_wchar(str: &str) -> *const u16 {
    return to_vec(str).as_ptr();
}

To convert from WCHAR* I can use:

fn from_wide_string(s: &[u16]) -> String 
{ 
	use std::ffi::OsString; 
	use std::os::windows::ffi::OsStringExt; 
	let slice = s.split(|&v| v == 0).next().unwrap(); 
	OsString::from_wide(slice).to_string_lossy().into() 
}

To convert WCHAR array to string:

let face_name_ptr = &mut font_struct.lfFaceName as &[u16];
let str = from_wide_string(face_name_ptr);
println!("lfFaceName={}",str);

But how copy string or WCHAR* to WCHAR array and copy 16-bit zero at end and fit in N(=32) words?

You'll probably be interested in wio or widestring, both of which do the low-level conversions for you and provide a (slightly) safer interface than juggling the raw pointers yourself.

fn copy_to_c_array16(vec_from: Vec<u16>, n: usize, mut to_array: [u16; 32]) {
    use std::cmp;
    for i in 0..cmp::min(vec_from.len(), n - 1) {
        to_array[i] = vec_from[i];
    }
    to_array[n - 1] = 0;
}

I only can for len=32; How do it for any len? Maybe generic?

TL;DR: Does something like this help? We're working on a FFI guide to hopefully help teach people the correct way of doing these kinds of things in Rust. If it doesn't answer your question fully then feel free to create an issue so we can improve the document :slight_smile:

Basically if you want to write to a c-style WCHAR array your function needs to take a pointer to the first element and the length of the array. Then you use slice::from_raw_parts_mut() to turn that into a slice you can write to like normal.

The problem you're going to have with your implementation is you have dangling pointers. For example, your to_vec function takes a string slice then constructs a new vector which is stored as a temporary variable in the to_wchar function. You then return a pointer to that temporary value and then promptly deallocate the vector.

Usually when working with C arrays you'll either be passed a pointer to an array or string and treat it as a slice (i.e. you can use it, but don't own it). Then when you want to transfer an array of things from Rust to C, you tell the C code to give you a buffer and copy your values into that buffer.

If you directly pass a Rust Vec (or a pointer to its data with some_vec.as_ptr()) the Vec won't be deallocated properly and at best you'll have a memory leak. At worst, the C could think it's like any other array and call free() manually, messing with the allocator's internal book-keeping and most probably causing a double free.

I can make slice from pointers, but can't get mut pointers to modify slice:

pub type wchar_t = u16;
pub type WCHAR = ::wchar_t;
pub const LF_FACESIZE: usize = 32;
struct LOGFONTW {
    other_field: u8,
    lfFaceName: [::WCHAR; LF_FACESIZE],
}
pub type LPLOGFONTW = *mut LOGFONTW;

use std::mem;
fn main() {
    unsafe {
        let mut font_struct: LOGFONTW = mem::uninitialized();
        let face_name_ptr = (&font_struct.lfFaceName).as_mut_ptr();//<--ERROR
    }
}

Error is: cannot borrow immutable borrowed content as mutable [E0596]:
font_struct is mut !
It is error Rustc compiler?

You just need to borrow it mutably as well, ie ... (&mut font_struct.lfFaceName)...

1 Like

The libc crate contains Rust definitions for most C types on your platform, in particular it already has one for wchar_t. You should prefer to use that over writing your own typedefs because it is more reliable and will work cross-platform.

Hiding a type's raw pointer behind a typedef also feels like a code smell to me. It's very much a C-ism, whereas Rust usually prefers to make it explicit how you are borrowing/using your type. Plus there are multiple types of pointers in Rust, which means your LPLOGFONTW typedef won't be as useful.

1 Like

Why would you get those types from libc? These types obviously come from winapi (winapi::wingdi::LOGFONTW - Rust).

1 Like

I believe I said that in response to the OP's typedef for wchar_t. My point was more that you should use the typedefs already defined by official (and well tested) crates like libc and winapi than roll your own. So in this case if you need a definition for LOGFONTW then, as you point out, winapi is the place to get it from.

In particular it looks like the definition for LOGFONTW isn't correct. @AndrzejB declared it as

struct LOGFONTW {
    other_field: u8,
    lfFaceName: [::WCHAR; LF_FACESIZE],
}

Whereas winapi declares it to be

pub struct LOGFONTW {
    pub lfHeight: LONG,
    pub lfWidth: LONG,
    pub lfEscapement: LONG,
    pub lfOrientation: LONG,
    pub lfWeight: LONG,
    pub lfItalic: BYTE,
    pub lfUnderline: BYTE,
    pub lfStrikeOut: BYTE,
    pub lfCharSet: BYTE,
    pub lfOutPrecision: BYTE,
    pub lfClipPrecision: BYTE,
    pub lfQuality: BYTE,
    pub lfPitchAndFamily: BYTE,
    pub lfFaceName: [WCHAR; 32],
}
1 Like

When interacting with C code the concept of ownership still applies even if it's not encoded in the type system any more.

If you're going to create a string and maintain ownership of it, you can use Box::into_raw, at which point you won't de-allocate your memory by dropping the box, but to clean up the memory you have to call Box::from_raw and then drop the box (let the variable go out of scope) to clean up, once you know the C lib has finished with the data.

If the C library wants you to allocate data and then pass ownership to it, you need to use libc::malloc, to use the C allocator (rust and C allocators are not compatible and can never interact (the same library that creates it must free it)).

Also, because C libraries are not forced to encode ownership information in the type system, you are reliant on them putting it in the documentation (or reading through all the source code, since ownership is often a non-local issue). All I'll say is I've had mixed experiences on this.

Just shows you how rubbish C is for interacting with untrusted input. Ownership still exists, and if you don't have to think about it, you probably won't and so get remote code execution bugs.

Good luck! :slight_smile:

3 Likes