使用可变字符串调用GetUserName WinAPI函数不会填充字符串

Del*_*ore 1 winapi rust

这似乎部分工作,但我无法获得要打印的字符串值

pub fn test() {
    let mut buf: Vec<u16> = vec![0; 64];
    let mut sz: DWORD = 0;
    unsafe {
        advapi32::GetUserNameW(buf.as_mut_ptr(), &mut sz);
    }
    let str1 = OsString::from_wide(&buf).into_string().unwrap();
    println!("Here: {} {}", sz, str1);
}
Run Code Online (Sandbox Code Playgroud)

打印:

Here: 10
Run Code Online (Sandbox Code Playgroud)

当我希望它也打印

Here: 10 <username>
Run Code Online (Sandbox Code Playgroud)

作为测试,C版

TCHAR buf[100];
DWORD sz;
GetUserName(buf, &sz);
Run Code Online (Sandbox Code Playgroud)

似乎buf很好.

She*_*ter 10

GetUserName

您应该重新阅读API文档GetUserName以回忆参数的工作原理:

lpnSize [in, out]

在输入时,此变量lpBufferTCHARs 为单位指定缓冲区的大小 .在输出时,变量接收TCHAR复制到缓冲区的s 数,包括终止空字符.如果lpBuffer太小,则函数失败并 GetLastError返回ERROR_INSUFFICIENT_BUFFER.此参数接收所需的缓冲区大小,包括终止空字符.

TL; DR:

  • 输入时:调用者告诉API缓冲区有多少个空格.
  • 成功时:API告诉调用者使用了多少空格.
  • 失败时:API告诉调用者需要多少空格.

C版

这有一个固定大小的堆栈分配数组,为100 TCHAR秒.

此代码已损坏且不安全,因为sz未初始化.这允许API将未定义数量的字符写入仅长100的缓冲区.如果用户名超过100个字符,那么您刚刚在程序中引入了一个安全漏洞.

锈版

Rust代码以更好的方式被破坏.sz设置为零,这意味着"您可以写入零数据条目",因此它会写入零条目.因此,Vec缓冲区充满零,结果字符串为空.报告的缓冲区太小而无法接收用户名,因此GetUserNameW设置sz为缓冲区需要分配的字符数.

该怎么办

一个"修复"是设置sz为数组的长度.但是,这可能会使缓冲区分配过多或过少.

如果您对截断的字符串没问题(我不确定TCHAR字符串是否可以任意拆分,我知道UTF-8不能),那么最好使用像C代码那样的固定大小的数组.

如果您想更合适地分配内存来调用这种类型的WinAPI函数,请参阅分配数据以传递给FFI调用的正确方法是什么?.

extern crate advapi32;
extern crate winapi;

use std::ptr;

fn get_user_name() -> String {
    unsafe {
        let mut size = 0;
        let retval = advapi32::GetUserNameW(ptr::null_mut(), &mut size);
        assert_eq!(retval, 0, "Should have failed");

        let mut username = Vec::with_capacity(size as usize);
        let retval = advapi32::GetUserNameW(username.as_mut_ptr(), &mut size);
        assert_ne!(retval, 0, "Perform better error handling");
        assert!((size as usize) <= username.capacity());
        username.set_len(size as usize);

        // Beware: This leaves the trailing NUL character in the final string,
        // you may want to remove it!
        String::from_utf16(&username).unwrap()
    }
}

fn main() {
    println!("{:?}", get_user_name()); // "IEUser\u{0}"
}
Run Code Online (Sandbox Code Playgroud)