近期專案上有個需求,需要使用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);
}
}
}
最終結果
改善前 改善後