C# - ICU 라이브러리를 활용한 문자열의 대소문자 변환
지난 글의,
C++ - 문자열의 대소문자를 변환하는 transform + std::tolower/toupper 방식의 문제점
; https://www.sysnet.pe.kr/2/0/13797
C# 버전을 작성해 보겠습니다. ^^
우선, C#의 경우 .NET 5부터 ICU(International Components for Unicode) 라이브러리를 지원하는데요, 따라서 .NET Core 3.1 이하 버전에서는 (아마도
LCMapStringEx를 사용할 것이므로) 대/소문자 변환이 매끄럽지 않을 것이라고 예상할 수 있습니다.
실제로 테스트를 해볼까요? ^^
// .NET Core 3.1 이하 + Windows 11
using System;
internal class Program
{
static void Main(string[] args)
{
string text = "𐲀, ß, à, fl";
Console.WriteLine(text);
Console.WriteLine(text.ToLower());
Console.WriteLine(text.ToUpper());
}
}
/* 출력 결과
𐲀, ß, à, fl
𐲀, ß, à, fl
𐲀, ß, À, fl
*/
보는 바와 같이 '
𐲀' 문자의 소문자 변환이 안 되었고, ß, fl 문자의 대문자 변환이 안 되었습니다.
동일한 예제를 .NET 5+ 버전에서 해보면 어떨까요?
// .NET Core 5 이상 + Windows 11
internal class Program
{
static void Main(string[] args)
{
string text = "𐲀, ß, à, fl";
Console.WriteLine(text);
Console.WriteLine(text.ToLower());
Console.WriteLine(text.ToUpper());
}
}
/* 출력 결과
𐲀, ß, à, fl
𐳀, ß, à, fl
𐲀, ß, À, fl
*/
그나마 이번엔
𐲀 문자의 소문자 처리는 되었지만, 여전히 ß, fl 문자의 대문자 변환이 안 되었습니다.
혹시, Windows 11에 기본 포함된 ICU Combined Library의 버전 차이 때문일까요? 기본적으로 (2024-11-03 기준) Windows 11에 포함된 icu.dll은 68.2.0.10 버전입니다. 반면
지난 글에서 테스트한 버전은 74였는데요, 혹시나 싶어 그 DLL들을 포함하고,
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Include="..\lib\icudt74.dll" Link="icudt74.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\lib\icuin74.dll" Link="icuin74.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\lib\icuio74.dll" Link="icuio74.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\lib\icutu74.dll" Link="icutu74.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\lib\icuuc74.dll" Link="icuuc74.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" Value="74" />
</ItemGroup>
</Project>
빌드해 봤지만 결과는 변하지 않았습니다. 그렇다면, 아직 닷넷은 ICU 라이브러리를 100% 활용하고 있지는 않는 것 같습니다.
그런데, 문자열 비교를 해보면 더 재미있는 결과가 나옵니다. ^^;
Console.WriteLine(StringComparer.Create(CultureInfo.InvariantCulture, true).Compare("𐲀", "𐳀"));
Console.WriteLine(StringComparer.Create(CultureInfo.InvariantCulture, true).Compare("a", "A"));
Console.WriteLine(StringComparer.Create(CultureInfo.InvariantCulture, true).Compare("fl", "FL"));
Console.WriteLine(StringComparer.Create(CultureInfo.InvariantCulture, true).Compare("ß", "ẞ"));
/* 출력 결과
-1
0
0
0
*/
𐲀 문자의 경우 정작 소문자 변환은 성공했지만 ignoreCase 비교에서는 실패한 반면, fl, ß 2개의 문자는 오히려 대문자 변환은 실패했지만 ignoreCase 비교에서는 성공했습니다. ^^;
참고로, 윈도우에 기본 포함된 icu.dll이 제공하는 함수를 직접 이용하는 것도 가능합니다.
C:\temp> dumpbin /EXPORTS c:\windows\system32\icu.dll | findstr u_strToLower
114 71 00008940 u_strToLower
따라서, 이렇게 interop을 하면,
using System.Globalization;
using System.Runtime.InteropServices;
internal unsafe class Program
{
[DllImport("icu.dll")]
public static extern int u_strToLower(char* dest, int destCapacity, char* src, int srcLength, char* locale, int* pErrorCode);
[DllImport("icu.dll")]
public static extern int u_strToUpper(char* dest, int destCapacity, char* src, int srcLength, char* locale, int* pErrorCode);
static void Main(string[] args)
{
string text = "𐲀, ß, à, fl";
// U_ILLEGAL_ARGUMENT_ERROR = 1, /**< Start of codes indicating failure */
// U_BUFFER_OVERFLOW_ERROR = 15, /**< A result would not fit in the supplied buffer */
// U_STRING_NOT_TERMINATED_WARNING = -124,/**< An output string could not be NUL-terminated because output length==destCapacity. */
{
// ICU4C
int errorCode = 0;
fixed (char* src = text)
{
int needBuffer = u_strToLower(null, 0, src, -1, null, &errorCode);
errorCode = 0;
char* dest = stackalloc char[needBuffer + 1];
u_strToLower(dest, needBuffer + 1, src, -1, null, &errorCode);
Console.WriteLine(new string(dest));
}
}
{
// ICU4C
int errorCode = 0;
fixed (char* src = text)
{
int needBuffer = u_strToUpper(null, 0, src, -1, null, &errorCode);
errorCode = 0;
char* dest = stackalloc char[needBuffer + 1];
u_strToUpper(dest, needBuffer + 1, src, -1, null, &errorCode);
Console.WriteLine(new string(dest));
}
}
}
}
/* 출력 결과
𐳀, ß, à, fl
𐲀, SS, À, FL
*/
"
C++ - ICU dll을 이용하는 예제 코드 (Windows)" 글에서 작성한 결과 그대로 나왔습니다.
만약, 윈도우가 기본 제공하는 icu.dll이 아닌 직접 빌드한 것을 사용하고 싶다면 u_strToLower의 경우 icuuc??dll에 있을 테니,
C:\temp> dumpbin /EXPORTS icuuc74.dll | findstr u_strToLower
2358 935 000E0E20 u_strToLower_74
버전 번호를 EntryPoint로 명시해 사용하면 됩니다.
[DllImport("icuuc74.dll", EntryPoint = "u_strToLower_74")]
public static extern int u_strToLower(char* dest, int destCapacity, char* src, int srcLength, char* locale, int* pErrorCode);
[DllImport("icuuc74.dll", EntryPoint = "u_strToUpper_74")]
public static extern int u_strToUpper(char* dest, int destCapacity, char* src, int srcLength, char* locale, int* pErrorCode);
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]