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);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM