성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - 사용자가 빌드한 ICU dll 파일을 사용하는 방법</h1> <p> 아래와 같은 글이 있군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > How can I convert between IANA time zones and <a target='tab' href='https://www.sysnet.pe.kr/2/0/13413#tz_reg'>Windows registry-based time zones</a>? ; <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255'>https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255</a> </pre> <br /> 위의 글에서는 icu.dll에 있는 함수 ucal_getWindowsTimeZoneID, ucal_getTimeZoneIDForWindowsID 2개를 이용해 변환을 하고 있는데요, C#에서는 .NET 6부터 그에 대응하는 TryConvertIanaIdToWindowsId, TryConvertWindowsIdToIanaId 메서드를 각각 제공합니다. (.NET 5 이하라면 P/Invoke로 대신해야 합니다.)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > TimeZoneInfo.TryConvertIanaIdToWindowsId Method ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.timezoneinfo.tryconvertianaidtowindowsid'>https://learn.microsoft.com/en-us/dotnet/api/system.timezoneinfo.tryconvertianaidtowindowsid</a> TimeZoneInfo.TryConvertWindowsIdToIanaId Method ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.timezoneinfo.tryconvertwindowsidtoianaid'>https://learn.microsoft.com/en-us/dotnet/api/system.timezoneinfo.tryconvertwindowsidtoianaid</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 아래의 코드는, <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255'>How can I convert between IANA time zones and Windows registry-based time zones?</a> 글에 실린 C++ 예제 코드를 C#으로 변환한 것과 같은 효과를 갖습니다. if (TimeZoneInfo.TryConvertIanaIdToWindowsId("America/Vancouver", out string windowsTimeZoneId)) { Console.WriteLine(windowsTimeZoneId); // 출력 결과: Pacific Standard Time } if (TimeZoneInfo.TryConvertWindowsIdToIanaId("Pacific Standard Time", out string ianaTimeZoneId)) { Console.WriteLine(ianaTimeZoneId); // 출력 결과: America/Vancouver } </pre> <br /> 또한, 기존 NLS 대신 ICU를 사용하므로 아래의 코드 수행 결과도 달라집니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("de"); string text = string.Format("{0:C}", 100); Console.WriteLine(text); // 출력 결과: 100,00 ¤ </pre> <br /> NLS가 사용되는 경우에는 출력 결과가 "100,00 €"였지만 ICU를 사용하면서 화폐 단위가 "¤" (<a target='tab' href='https://ko.wikipedia.org/wiki/국제_통화_기호'>scarab</a>) 국제 통화 기호로 바뀝니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 현재 (c:\Windows\system32 디렉터리에 위치한) icu.dll은 윈도우 서버 버전의 경우 2019 이상, 클라이언트 버전의 경우 Windows 10 1703 이상에서만 기본 포함하고 있다는 점을 유의해야 합니다.<br /> <br /> 그런 탓에 TryConvertIanaIdToWindowsId, TryConvertWindowsIdToIanaId 메서드를 (예를 들어) Windows Server 2016 환경에서 호출하면 모두 false를 반환하면서 변환에 실패합니다. icu.dll을 내장하지 않은 윈도우 버전이라면, 그냥 직접 빌드한 DLL을 닷넷 응용 프로그램과 함께 배포하는 것도 가능한데요, <a target='tab' href='https://www.sysnet.pe.kr/2/0/11409'>vcpkg를 이용하면 간단하게 빌드</a>할 수 있고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C:\vcpkg> <span style='color: blue; font-weight: bold'>vcpkg search | findstr icu</span> ...[생략]... icu 72.1#3 Mature and widely used Unicode and localization library. ...[생략]... C:\vcpkg> <span style='color: blue; font-weight: bold'>vcpkg install icu[core]:x64-windows</span> ...[생략]... </pre> <br /> 그럼 다음과 같은 결과물 파일들이 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > icudt72.dll icuin72.dll icuio72.dll icutu72.dll icuuc72.dll </pre> <br /> 보는 바와 같이 "72"라는 버전 번호가 함께 붙는데요, 심지어 내부의 API들까지 모두 버전 번호가 따라붙어 빌드가 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C:\vcpkg\installed\x64-windows\bin> dumpbin /EXPORTS icuin72.dll | findstr ucal_getWindowsTimeZoneID 5794 16A1 0014CEF0 <span style='color: blue; font-weight: bold'>ucal_getWindowsTimeZoneID_72 = ucal_getWindowsTimeZoneID_72</span> </pre> <br /> 그래서, 우리가 직접 P/Invoke interop을 하려면 매우 귀찮아질 수 있는데요, 다행히 닷넷 런타임에서 이러한 다양성을 고려해 설정할 수 있는 옵션을 제공하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET globalization and ICU - App-local ICU ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/core/extensions/globalization-icu#app-local-icu'>https://learn.microsoft.com/en-us/dotnet/core/extensions/globalization-icu#app-local-icu</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <ItemGroup> <RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" Value="<span style='color: blue; font-weight: bold'><suffix>:<version> or <version></span>" /> </ItemGroup> </pre> <br /> suffix와 version을 각각 지정할 수 있는데 vcpkg의 경우 기본 빌드에서 suffix를 붙이지는 않으므로 버전만 다음과 같이 지정하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <ItemGroup> <RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" <span style='color: blue; font-weight: bold'>Value="72"</span> /> </ItemGroup> </pre> <br /> 자, 그럼 통합해 볼까요? ^^ icu 빌드 후 나온 결과물 중에 테스트를 해보면 닷넷을 위해 3개(icuuc72.dll, icudt72.dll, icuin72.dll)가 필요하다는 것을 알 수 있습니다. 따라서 다음과 같은 식으로 csproj를 구성해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net6.0</TargetFramework> </PropertyGroup> <span style='color: blue; font-weight: bold'><ItemGroup> <None Include="..\Libs\x64\icuuc72.dll" Link="icuuc72.dll"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> <None Include="..\Libs\x64\icudt72.dll" Link="icudt72.dll"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> <None Include="..\Libs\x64\icuin72.dll" Link="icuin72.dll"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup></span> <span style='color: blue; font-weight: bold'><ItemGroup> <RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" Value="72" /> </ItemGroup></span> </Project> </pre> <br /> (위의 경우 ..\Libs\x64 디렉터리에 DLL을 위치해 두고) 빌드 시 EXE 파일이 생성되는 위치에 dll도 함께 복사가 되므로 배포가 용이해집니다.<br /> <br /> 실제로 해당 파일들을 모두 복사해 Windows Server 2016에서 실행하면 정상적으로 ICU 관련 메서드들이 호출되는 것을 볼 수 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 문서에 보면, 반대로 ICU가 아닌 윈도우의 기본 NLS 모듈을 사용하도록 만드는 옵션도 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Use NLS instead of ICU ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/core/extensions/globalization-icu#use-nls-instead-of-icu'>https://learn.microsoft.com/en-us/dotnet/core/extensions/globalization-icu#use-nls-instead-of-icu</a> </pre> <br /> 따라서, 다음의 옵션을 csproj에 넣은 후,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <ItemGroup> <RuntimeHostConfigurationOption Include="System.Globalization.UseNls" Value="true" /> </ItemGroup> </pre> <br /> 빌드하면 이제는 Windows 11에서도 .NET 5+ 응용 프로그램은 이전과 같은 실행 결과가 나옵니다.<br /> <br /> 참고로, csproj에 지정한 RuntimeHostConfigurationOption은 결국 빌드 후 결과물로 나오는 "[exe].runtimeconfig.json" 파일에 반영되는 효과만 있는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > { "runtimeOptions": { "tfm": "net6.0", "framework": { "name": "Microsoft.NETCore.App", "version": "6.0.0" }, "configProperties": { <span style='color: blue; font-weight: bold'>"System.Globalization.AppLocalIcu": 72, "System.Globalization.UseNls": true</span> } } } </pre> <br /> 즉, csproj에 지정하지 않았어도 어떤 식으로든 runtimeconfig.json에 저 설정만 넣어주면 됩니다. 심지어 환경 변수(DOTNET_SYSTEM_GLOBALIZATION_USENLS, DOTNET_SYSTEM_GLOBALIZATION_APPLOCALICU)로도 가능합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그나저나, 우리가 vcpkg로 직접 빌드한 3개의 모듈은 각각의 파일 용량이 icudt72.dll 30MB, icuin72.dll 2.5MB, icuuc72.dll 1.7MB로 꽤나 큽니다. 반면, 윈도우 내장 icu.dll의 경우 2.5MB에 불과한데요, "<a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255'>How can I convert between IANA time zones and Windows registry-based time zones?</a>" 글에 보면, 윈도우도 처음에는 icuuc.dll, icuin.dll로 나뉘어 제공하다가 Windows 10 Version 1903부터 icu.dll 하나로 합쳤다고 합니다. <br /> <br /> 어쨌든 직접 빌드한 DLL이 적은 용량은 아니므로 배포 파일에 포함해야 한다면 아마도 윈도우 대상에 따라 셋업 파일을 제공하는 것도 고민해야 할 것입니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1080
(왼쪽의 숫자를 입력해야 합니다.)