近期專案上有個需求,需要使用opencc來將接收到的資料將簡體轉繁體,參考了will保哥與黑暗執行緒的範例,其中保哥的文章指出有做防止記憶體洩漏的調整,但上線後發現一樣有記憶體洩漏的問題,最後找到的解決方案是,應該要使用opencc的函式做記憶體釋放,而非使用C#的函式。

調整前

using System.Runtime.InteropServices;
using System.Text;

public static class OpenCCHelper
{
    [DllImport(@"C:\Tools\opencc\bin\opencc.dll", EntryPoint = "opencc_open")]
    private static extern IntPtr opencc_open(string configFileName);

    [DllImport(@"C:\Tools\opencc\bin\opencc.dll", EntryPoint = "opencc_convert_utf8")]
    private static extern IntPtr opencc_convert_utf8(IntPtr opencc, IntPtr input, long length);

    public static string ConvertFromSimplifiedToTraditional(this string text, string config = "s2t")
    {
        return OpenCC(text, config: config);
    }

    public static string ConvertFromSimplifiedToTraditionalTaiwan(this string text, string config = "s2twp")
    {
        return OpenCC(text, config: config);
    }

    public static string ConvertFromTraditionalTaiwanToSimplified(this string text, string config = "tw2sp")
    {
        return OpenCC(text, config: config);
    }

    public static string ConvertFromTraditionalToSimplified(this string text, string config = "t2s")
    {
        return OpenCC(text, config: config);
    }

    public static string OpenCC(this string text, string config)
    {
        var configFile = $"C:\\Tools\\OpenCC\\share\\opencc\\{config}.json";
        if (!File.Exists(configFile))
        {
            throw new FileNotFoundException("設定檔找不到", configFile);
        }

        IntPtr opencc = opencc_open(configFile);
        try
        {
            int len = Encoding.UTF8.GetByteCount(text);
            byte[] buffer = new byte[len + 1];
            Encoding.UTF8.GetBytes(text, 0, text.Length, buffer, 0);
            IntPtr inStr = Marshal.AllocHGlobal(buffer.Length);
            try
            {
                Marshal.Copy(buffer, 0, inStr, buffer.Length);
                IntPtr outStr = opencc_convert_utf8(opencc, inStr, -1);
                try
                {
                    int outLen = 0;
                    while (Marshal.ReadByte(outStr, outLen) != 0) ++outLen;
                    byte[] outBuffer = new byte[outLen];
                    Marshal.Copy(outStr, outBuffer, 0, outBuffer.Length);
                    return Encoding.UTF8.GetString(outBuffer);
                }
                finally
                {
                    Marshal.FreeHGlobal(outStr);
                }
            }
            finally
            {
                Marshal.FreeHGlobal(inStr);
            }
        }
        finally
        {
            Marshal.FreeHGlobal(opencc);
        }
    }
}

調整後

using System.Runtime.InteropServices;
using System.Text;

public static class OpenCCHelper
{
    [DllImport(@"C:\Tools\opencc\bin\opencc.dll", EntryPoint = "opencc_open")]
    private static extern IntPtr opencc_open(string configFileName);
    //傳入參數做調整
    [DllImport(@"C:\Tools\opencc\bin\opencc.dll", EntryPoint = "opencc_convert_utf8")]
    private static extern IntPtr opencc_convert_utf8(IntPtr opencc, IntPtr input, UIntPtr length);
    //新增
    [DllImport(@"C:\Tools\opencc\bin\opencc.dll", EntryPoint = "opencc_convert_utf8_free")]
    private static extern IntPtr opencc_convert_utf8_free(IntPtr str);
    //新增
    [DllImport(@"C:\Tools\opencc\bin\opencc.dll", EntryPoint = "opencc_close")]
    private static extern IntPtr opencc_close(IntPtr opencc);

    public static string ConvertFromSimplifiedToTraditional(this string text, string config = "s2t")
    {
        return OpenCC(text, config: config);
    }

    public static string ConvertFromSimplifiedToTraditionalTaiwan(this string text, string config = "s2twp")
    {
        return OpenCC(text, config: config);
    }

    public static string ConvertFromTraditionalTaiwanToSimplified(this string text, string config = "tw2sp")
    {
        return OpenCC(text, config: config);
    }

    public static string ConvertFromTraditionalToSimplified(this string text, string config = "t2s")
    {
        return OpenCC(text, config: config);
    }

    public static string OpenCC(this string text, string config)
    {
        var configFile = $"C:\\Tools\\OpenCC\\share\\opencc\\{config}.json";
        if (!File.Exists(configFile))
        {
            throw new FileNotFoundException("設定檔找不到", configFile);
        }
        IntPtr opencc = opencc_open(configFile);
        try
        {
            IntPtr inputPtr = Marshal.StringToCoTaskMemUTF8(text);
            try
            {
                var input = text.ToCharArray();
                UIntPtr length = new((uint)Encoding.UTF8.GetByteCount(input));

                IntPtr resultPtr = opencc_convert_utf8(opencc, inputPtr, length);
                try
                {
                    string? result = Marshal.PtrToStringUTF8(resultPtr);
                    return result!;
                }
                finally
                {
                    //改為使用Opencc內建的釋放資源函數
                    opencc_convert_utf8_free(resultPtr);
                }
            }
            finally
            {
                Marshal.FreeCoTaskMem(inputPtr);
            }
        }
        finally
        {
            //改為使用Opencc內建的釋放資源函數
            opencc_close(opencc);
        }
    }
}

最終結果

改善前 改善前 改善後 改善後

相關連結