diff --git a/src/coreclr/src/System.Private.CoreLib/PinvokeAnalyzerExceptionList.analyzerdata b/src/coreclr/src/System.Private.CoreLib/PinvokeAnalyzerExceptionList.analyzerdata index c53e2b766ad136..f2570c7c958eea 100644 --- a/src/coreclr/src/System.Private.CoreLib/PinvokeAnalyzerExceptionList.analyzerdata +++ b/src/coreclr/src/System.Private.CoreLib/PinvokeAnalyzerExceptionList.analyzerdata @@ -36,6 +36,7 @@ libSystem.Globalization.Native!GlobalizationNative_GetSortVersion libSystem.Globalization.Native!GlobalizationNative_GetTimeZoneDisplayName libSystem.Globalization.Native!GlobalizationNative_IndexOf libSystem.Globalization.Native!GlobalizationNative_IndexOfOrdinalIgnoreCase +libSystem.Globalization.Native!GlobalizationNative_InitICUFunctions libSystem.Globalization.Native!GlobalizationNative_IsNormalized libSystem.Globalization.Native!GlobalizationNative_IsPredefinedLocale libSystem.Globalization.Native!GlobalizationNative_LastIndexOf diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.Unix.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.Unix.cs index 6cb513abc23d09..14c7699d1b7133 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.Unix.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.Unix.cs @@ -17,7 +17,11 @@ private static bool GetGlobalizationInvariantMode() bool invariantEnabled = GetInvariantSwitchValue(); if (!invariantEnabled) { - if (Interop.Globalization.LoadICU() == 0) + if (TryGetAppLocalIcuSwitchValue(out string? icuSuffixAndVersion)) + { + LoadAppLocalIcu(icuSuffixAndVersion, suffixWithSeparator: true); + } + else if (Interop.Globalization.LoadICU() == 0) { string message = "Couldn't find a valid ICU package installed on the system. " + "Set the configuration flag System.Globalization.Invariant to true if you want to run with no globalization support."; @@ -26,5 +30,28 @@ private static bool GetGlobalizationInvariantMode() } return invariantEnabled; } + + private static void LoadAppLocalIcuCore(ReadOnlySpan version, ReadOnlySpan suffix) + { + +#if TARGET_OSX + const string extension = ".dylib"; + bool versionAtEnd = false; +#else + string extension = version.Length > 0 ? "so." : "so"; + bool versionAtEnd = true; +#endif + +#if !TARGET_OSX + // In Linux we need to load libicudata first because libicuuc and libicui18n depend on it. In order for the loader to find + // it on the same path, we load it before loading the other two libraries. + LoadLibrary(CreateLibraryName("libicudata", suffix, extension, version, versionAtEnd), failOnLoadFailure: true); +#endif + + IntPtr icuucLib = LoadLibrary(CreateLibraryName("libicuuc", suffix, extension, version, versionAtEnd), failOnLoadFailure: true); + IntPtr icuinLib = LoadLibrary(CreateLibraryName("libicui18n", suffix, extension, version, versionAtEnd), failOnLoadFailure: true); + + Interop.Globalization.InitICUFunctions(icuucLib, icuinLib, version, suffix); + } } } diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.Windows.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.Windows.cs index 50285f01da2028..1fa21bc928b6d3 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.Windows.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.Windows.cs @@ -12,6 +12,50 @@ internal static partial class GlobalizationMode internal static bool UseNls { get; } = !Invariant && (GetSwitchValue("System.Globalization.UseNls", "DOTNET_SYSTEM_GLOBALIZATION_USENLS") || - Interop.Globalization.LoadICU() == 0); + !LoadIcu()); + + private static bool LoadIcu() + { + if (!TryGetAppLocalIcuSwitchValue(out string? icuSuffixAndVersion)) + { + return Interop.Globalization.LoadICU() != 0; + } + + LoadAppLocalIcu(icuSuffixAndVersion); + return true; + } + + private static void LoadAppLocalIcuCore(ReadOnlySpan version, ReadOnlySpan suffix) + { + const string extension = ".dll"; + const string icuucBase = "icuuc"; + const string icuinBase = "icuin"; + IntPtr icuucLib = IntPtr.Zero; + IntPtr icuinLib = IntPtr.Zero; + + int index = version.IndexOf('.'); + if (index > 0) + { + ReadOnlySpan truncatedVersion = version.Slice(0, index); + icuucLib = LoadLibrary(CreateLibraryName(icuucBase, suffix, extension, truncatedVersion), failOnLoadFailure: false); + + if (icuucLib != IntPtr.Zero) + { + icuinLib = LoadLibrary(CreateLibraryName(icuinBase, suffix, extension, truncatedVersion), failOnLoadFailure: false); + } + } + + if (icuucLib == IntPtr.Zero) + { + icuucLib = LoadLibrary(CreateLibraryName(icuucBase, suffix, extension, version), failOnLoadFailure: true); + } + + if (icuinLib == IntPtr.Zero) + { + icuinLib = LoadLibrary(CreateLibraryName(icuinBase, suffix, extension, version), failOnLoadFailure: true); + } + + Interop.Globalization.InitICUFunctions(icuucLib, icuinLib, version, suffix); + } } } diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs index 1433e5220b976d..dd460b178945f6 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs @@ -2,6 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.InteropServices; + namespace System.Globalization { internal static partial class GlobalizationMode @@ -9,13 +13,15 @@ internal static partial class GlobalizationMode private static bool GetInvariantSwitchValue() => GetSwitchValue("System.Globalization.Invariant", "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"); + private static bool TryGetAppLocalIcuSwitchValue([NotNullWhen(true)] out string? value) => + TryGetStringValue("System.Globalization.AppLocalIcu", "DOTNET_SYSTEM_GLOBALIZATION_APPLOCALICU", out value); + // GetSwitchValue calls CLRConfig first to detect if the switch is defined in the config file. // if the switch is defined we just use the value of this switch. otherwise, we'll try to get the switch // value from the environment variable if it is defined. private static bool GetSwitchValue(string switchName, string envVariable) { - bool ret = CLRConfig.GetBoolValue(switchName, out bool exist); - if (!exist) + if (!AppContext.TryGetSwitch(switchName, out bool ret)) { string? switchValue = Environment.GetEnvironmentVariable(envVariable); if (switchValue != null) @@ -26,5 +32,72 @@ private static bool GetSwitchValue(string switchName, string envVariable) return ret; } + + private static bool TryGetStringValue(string switchName, string envVariable, [NotNullWhen(true)] out string? value) + { + value = AppContext.GetData(switchName) as string; + if (string.IsNullOrEmpty(value)) + { + value = Environment.GetEnvironmentVariable(envVariable); + if (string.IsNullOrEmpty(value)) + { + return false; + } + } + + return true; + } + + private static void LoadAppLocalIcu(string icuSuffixAndVersion, bool suffixWithSeparator = false) + { + ReadOnlySpan icuSuffix = default; + ReadOnlySpan version = default; + + // Custom built ICU can have a suffix on the name, i.e: libicuucmyapp.so.67.1 + // So users would set the runtime switch as: myapp:67.1 + int indexOfSeparator = icuSuffixAndVersion.IndexOf(':'); + if (indexOfSeparator >= 0) + { + icuSuffix = icuSuffixAndVersion.AsSpan().Slice(0, indexOfSeparator); + + if (icuSuffix.Length > 35) + { + Environment.FailFast($"The resolved \"{icuSuffix.ToString()}\" suffix from System.Globalization.AppLocalIcu switch has to be < 20 chars long."); + } + + version = icuSuffixAndVersion.AsSpan().Slice(icuSuffix.Length + 1); + } + else + { + version = icuSuffixAndVersion; + } + + if (version.Length > 33) + { + Environment.FailFast($"The resolved version \"{version.ToString()}\" from System.Globalization.AppLocalIcu switch has to be < 33 chars long."); + } + + if (suffixWithSeparator) + { + icuSuffix = string.Concat(icuSuffix, "."); + } + + LoadAppLocalIcuCore(version, icuSuffix); + } + + private static string CreateLibraryName(ReadOnlySpan baseName, ReadOnlySpan suffix, ReadOnlySpan extension, ReadOnlySpan version, bool versionAtEnd = false) => + versionAtEnd ? + string.Concat(baseName, suffix, extension, version) : + string.Concat(baseName, suffix, version, extension); + + private static IntPtr LoadLibrary(string library, bool failOnLoadFailure) + { + if (!NativeLibrary.TryLoad(library, typeof(object).Assembly, DllImportSearchPath.ApplicationDirectory, out IntPtr lib) && failOnLoadFailure) + { + Environment.FailFast($"Failed to load app-local ICU: {library}"); + } + + return lib; + } } } diff --git a/src/coreclr/src/libraries-native/entrypoints.c b/src/coreclr/src/libraries-native/entrypoints.c index 68553e99997af6..c0dc63961b9b4b 100644 --- a/src/coreclr/src/libraries-native/entrypoints.c +++ b/src/coreclr/src/libraries-native/entrypoints.c @@ -51,6 +51,7 @@ FCFuncStart(gPalGlobalizationNative) QCFuncElement("GetTimeZoneDisplayName", GlobalizationNative_GetTimeZoneDisplayName) QCFuncElement("IndexOf", GlobalizationNative_IndexOf) QCFuncElement("IndexOfOrdinalIgnoreCase", GlobalizationNative_IndexOfOrdinalIgnoreCase) + QCFuncElement("InitICUFunctions", GlobalizationNative_InitICUFunctions) QCFuncElement("IsNormalized", GlobalizationNative_IsNormalized) QCFuncElement("IsPredefinedLocale", GlobalizationNative_IsPredefinedLocale) QCFuncElement("LastIndexOf", GlobalizationNative_LastIndexOf) diff --git a/src/libraries/Common/src/Interop/Interop.Collation.cs b/src/libraries/Common/src/Interop/Interop.Collation.cs index ece19be581c8bc..07cf42b5ad7564 100644 --- a/src/libraries/Common/src/Interop/Interop.Collation.cs +++ b/src/libraries/Common/src/Interop/Interop.Collation.cs @@ -27,6 +27,7 @@ internal static partial class Globalization [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOfOrdinalIgnoreCase")] internal static extern unsafe int IndexOfOrdinalIgnoreCase(string target, int cwTargetLength, char* pSource, int cwSourceLength, bool findLast); + [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOfOrdinalIgnoreCase")] internal static extern unsafe int IndexOfOrdinalIgnoreCase(char* target, int cwTargetLength, char* pSource, int cwSourceLength, bool findLast); diff --git a/src/libraries/Common/src/Interop/Interop.ICU.cs b/src/libraries/Common/src/Interop/Interop.ICU.cs index 0dce072958a7fa..fb8b56e41d35f2 100644 --- a/src/libraries/Common/src/Interop/Interop.ICU.cs +++ b/src/libraries/Common/src/Interop/Interop.ICU.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; +using System.Diagnostics; using System.Runtime.InteropServices; internal static partial class Interop @@ -11,6 +13,17 @@ internal static partial class Globalization [DllImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_LoadICU")] internal static extern int LoadICU(); + internal static void InitICUFunctions(IntPtr icuuc, IntPtr icuin, ReadOnlySpan version, ReadOnlySpan suffix) + { + Debug.Assert(icuuc != IntPtr.Zero); + Debug.Assert(icuin != IntPtr.Zero); + + InitICUFunctions(icuuc, icuin, version.ToString(), suffix.Length > 0 ? suffix.ToString() : null); + } + + [DllImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_InitICUFunctions")] + internal static extern void InitICUFunctions(IntPtr icuuc, IntPtr icuin, string version, string? suffix); + [DllImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetICUVersion")] internal static extern int GetICUVersion(); } diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.c b/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.c index fbea9cf6ca87f4..1a594c461c0ed9 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.c +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.c @@ -23,11 +23,76 @@ FOR_ALL_ICU_FUNCTIONS #undef PER_FUNCTION_BLOCK +// 35 for the actual suffix, 1 for _ and 1 for '\0' +#define SYMBOL_CUSTOM_SUFFIX_SIZE 37 +#define SYMBOL_NAME_SIZE (128 + SYMBOL_CUSTOM_SUFFIX_SIZE) +#define MaxICUVersionStringWithSuffixLength (MaxICUVersionStringLength + SYMBOL_CUSTOM_SUFFIX_SIZE) + + +#if defined(TARGET_WINDOWS) || defined(TARGET_OSX) || defined(TARGET_ANDROID) + +#define MaxICUVersionStringLength 33 + +#endif + static void* libicuuc = NULL; static void* libicui18n = NULL; +#if defined (TARGET_UNIX) + +#define PER_FUNCTION_BLOCK(fn, lib) \ + c_static_assert_msg((sizeof(#fn) + MaxICUVersionStringWithSuffixLength + 1) <= sizeof(symbolName), "The symbolName is too small for symbol " #fn); \ + sprintf(symbolName, #fn "%s", symbolVersion); \ + fn##_ptr = (__typeof(fn)*)dlsym(lib, symbolName); \ + if (fn##_ptr == NULL) { fprintf(stderr, "Cannot get symbol %s from " #lib "\nError: %s\n", symbolName, dlerror()); abort(); } + +static int FindSymbolVersion(int majorVer, int minorVer, int subVer, char* symbolName, char* symbolVersion, char* suffix) +{ + // Find out the format of the version string added to each symbol + // First try just the unversioned symbol + if (dlsym(libicuuc, "u_strlen") == NULL) + { + // Now try just the _majorVer added + sprintf(symbolVersion, "_%d%s", majorVer, suffix); + sprintf(symbolName, "u_strlen%s", symbolVersion); + if (dlsym(libicuuc, symbolName) == NULL) + { + if (minorVer == -1) + return FALSE; + + // Now try the _majorVer_minorVer added + sprintf(symbolVersion, "_%d_%d%s", majorVer, minorVer, suffix); + sprintf(symbolName, "u_strlen%s", symbolVersion); + if (dlsym(libicuuc, symbolName) == NULL) + { + if (subVer == -1) + return FALSE; + + // Finally, try the _majorVer_minorVer_subVer added + sprintf(symbolVersion, "_%d_%d_%d%s", majorVer, minorVer, subVer, suffix); + sprintf(symbolName, "u_strlen%s", symbolVersion); + if (dlsym(libicuuc, symbolName) == NULL) + { + return FALSE; + } + } + } + } + + return TRUE; +} + +#endif // TARGET_UNIX + #if defined(TARGET_WINDOWS) +#define sscanf sscanf_s + +#define PER_FUNCTION_BLOCK(fn, lib) \ + sprintf_s(symbolName, SYMBOL_NAME_SIZE, #fn "%s", symbolVersion); \ + fn##_ptr = (__typeof(fn)*)GetProcAddress((HMODULE)lib, symbolName); \ + if (fn##_ptr == NULL) { fprintf(stderr, "Cannot get symbol %s from " #lib "\nError: %u\n", symbolName, GetLastError()); abort(); } + static int FindICULibs() { libicuuc = LoadLibraryExW(L"icu.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); @@ -41,6 +106,42 @@ static int FindICULibs() return TRUE; } +static int FindSymbolVersion(int majorVer, int minorVer, int subVer, char* symbolName, char* symbolVersion, char* suffix) +{ + HMODULE lib = (HMODULE)libicuuc; + // Find out the format of the version string added to each symbol + // First try just the unversioned symbol + if (GetProcAddress(lib, "u_strlen") == NULL) + { + // Now try just the _majorVer added + sprintf_s(symbolVersion, MaxICUVersionStringWithSuffixLength,"_%d%s", majorVer, suffix); + sprintf_s(symbolName, SYMBOL_NAME_SIZE, "u_strlen%s", symbolVersion); + if (GetProcAddress(lib, symbolName) == NULL) + { + if (minorVer == -1) + return FALSE; + + // Now try the _majorVer_minorVer added + sprintf_s(symbolVersion, MaxICUVersionStringWithSuffixLength, "_%d_%d%s", majorVer, minorVer, suffix); + sprintf_s(symbolName, SYMBOL_NAME_SIZE, "u_strlen%s", symbolVersion); + if (GetProcAddress(lib, symbolName) == NULL) + { + if (subVer == -1) + return FALSE; + // Finally, try the _majorVer_minorVer_subVer added + sprintf_s(symbolVersion, MaxICUVersionStringWithSuffixLength, "_%d_%d_%d%s", majorVer, minorVer, subVer, suffix); + sprintf_s(symbolName, SYMBOL_NAME_SIZE, "u_strlen%s", symbolVersion); + if (GetProcAddress(lib, symbolName) == NULL) + { + return FALSE; + } + } + } + } + + return TRUE; +} + #elif defined(TARGET_OSX) static int FindICULibs() @@ -68,22 +169,6 @@ static int FindICULibs() // support ICU versions from 50-255 #define MinICUVersion 50 #define MaxICUVersion 255 -#define MaxICUVersionStringLength 4 - -static int FindSymbolVersion(char* symbolName, char* symbolVersion) -{ - for (int i = MinICUVersion; i <= MaxICUVersion; i++) - { - sprintf(symbolVersion, "_%d", i); - sprintf(symbolName, "u_strlen%s", symbolVersion); - if (dlsym(libicuuc, symbolName) != NULL) - { - return TRUE; - } - } - - return FALSE; -} static int FindICULibs(char* symbolName, char* symbolVersion) { @@ -101,13 +186,17 @@ static int FindICULibs(char* symbolName, char* symbolVersion) return FALSE; } - if (!FindSymbolVersion(symbolName, symbolVersion)) + char symbolSuffix[SYMBOL_CUSTOM_SUFFIX_SIZE]=""; + for (int i = MinICUVersion; i <= MaxICUVersion; i++) { - fprintf(stderr, "Cannot determine ICU version."); - return FALSE; + if (FindSymbolVersion(i, -1, -1, symbolName, symbolVersion, symbolSuffix)) + { + return TRUE; + } } - return TRUE; + fprintf(stderr, "Cannot determine ICU version."); + return FALSE; } #else // !TARGET_WINDOWS && !TARGET_OSX && !TARGET_ANDROID @@ -153,36 +242,6 @@ static void GetVersionedLibFileName(const char* baseFileName, int majorVer, int } } -static int FindSymbolVersion(int majorVer, int minorVer, int subVer, char* symbolName, char* symbolVersion) -{ - // Find out the format of the version string added to each symbol - // First try just the unversioned symbol - if (dlsym(libicuuc, "u_strlen") == NULL) - { - // Now try just the _majorVer added - sprintf(symbolVersion, "_%d", majorVer); - sprintf(symbolName, "u_strlen%s", symbolVersion); - if ((dlsym(libicuuc, symbolName) == NULL) && (minorVer != -1)) - { - // Now try the _majorVer_minorVer added - sprintf(symbolVersion, "_%d_%d", majorVer, minorVer); - sprintf(symbolName, "u_strlen%s", symbolVersion); - if ((dlsym(libicuuc, symbolName) == NULL) && (subVer != -1)) - { - // Finally, try the _majorVer_minorVer_subVer added - sprintf(symbolVersion, "_%d_%d_%d", majorVer, minorVer, subVer); - sprintf(symbolName, "u_strlen%s", symbolVersion); - if (dlsym(libicuuc, symbolName) == NULL) - { - return FALSE; - } - } - } - } - - return TRUE; -} - // Try to open the necessary ICU libraries static int OpenICULibraries(int majorVer, int minorVer, int subVer, const char* versionPrefix, char* symbolName, char* symbolVersion) { @@ -198,7 +257,8 @@ static int OpenICULibraries(int majorVer, int minorVer, int subVer, const char* libicuuc = dlopen(libicuucName, RTLD_LAZY); if (libicuuc != NULL) { - if (FindSymbolVersion(majorVer, minorVer, subVer, symbolName, symbolVersion)) + char symbolSuffix[SYMBOL_CUSTOM_SUFFIX_SIZE]=""; + if (FindSymbolVersion(majorVer, minorVer, subVer, symbolName, symbolVersion, symbolSuffix)) { libicui18n = dlopen(libicui18nName, RTLD_LAZY); } @@ -306,41 +366,36 @@ static int FindICULibs(const char* versionPrefix, char* symbolName, char* symbol #endif +static void ValidateICUDataCanLoad() +{ + UVersionInfo version; + UErrorCode err = U_ZERO_ERROR; + ulocdata_getCLDRVersion(version, &err); + + if (U_FAILURE(err)) + { + fprintf(stderr, "Could not load ICU data. UErrorCode: %d\n", err); + abort(); + } +} + // GlobalizationNative_LoadICU // This method get called from the managed side during the globalization initialization. // This method shouldn't get called at all if we are running in globalization invariant mode // return 0 if failed to load ICU and 1 otherwise int32_t GlobalizationNative_LoadICU() { -#if defined(TARGET_WINDOWS) - - if (!FindICULibs()) - { - return FALSE; - } - -#define PER_FUNCTION_BLOCK(fn, lib) \ - fn##_ptr = (__typeof(fn)*)GetProcAddress((HMODULE)lib, #fn); \ - if (fn##_ptr == NULL) { fprintf(stderr, "Cannot get symbol %s from " #lib "\nError: %u\n", #fn, GetLastError()); abort(); } + char symbolName[SYMBOL_NAME_SIZE]; + char symbolVersion[MaxICUVersionStringLength + 1]=""; -#elif defined(TARGET_OSX) +#if defined(TARGET_WINDOWS) || defined(TARGET_OSX) if (!FindICULibs()) { return FALSE; } - // Get pointers to all the ICU functions that are needed -#define PER_FUNCTION_BLOCK(fn, lib) \ - fn##_ptr = (__typeof(fn)*)dlsym(lib, #fn); \ - if (fn##_ptr == NULL) { fprintf(stderr, "Cannot get symbol %s from " #lib "\nError: %s\n", #fn, dlerror()); abort(); } - -#else // !TARGET_WINDOWS && !TARGET_OSX - - char symbolName[128]; - char symbolVersion[MaxICUVersionStringLength + 1] = ""; - -#if defined(TARGET_ANDROID) +#elif defined(TARGET_ANDROID) if (!FindICULibs(symbolName, symbolVersion)) { return FALSE; @@ -353,23 +408,54 @@ int32_t GlobalizationNative_LoadICU() return FALSE; } } -#endif +#endif // TARGET_WINDOWS || TARGET_OSX - // Get pointers to all the ICU functions that are needed -#define PER_FUNCTION_BLOCK(fn, lib) \ - c_static_assert_msg((sizeof(#fn) + MaxICUVersionStringLength + 1) <= sizeof(symbolName), "The symbolName is too small for symbol " #fn); \ - sprintf(symbolName, #fn "%s", symbolVersion); \ - fn##_ptr = (__typeof(fn)*)dlsym(lib, symbolName); \ - if (fn##_ptr == NULL) { fprintf(stderr, "Cannot get symbol %s from " #lib "\nError: %s\n", symbolName, dlerror()); abort(); } + FOR_ALL_ICU_FUNCTIONS + ValidateICUDataCanLoad(); + return TRUE; +} + +void GlobalizationNative_InitICUFunctions(void* icuuc, void* icuin, const char* version, const char* suffix) +{ + assert(icuuc != NULL); + assert(icuin != NULL); + assert(version != NULL); + + libicuuc = icuuc; + libicui18n = icuin; + int major = -1; + int minor = -1; + int build = -1; + + char symbolName[SYMBOL_NAME_SIZE]; + char symbolVersion[MaxICUVersionStringWithSuffixLength + 1]=""; + char symbolSuffix[SYMBOL_CUSTOM_SUFFIX_SIZE]=""; + sscanf(version, "%d.%d.%d", &major, &minor, &build); + + if (suffix != NULL) + { + assert(strlen(suffix) + 1 <= SYMBOL_CUSTOM_SUFFIX_SIZE); + +#if defined(TARGET_WINDOWS) + sprintf_s(symbolSuffix, SYMBOL_CUSTOM_SUFFIX_SIZE, "_%s", suffix); +#else + sprintf(symbolSuffix, "_%s", suffix); #endif + } - FOR_ALL_ICU_FUNCTIONS -#undef PER_FUNCTION_BLOCK + if(!FindSymbolVersion(major, minor, build, symbolName, symbolVersion, symbolSuffix)) + { + fprintf(stderr, "Could not find symbol: %s from libicuuc\n", symbolName); + abort(); + } - return TRUE; + FOR_ALL_ICU_FUNCTIONS + ValidateICUDataCanLoad(); } +#undef PER_FUNCTION_BLOCK + // GlobalizationNative_GetICUVersion // return the current loaded ICU version int32_t GlobalizationNative_GetICUVersion() diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.h b/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.h index 49db55214bf8dd..ef940e2509355a 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.h +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.h @@ -5,4 +5,6 @@ PALEXPORT int32_t GlobalizationNative_LoadICU(void); +PALEXPORT void GlobalizationNative_InitICUFunctions(void* icuuc, void* icuin, const char* version, const char* suffix); + PALEXPORT int32_t GlobalizationNative_GetICUVersion(void); diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_internal.h b/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_internal.h index a13ea5f7ad9219..33617e7f5d039d 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_internal.h +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_internal.h @@ -87,8 +87,6 @@ PER_FUNCTION_BLOCK(ucol_safeClone, libicui18n) \ PER_FUNCTION_BLOCK(ucol_setAttribute, libicui18n) \ PER_FUNCTION_BLOCK(ucol_strcoll, libicui18n) \ - PER_FUNCTION_BLOCK(ucurr_forLocale, libicui18n) \ - PER_FUNCTION_BLOCK(ucurr_getName, libicui18n) \ PER_FUNCTION_BLOCK(udat_close, libicui18n) \ PER_FUNCTION_BLOCK(udat_countSymbols, libicui18n) \ PER_FUNCTION_BLOCK(udat_getSymbols, libicui18n) \ @@ -105,9 +103,6 @@ PER_FUNCTION_BLOCK(uidna_nameToASCII, libicuuc) \ PER_FUNCTION_BLOCK(uidna_nameToUnicode, libicuuc) \ PER_FUNCTION_BLOCK(uidna_openUTS46, libicuuc) \ - PER_FUNCTION_BLOCK(uldn_close, libicui18n) \ - PER_FUNCTION_BLOCK(uldn_keyValueDisplayName, libicui18n) \ - PER_FUNCTION_BLOCK(uldn_open, libicui18n) \ PER_FUNCTION_BLOCK(uloc_canonicalize, libicuuc) \ PER_FUNCTION_BLOCK(uloc_countAvailable, libicuuc) \ PER_FUNCTION_BLOCK(uloc_getAvailable, libicuuc) \ @@ -126,6 +121,7 @@ PER_FUNCTION_BLOCK(uloc_getName, libicuuc) \ PER_FUNCTION_BLOCK(uloc_getParent, libicuuc) \ PER_FUNCTION_BLOCK(uloc_setKeywordValue, libicuuc) \ + PER_FUNCTION_BLOCK(ulocdata_getCLDRVersion, libicui18n) \ PER_FUNCTION_BLOCK(ulocdata_getMeasurementSystem, libicui18n) \ PER_FUNCTION_BLOCK(unorm2_getNFCInstance, libicuuc) \ PER_FUNCTION_BLOCK(unorm2_getNFDInstance, libicuuc) \ @@ -150,16 +146,37 @@ PER_FUNCTION_BLOCK(usearch_openFromCollator, libicui18n) #if HAVE_SET_MAX_VARIABLE -#define FOR_ALL_ICU_FUNCTIONS \ - FOR_ALL_UNCONDITIONAL_ICU_FUNCTIONS \ +#define FOR_ALL_SET_VARIABLE_ICU_FUNCTIONS \ PER_FUNCTION_BLOCK(ucol_setMaxVariable, libicui18n) #else -#define FOR_ALL_ICU_FUNCTIONS \ - FOR_ALL_UNCONDITIONAL_ICU_FUNCTIONS \ +#define FOR_ALL_SET_VARIABLE_ICU_FUNCTIONS \ PER_FUNCTION_BLOCK(ucol_setVariableTop, libicui18n) #endif +#if defined(TARGET_WINDOWS) +#define FOR_ALL_OS_CONDITIONAL_ICU_FUNCTIONS \ + PER_FUNCTION_BLOCK(ucurr_forLocale, libicuuc) \ + PER_FUNCTION_BLOCK(ucurr_getName, libicuuc) \ + PER_FUNCTION_BLOCK(uldn_close, libicuuc) \ + PER_FUNCTION_BLOCK(uldn_keyValueDisplayName, libicuuc) \ + PER_FUNCTION_BLOCK(uldn_open, libicuuc) +#else + // Unix ICU is dynamically resolved at runtime and these APIs in old versions + // of ICU were in libicui18n +#define FOR_ALL_OS_CONDITIONAL_ICU_FUNCTIONS \ + PER_FUNCTION_BLOCK(ucurr_forLocale, libicui18n) \ + PER_FUNCTION_BLOCK(ucurr_getName, libicui18n) \ + PER_FUNCTION_BLOCK(uldn_close, libicui18n) \ + PER_FUNCTION_BLOCK(uldn_keyValueDisplayName, libicui18n) \ + PER_FUNCTION_BLOCK(uldn_open, libicui18n) +#endif + +#define FOR_ALL_ICU_FUNCTIONS \ + FOR_ALL_UNCONDITIONAL_ICU_FUNCTIONS \ + FOR_ALL_SET_VARIABLE_ICU_FUNCTIONS \ + FOR_ALL_OS_CONDITIONAL_ICU_FUNCTIONS + // Declare pointers to all the used ICU functions #define PER_FUNCTION_BLOCK(fn, lib) EXTERN_C __typeof(fn)* fn##_ptr; FOR_ALL_ICU_FUNCTIONS @@ -240,6 +257,7 @@ FOR_ALL_ICU_FUNCTIONS #define uloc_getName(...) uloc_getName_ptr(__VA_ARGS__) #define uloc_getParent(...) uloc_getParent_ptr(__VA_ARGS__) #define uloc_setKeywordValue(...) uloc_setKeywordValue_ptr(__VA_ARGS__) +#define ulocdata_getCLDRVersion(...) ulocdata_getCLDRVersion_ptr(__VA_ARGS__) #define ulocdata_getMeasurementSystem(...) ulocdata_getMeasurementSystem_ptr(__VA_ARGS__) #define unorm2_getNFCInstance(...) unorm2_getNFCInstance_ptr(__VA_ARGS__) #define unorm2_getNFDInstance(...) unorm2_getNFDInstance_ptr(__VA_ARGS__) diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_internal_android.h b/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_internal_android.h index 5a3ac55072e860..17e1b8d8c8ad03 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_internal_android.h +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_internal_android.h @@ -490,6 +490,7 @@ uint32_t uloc_getLCID(const char * localeID); int32_t uloc_getName(const char * localeID, char * name, int32_t nameCapacity, UErrorCode * err); int32_t uloc_getParent(const char * localeID, char * parent, int32_t parentCapacity, UErrorCode * err); int32_t uloc_setKeywordValue(const char * keywordName, const char * keywordValue, char * buffer, int32_t bufferCapacity, UErrorCode * status); +void ulocdata_getCLDRVersion(UVersionInfo versionArray, UErrorCode * status); UMeasurementSystem ulocdata_getMeasurementSystem(const char * localeID, UErrorCode * status); const UNormalizer2 * unorm2_getNFCInstance(UErrorCode * pErrorCode); const UNormalizer2 * unorm2_getNFDInstance(UErrorCode * pErrorCode); diff --git a/src/libraries/System.Globalization/tests/NlsTests/NlsSwitchTests.cs b/src/libraries/System.Globalization/tests/NlsTests/NlsSwitchTests.cs index c7885ac41774ef..58da0a9dae525f 100644 --- a/src/libraries/System.Globalization/tests/NlsTests/NlsSwitchTests.cs +++ b/src/libraries/System.Globalization/tests/NlsTests/NlsSwitchTests.cs @@ -30,7 +30,7 @@ public static void NlsRuntimeSwitchIsHonored() [Fact] public static void IcuShouldNotBeLoaded() { - Assert.False(PlatformDetection.IsIcuGlobalization); + Assert.False(PlatformDetection.IsIcuGlobalization, $"Found ICU: {PlatformDetection.ICUVersion}"); } } }