Rust binding to windows

I will use the code below as reference (from rust-installer/src/remove_dir_all.rs at master · rust-lang/rust-installer · GitHub)

fn rename(&self, new: &Path, replace: bool) -> io::Result<()> {
            // &self must be opened with DELETE permission
            use std::iter;
            #[cfg(target_arch = "x86")]
            const STRUCT_SIZE: usize = 12;
            #[cfg(target_arch = "x86_64")]
            const STRUCT_SIZE: usize = 20;

            // FIXME: check for internal NULs in 'new'
            let mut data: Vec<u16> = iter::repeat(0u16)
                .take(STRUCT_SIZE / 2)
                .chain(new.as_os_str().encode_wide())
                .collect();
            data.push(0);
            let size = data.len() * 2;

            unsafe {
                // Thanks to alignment guarantees on Windows this works
                // (8 for 32-bit and 16 for 64-bit)
                let info = data.as_mut_ptr() as *mut FILE_RENAME_INFO;
                // The type of ReplaceIfExists is BOOL, but it actually expects a
                // BOOLEAN. This means true is -1, not c::TRUE.
                (*info).ReplaceIfExists = if replace { -1 } else { FALSE };
                (*info).RootDirectory = ptr::null_mut();
                (*info).FileNameLength = (size - STRUCT_SIZE) as DWORD;
                cvt(SetFileInformationByHandle(
                    self.handle().raw(),
                    FileRenameInfo,
                    data.as_mut_ptr() as *mut _ as *mut _,
                    size as DWORD,
                ))?;
                Ok(())
            }
        }

this code uses winapi which declare FILE_RENAME_INFO as

pub struct FILE_RENAME_INFO {
    pub ReplaceIfExists: BOOL,
    pub RootDirectory: HANDLE,
    pub FileNameLength: DWORD,
    pub FileName: [WCHAR; 1],
}

while the Windows API FILE_RENAME_INFO (winbase.h) - Win32 apps | Microsoft Learn declare it as such

typedef struct _FILE_RENAME_INFO {
    union {
        BOOLEAN ReplaceIfExists;
        DWORD   Flags;
    } DUMMYUNIONNAME;
    BOOLEAN ReplaceIfExists;
    HANDLE  RootDirectory;
    DWORD   FileNameLength;
    WCHAR   FileName[1];
} FILE_RENAME_INFO, *PFILE_RENAME_INFO;

Notice the extra union

(The same apply with the windows crate that declare the union but not the BOOLEAN ReplaceIfExists;)

my question is:

How does the above code works while the struct are different ?
How could the struct size be 20 (on x64) while I count only 16 ?

Any explanation on this would make my day ^^

1 Like

There's indeed something a little odd about this.

The way the windows-sys crate works (which the windows crate is built on top of) is that it's generated from a massive API database called win32metadata. This database, and its conversion, is not perfect. In particular, I think a lot of preprocessor stuff needs to be manually implememented into the win32metadata tools. I'm pretty sure that the online docs are generated from the database as well. There have been defects in the conversion previously, and it's definitely not unthinkable that this is another one.

Going by the theory that the safest assumption to make is that what's in the actual SDK is the Proper Thing™️: Writing FILE_RENAME_INFO in a source file and telling Visual Studio to show its definition reveals the following:

typedef struct _FILE_RENAME_INFO {
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN10_RS1)
    union {
        BOOLEAN ReplaceIfExists;  // FileRenameInfo
        DWORD Flags;              // FileRenameInfoEx
    } DUMMYUNIONNAME;
#else
    BOOLEAN ReplaceIfExists;
#endif
    HANDLE RootDirectory;
    DWORD FileNameLength;
    WCHAR FileName[1];
} FILE_RENAME_INFO, *PFILE_RENAME_INFO;

Mystery solved? It looks like the win32metadata database/tools are not equipped to handle these particular preprocessor conditionals. It's a little annoying that they don't at least make sure that their own online documentation is properly generated.

The windows-sys crate uses:

#[repr(C)]
pub struct FILE_RENAME_INFO {
    pub Anonymous: FILE_RENAME_INFO_0,
    pub RootDirectory: HANDLE,
    pub FileNameLength: u32,
    pub FileName: [u16; 1],
}

#[repr(C)]
pub union FILE_RENAME_INFO_0 {
    pub ReplaceIfExists: BOOLEAN,
    pub Flags: u32,
}

I assume that the reason winapi one works is due to #[repr(C)] causing padding to occur at the ReplaceIfExists, so it ends up being the proper size?

As long as you don't trust the documentation, there are no mysteries. :slight_smile:

2 Likes

Just to clarify something. The C header given above is, after evaluating the #if two structs. An older one (pre-WIN10_RS!) and a newer one.

Older:

typedef struct _FILE_RENAME_INFO {
    BOOLEAN ReplaceIfExists;
    HANDLE RootDirectory;
    DWORD FileNameLength;
    WCHAR FileName[1];
} FILE_RENAME_INFO, *PFILE_RENAME_INFO;

Newer:

typedef struct _FILE_RENAME_INFO {
    union {
        BOOLEAN ReplaceIfExists;  // FileRenameInfo
        DWORD Flags;              // FileRenameInfoEx
    } DUMMYUNIONNAME;
    HANDLE RootDirectory;
    DWORD FileNameLength;
    WCHAR FileName[1];
} FILE_RENAME_INFO, *PFILE_RENAME_INFO;

From this it's clearer that winapi is using the older definition, which makes sense because it stopped being updated years ago.

The windows and windows-sys crates uses the more modern definition.

The reason the two structs are the same size is because of size and alignment. RootDirectory is a HANDLE which has an alignment of 8 on 64-bit applications and 4 on 32-bit applications. So it must be aligned to at least 4. A BOOLEAN is 1 byte so in the older version of the struct there's at least 3 bytes "wasted" in order to align RootDirectory. The newer struct makes use of thoes bytes.

2 Likes