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) // 使用代碼頁轉換為寬字符字符串
兼容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);