The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

如何在 Windows 打造 OpenCC 中文繁簡轉換工具

OpenCC 是一個開源的中文繁簡轉換工具,可以用來將簡體中文轉換成繁體中文,或是將繁體中文轉換成簡體中文,我已經用十多年了,是個很棒的工具。不過,我以前一直都是在 Linux 底下使用它,因為官方並沒有提供 Windows 執行檔,如果要能在 Windows 直接執行,就需要自行編譯原始碼才行。這篇文章將介紹如何在 Windows 平台上建置 OpenCC 專案,以及分享如何透過 .NET 8 (C#) 載入 opencc.dll 直接呼叫 C++ 的函式庫。

image

安裝 Microsoft Visual C++ Redistributable

由於 OpenCC 主要以 C++ 開發而成,所以在 Windows 平台上執行時,需要安裝 Microsoft Visual C++ Redistributable 才能確保執行時不會遇到缺少 DLL 的問題。

你可以直接到 Microsoft Visual C++ Redistributable latest supported downloads 頁面下載 X64 版本回來安裝即可:

直接下載連結: https://aka.ms/vs/17/release/vc_redist.x64.exe

安裝 CMake 建置工具

CMake 是一套強大的軟體建置系統,主要用來建置 C++ 的程式碼,而 OpenCC 若要在 Windows 平台建置,就要使用 CMake 來建置專案。

我打算使用 Chocolatey 來安裝 CMake 3.29.1 套件:

choco install cmake --installargs 'ADD_CMAKE_TO_PATH=User' --apply-install-arguments-to-dependencies

下載原始碼並建置專案

請使用「命令提示字元」(Command Prompt) 來執行以下指令:

  1. 下載最新版原始碼

    git clone https://github.com/BYVoid/OpenCC.git
    cd OpenCC
    
  2. 建立 build/ 資料夾

    這個步驟會指定最後的安裝路徑C:\Tools\OpenCC 目錄下,執行完後會產生一個 build/ 資料夾,裡面會準備好編譯的原始碼:

    cmake -S. -Bbuild -DCMAKE_INSTALL_PREFIX:PATH="C:\Tools\OpenCC"
    

    在建置成功後,你就會在 build/ 資料夾下看到完整的 Visual Studio 方案檔 (opencc.sln),也代表你可以用 Visual Studio 2022 來開啟並編譯這個方案。

    你從 cmake 的文件中可以發現,當你加上 -D 參數時,若設定選項的值是個「路徑」的話,你可以加上 :PATH 代表你要設定的是一個「路徑」,如果輸入相對路徑的話,cmake 在執行時會自動幫你轉成「絕對路徑」。

  3. 建置專案並安裝至指定路徑

    接下來可以直接透過 cmake 命令進行建置並自動安裝,你若使用 Visual Studio 開啟 build\opencc.sln 也可以建置!

    cmake --build build --config Release --target install
    

    這個命令的 --target install 參數,代表在建置成功後,會自動安裝到指定目錄。

  4. 最後一個步驟,則是自行設定 PATH 環境變數,讓系統可以自動找到 opencc.exe 執行檔:

    以我上述的安裝步驟來說,你要設定的路徑在:

    C:\Tools\OpenCC\bin
    

    請注意: 由於在建置 OpenCC 的時候,我們已經設定了 CMAKE_INSTALL_PREFIXC:\Tools\OpenCC,所以 opencc.exe 會被安裝在 C:\Tools\OpenCC\bin 目錄下,如果你今後移動了 opencc.exe 執行檔的路徑,他在執行時還是會去找 C:\Tools\OpenCC 目錄下的相關檔案,路徑是寫死在程式中的。也因為這樣,如果你想要變更路徑,就需要自行重新編譯程式,並指定不同的 CMAKE_INSTALL_PREFIX 參數值。

使用 OpenCC 命令

在安裝完成後,你可以在命令提示字元中輸入 opencc 來執行 OpenCC 的命令列工具,以下是一些常見用法:

  1. 查看版本

    opencc --version
    
    Open Chinese Convert (OpenCC) Command Line Tool
    Version: 1.1.7
    
  2. 查看使用說明

    opencc -h
    
    Open Chinese Convert (OpenCC) Command Line Tool
    Author: Carbo Kuo <byvoid@byvoid.com>
    Bug Report: http://github.com/BYVoid/OpenCC/issues
    
    Usage:
    
      opencc  [--noflush <bool>] [-i <file>] [-o <file>] [-c <file>] [--]
              [--version] [-h]
    
    Options:
    
      --noflush <bool>
        Disable flush for every line
    
      -i <file>,  --input <file>
        Read original text from <file>.
    
      -o <file>,  --output <file>
        Write converted text to <file>.
    
      -c <file>,  --config <file>
        Configuration file
    
      --,  --ignore_rest
        Ignores the rest of the labeled arguments following this flag.
    
      --version
        Displays version information and exits.
    
      -h,  --help
        Displays usage information and exits.
    
    
      Open Chinese Convert (OpenCC) Command Line Tool
    
  3. README_CN.md (簡體中文) 轉換為 README_TW.txt (繁體中文)

    若要將一份簡體中文的 README_CN.md 轉換為繁體中文的 README_TW.md,以下是我常用的指令:

    opencc -i README_CN.md -o README_TW.md -c s2twp.json
    

    你可以從 C:\Tools\OpenCC\share\opencc 目錄下找到許多不同的轉換設定檔 (*.json),這裡我使用的是 s2twp.json,代表將簡體中文轉換為繁體中文(台灣)。這裡的 s 代表簡體中文,t 代表繁體中文,w 代表台灣慣用字,而 p (phrase) 才是代表台灣慣用詞彙。

執行 OpenCC 的效能測試

OpenCC 的執行效率非常高,如果要看看繁簡轉換的效能,必須重新編譯程式,並加入 ENABLE_BENCHMARK=ON 參數。

  1. 準備 OpenCC 的效能測試

    cmake -S. -Bbuild -DENABLE_BENCHMARK=ON -DCMAKE_INSTALL_PREFIX:PATH=C:\Tools\OpenCC
    
  2. 建置專案

    cmake --build build --config Release
    
  3. 手動複製 benchmark.dll 檔案到發行目錄

    copy build\src\benchmark\benchmark.dll build\src\benchmark\Release\
    
  4. 執行效能測試

    build\src\benchmark\Release\performance.exe
    

從 C# 呼叫 OpenCC 執行

這裡我參考了黑暗執行緒使用 C# 整合 OpenCC 執行中文繁簡轉換文章中提供的範例,並修正了一些 Interop 寫法 (主要是用來避免記憶體洩漏的問題)。也参考了 Opencc Memory Leak 文章,改用 OpenCC 內部原生的 opencc_convert_utf8_freeopencc_close 方法來釋放記憶體,這才徹底解決了記憶體洩漏的問題。新版如下:

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

使用方式如下:

string text = "";

text = "多奇数位创意有限公司";

Console.WriteLine(text.ConvertFromSimplifiedToTraditionalTaiwan());

text = "多奇數位創意有限公司";

Console.WriteLine(text.ConvertFromTraditionalTaiwanToSimplified());

相關連結

留言評論