将指向16位Unicode字符的原始指针转换为Rust文件路径

5

我正在将一个用C++编写的DLL替换为一个用Rust编写的DLL。 目前,DLL中的函数被如下调用:

BOOL calledFunction(wchar_t* pFileName)

我认为在这个上下文中,wchar_t是一个16位Unicode字符,因此我选择在我的Rust DLL中公开以下函数:

pub fn calledFunction(pFileName: *const u16)

什么是将这个原始指针转换为我可以用来从Rust DLL打开文件的最佳方式?

4
String::from_utf16 是 Rust 中的一个方法,用于将 UTF-16 编码的数据转换为字符串类型。 - Boiethios
这个答案指向了我之前已经考虑过的 Rust 文档:https://doc.rust-lang.org/std/string/struct.String.html#method.from_utf16。但是,如何将这个原始指针转换为 &[u16]? - watts
3
在这个上下文中,我相信wchar_t是一个16位的Unicode字符。请不要猜测,因为您的猜测在各种平台上都是错误的。相反,请使用正确定义适当目标平台的libc::wchar_t - Shepmaster
@Shepmaster 实际上,原帖是正确的。在Windows上,wchar_t是16位的:https://msdn.microsoft.com/zh-cn/library/windows/desktop/aa367308(v=vs.85).aspx - Boiethios
1
@Boiethios 我从未说过 OP 是错误的,只是在所有平台上将 wchar_t 硬编码为 u16 是一个糟糕的选择。C 语言的类型定义相当滑稽(例如,“int 的大小是多少”)。使用 Rust 的一个原因就是避免这些类型的问题。使用 libc::wchar_t 将防止代码在不同平台上编译,而不是编译但最终出现不匹配的指针,导致未定义的行为。 - Shepmaster
2个回答

7
您需要使用代表操作系统使用的本地字符串格式的OsString。在Windows中,这些是特定的16位字符字符串(通常为UTF-16)。
引用文档:
OsString和OsStr在您需要将字符串传输到操作系统本身或捕获外部命令输出时非常有用。 OsString,OsStr和Rust字符串之间的转换类似于CString和CStr的转换。
您首先需要使用不安全代码将指针转换为片段:
use std::slice;

// manifest a slice out of thin air!
let ptr = 0x1234 as const *u16;
let nb_elements = 10;
unsafe {
    let slice = slice::from_raw_parts(ptr, nb_elements);
}

这里假设您已经知道了字符串的大小,也就是说,您的函数应该接受字符数作为参数。

from_wide 方法应该是从本地格式转换的所需方法:

use std::ffi::OsString;
use std::os::windows::prelude::*;

// UTF-16 encoding for "Unicode".
let arr = [0x0055, 0x006E, 0x0069, 0x0063, 0x006F, 0x0064, 0x0065];

let string = OsString::from_wide(&arr[..]);

谢谢!我会调查一下并很快回来。实际上我不能改变调用函数的参数。如果我使用 C,我会使用 strlen 函数计算到 null 终止符为止的字符数。我猜我能在 Rust 中某种方式调用 strlen 函数。我会检查 from_wide 是否适当地工作。 - watts
1
@watts 可能需要使用不同的函数,而不是 strlen,因为 wchar_t 宽度为 16 位,而 strlen 假定没有嵌入的零字节。 - trent
2
假设字符串以空字符结尾,你仍然不能使用 strlen"Abc\0" 以小端 UTF-16 编码为 [65, 0, 98, 0, 99, 0, 0, 0]strlen 按字节逐个处理,因此它将返回 1。你需要使用一个每次处理两个字节的函数来获取所需的值(3)。 - trent
@trentcl 是的,我指的是适当的strlen,但感谢你指出来。 - watts
1
@watts 苛求细节是我最擅长的事情之一,而不是唯一的。 - trent
显示剩余2条评论

7
这里有一些示例代码:
use std::ffi::OsString;
use std::os::windows::prelude::*;

unsafe fn u16_ptr_to_string(ptr: *const u16) -> OsString {
    let len = (0..).take_while(|&i| *ptr.offset(i) != 0).count();
    let slice = std::slice::from_raw_parts(ptr, len);

    OsString::from_wide(slice)
}

// main example
fn main() {
    let buf = vec![97_u16, 98, 99, 100, 101, 102, 0];
    let ptr = buf.as_ptr(); // raw pointer

    let string = unsafe { u16_ptr_to_string(ptr) };

    println!("{:?}", string);
}

在函数中,你需要完成以下三个步骤:
  1. 通过使用offset(不安全)计算非零字符的数量,获取字符串的长度。
  2. 使用from_raw_parts(不安全)创建一个切片。
  3. 将这个&[u16]转换成OsString,使用from_wide方法。
建议使用libc库中的wchar_t和wcslen,并使用另一个crate进行转换。重新实现已经在crate中维护的东西可能是一个坏主意。

1
可能值得使用一些无法用ASCII表示的内容作为“w”。 - Shepmaster
@Shepmaster 为什么? - Boiethios
@Shepmaster 这是String::from_utf16的事情。 - Boiethios
@Shepmaster 我的 "strlen" 没问题: https://msdn.microsoft.com/zh-cn/library/69ze775t.aspx#Anchor_1 - Boiethios
@trentcl很好地解释了这个问题(也许实际上是不同的)。 - Shepmaster
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接