Rust 与 Windows 的字符串编码问题

C/C++字符串编码

MSVC++编译器本身支持的源文件编码是本地编码、带BOM的UTF-8、UTF-16LE、UTF-16BE,不支持不带BOM的UTF-8,会被误认为BGK这样的本地编码,所以字符串会直接被复制到程序中,不进行执行字符集的转换。所以会出现下面这种情况,程序中出现了UTF-8编码的字符串,但是xxA函数将其作为GBK编码进行解码:

如果源文件是本地编码、带BOM的UTF-8、UTF-16LE、UTF-16BE等编码,则会被正确以本地编码嵌入程序:

事实上,我们可以单独指定编译器的源码编码和执行字符集:

add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/source-charset:utf-8>") #指定源文件编码

这时之前UTF-8的识别错误就修复了:

可以指定执行字符集为utf-8,这样就意味着放弃了xxA函数,不过可以使用CP_UTF8代码页转为UTF-16字符串。

add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/execution-charset:utf-8>") #指定执行字符集
MultiByteToWideChar(CP_UTF8, 0, str, -1, buf, cBuf) // 使用代码页转换为宽字符字符串

可以看看/utf-8选项:https://docs.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170

兼容xxA函数和xxW函数的做法

add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/source-charset:utf-8>") #指定源文件编码为最常用的编码UTF-8
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/execution-charset:gbk>") #使用系统默认代码页GBK来兼容xxA函数

然后,使用以下函数转换UTF-16字符串:

#include "wchar.h"
#include <Windows.h>

#define CP_utf8to16 CP_UTF8

wchar_t*
utf8to16(const char* str) {
    if (str == NULL) return L"(null)";
    // 计算缓冲区需要的大小, 如果函数成功, 则返回 UTF-8 字符数量, 所以无法确定具体字节数
    int cBuf = MultiByteToWideChar(CP_utf8to16, 0, str, -1, NULL, 0);
    if (cBuf == 0) return L"(null)";
    wchar_t* buf = (wchar_t*)malloc(cBuf * 4);
    if (cBuf != MultiByteToWideChar(CP_utf8to16, 0, str, -1, buf, cBuf)) return L"(null)";
    return buf;
}

#define CP_asciito16 CP_ACP

wchar_t*
asciito16(const char* str) {
    if (str == NULL) return L"(null)";
    // 计算缓冲区需要的大小, 如果函数成功, 则返回 UTF-8 字符数量, 所以无法确定具体字节数
    int cBuf = MultiByteToWideChar(CP_asciito16, 0, str, -1, NULL, 0);
    if (cBuf == 0) return L"(null)";
    wchar_t* buf = (wchar_t*)malloc(cBuf * 4);
    if (cBuf != MultiByteToWideChar(CP_asciito16, 0, str, -1, buf, cBuf)) return L"(null)";
    return buf;
}

char*
utf16to8(const wchar_t* str) {
    if (str == NULL) return "(null)";
    // 计算缓冲区需要的大小, 如果函数成功, 则返回具体字节数, 所以 cBuf 至少是 1 (UTF-8以0x00结尾)
    int cBuf = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL);
    if (cBuf < 1) return "(null)";
    char* buf = (char*)malloc(cBuf); // 分配缓冲区
    if (cBuf != WideCharToMultiByte(CP_UTF8, 0, str, -1, buf, cBuf, NULL, NULL)) return "(null)";
    return buf;
}

参考以下函数:
https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/wcstombs-wcstombs-l?view=msvc-170
https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte

Rust 与 C++ 之间的字符串传递

第一,使用UTF-8互传,根据需要由C++选择是否转换为UTF-16。

第二,由于Rust支持UTF-16,可以直接调用xxW函数。

#[link(name = "User32")]
extern "C" {
    fn MessageBoxW(hWnd: u64, lpText: *const u8, lpCaption: *const u8, uType: u32) -> u32;
}

fn main() {
    unsafe {
        let str_utf16: Vec<u16> = "你好\0".encode_utf16().collect();
        let ptr = str_utf16.as_ptr() as *const u8;
        MessageBoxW(0, ptr, "A\0B\0\0\0".as_ptr(), 0);
    }
}

不过,C++传递给Rust的裸指针怎么转为Rust字符串呢?可以先使用std::slice::from_raw_parts将指针转为切片,再使用String::from_utf8或者from_utf16转为字符串对象。

fn main() {
    unsafe {
        let str_utf16 = "你好\0".encode_utf16();
        let str_utf16: Vec<u16> = str_utf16.collect();
        let ptr = str_utf16.as_ptr() as *const u16;
        // MessageBoxW(0, ptr, "A\0B\0\0\0".as_ptr(), 0);

        let a = std::slice::from_raw_parts(ptr, 3);
        let s = String::from_utf16(a).unwrap();
        MessageBoxA(0, s.as_ptr(), "A\0B\0\0\0".as_ptr(), 0);
    }
}

Node.js ffi-napi 传递UTF-16字符串

const ffi = require('ffi-napi');

function L(text) {
  return Buffer.from(text + '\0', 'utf16le');//.toString('binary');
};

// 通过ffi加载User32.dll
const myUser32 = new ffi.Library('User32', {
  'MessageBoxW': // 声明这个dll中的一个函数
    [
      'int32', ['int32', 'string', 'string', 'int32'], // 用json的格式罗列其返回类型和参数类型
    ],
});

// 调用user32.dll中的MessageBoxW()函数, 弹出一个对话框
const isOk = myUser32.MessageBoxW(
    0, 'H\0e\0l\0l\0o\0\0', L`你好`, 1
);
console.log(isOk);
posted @ 2022-03-15 11:40  develon  阅读(1685)  评论(0编辑  收藏  举报