首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 呼叫 C++ DLL 字串編碼轉換完整指南:Unicode ↔ UTF-8 邊界處理

     編輯:關於C語言

問題核心

C# 預設字串為 **UTF-16 (Unicode)**,C++ DLL 介面若約定 **UTF-8 (char*)**,跨邊界傳遞字串必須正確轉碼,否則中文亂碼、存取違規、記憶體洩漏。

編碼概念釐清

術語說明
字符集字符集合與編號 (Unicode, GB2312, BIG5)
編碼編號 → 位元組序列的規則 (UTF-8, UTF-16LE, UTF-16BE, GB2312)
ANSI系統當前代碼頁 (繁中 CP950, 簡中 CP936, 英文 CP1252)
Unicode (C# 內部)UTF-16LE (Windows)、UTF-16BE (部分平台),C# string 固定 UTF-16
UTF-8變長 1-4 位元組,ASCII 相容,網路/檔案/跨平台標準

P/Invoke 封送字串參數


using System.Runtime.InteropServices;

// C++ 簽名: void Process(const char* utf8Input, char** utf8Output);
// 其中 char** 為輸出緩衝區指標,由 DLL 配置記憶體,呼叫端負責釋放

public static class NativeInterop
{
    // 1. 輸入字串:C# string → UTF-8 byte[] (UnmanagedType.LPUTF8Str .NET 5+)
    [DllImport("MyCppDll.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    // CharSet.Ansi 告訴封送器:以 ANSI/當前代碼頁轉換,但我們手動控制 UTF-8,見下文
    private static extern void ProcessUtf8(byte[] input, out IntPtr outputPtr);

    // 2. 釋放 DLL 配置記憶體
    [DllImport("MyCppDll.dll", CallingConvention = CallingConvention.Cdecl)]
    private static extern void FreeString(IntPtr ptr);

    /// 
    /// 安全呼叫包裝:C# string (UTF-16) → UTF-8 → 呼叫 DLL → UTF-8 → C# string
    /// 
    public static string Process(string input)
    {
        if (input == null) throw new ArgumentNullException(nameof(input));

        // A. 手動轉 UTF-8 位元組陣列 (避免 CharSet.Ansi 使用系統代碼頁)
        byte[] utf8Input = Encoding.UTF8.GetBytes(input + '\0'); // C 風格結尾 null

        // B. 呼叫
        ProcessUtf8(utf8Input, out IntPtr outputPtr);

        try
        {
            if (outputPtr == IntPtr.Zero) return null;

            // C. 讀取 UTF-8 字串
            // 方法 1: 手動掃描 null 終止符
            int len = 0;
            while (Marshal.ReadByte(outputPtr, len) != 0) len++;
            byte[] utf8Output = new byte[len];
            Marshal.Copy(outputPtr, utf8Output, 0, len);

            // D. UTF-8 → C# string (UTF-16)
            return Encoding.UTF8.GetString(utf8Output);
        }
        finally
        {
            // E. 釋放非受控記憶體
            FreeString(outputPtr);
        }
    }
}

.NET 5+ 簡化:UnmanagedType.LPUTF8Str


[DllImport("MyCppDll.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void ProcessUtf8Modern(
    [MarshalAs(UnmanagedType.LPUTF8Str)] string input,  // 自動 UTF-16 → UTF-8
    out IntPtr outputPtr);                               // 仍需手動處理輸出

輸出參數模式比較

C++ 簽名C# 封送記憶體責任備註
void Out(char* buf, int bufSize)StringBuilder / byte[]呼叫端配置固定緩衝區,需預估長度
void Out(char** outStr)out IntPtr + FreeStringDLL 配置 / 呼叫端釋放彈性長度,**必須成對配置/釋放**
BSTR Out() (COM)[MarshalAs(UnmanagedType.BStr)] out stringCOM 規則自動轉 UTF-16,SysAllocString/SysFreeString

完整雙向轉換工具類


public static class EncodingMarshaler
{
    /// C# string (UTF-16) → UTF-8 byte[] (含 null 終止)
    public static byte[] ToUtf8NullTerminated(string s)
        => s == null ? Array.Empty() : Encoding.UTF8.GetBytes(s + '\0');

    /// 非受控 UTF-8 字串 (IntPtr, null 終止) → C# string
    public static string PtrToStringUtf8(IntPtr ptr)
    {
        if (ptr == IntPtr.Zero) return null;
        int len = 0;
        while (Marshal.ReadByte(ptr, len) != 0) len++;
        var bytes = new byte[len];
        Marshal.Copy(ptr, bytes, 0, len);
        return Encoding.UTF8.GetString(bytes);
    }

    /// C# string → 非受控 UTF-8 緩衝區 (呼叫端需 FreeHGlobal)
    public static IntPtr StringToHGlobalUtf8(string s)
    {
        byte[] bytes = ToUtf8NullTerminated(s);
        IntPtr ptr = Marshal.AllocHGlobal(bytes.Length);
        Marshal.Copy(bytes, 0, ptr, bytes.Length);
        return ptr;
    }
}

常見錯誤與避坑

  • ❌ 直接用 `CharSet = CharSet.Unicode` 傳 `string` 給 `char*` → 送出 UTF-16 位元組,C++ 當 UTF-8 讀取 → 亂碼/崩潰。
  • ❌ 用 `CharSet = CharSet.Ansi` 依賴系統代碼頁 → 部署到不同語系機器行為不一致。
  • ❌ 輸出 `char**` 未呼叫對應 `Free` 函式 → 記憶體洩漏 (C++ heap 與 C# heap 不同)。
  • ❌ `StringBuilder` 容量不足導致緩衝區溢位 → 傳入足夠大小或改用 `char**` 模式。
  • ❌ 結構體中嵌入字串指標 (`char*`) → 需手動 `Marshal.StructureToPtr` 配合 `IntPtr` 欄位處理。

現代化替代:C++/CLI 或 Rust/Go FFI

  • C++/CLI:混合模組,直接在 C++ 專案 `#using ` 使用 .NET 類型,邊界零轉換成本,但僅限 Windows。
  • Rust + `cdylib` + `cxx`/`unidiff`:Rust 端處理 UTF-8,透過 `cxx` 橋接產生 C# 友好介面,記憶體安全、跨平台。
  • gRPC / protobuf:將 C++ 邏輯封裝為微服務,C# 透過 gRPC 呼叫,完全避開 P/Invoke 複雜度,適合高併發/分散式架構。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved