Add app-local support for ICU (#35383)
authorSantiago Fernandez Madero <safern@microsoft.com>
Thu, 7 May 2020 01:09:52 +0000 (18:09 -0700)
committerGitHub <noreply@github.com>
Thu, 7 May 2020 01:09:52 +0000 (18:09 -0700)
* Add app-local support for ICU

* Fix android build

* Fix Unix distros with an old ICU version

* PR Feedback

* PR Feedback

* Add ICU data validation when initializing

* Fix linux build

12 files changed:
src/coreclr/src/System.Private.CoreLib/PinvokeAnalyzerExceptionList.analyzerdata
src/coreclr/src/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.Unix.cs
src/coreclr/src/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.Windows.cs
src/coreclr/src/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.cs
src/coreclr/src/libraries-native/entrypoints.c
src/libraries/Common/src/Interop/Interop.Collation.cs
src/libraries/Common/src/Interop/Interop.ICU.cs
src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.c
src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.h
src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_internal.h
src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_internal_android.h
src/libraries/System.Globalization/tests/NlsTests/NlsSwitchTests.cs

index c53e2b7..f2570c7 100644 (file)
@@ -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
index 6cb513a..14c7699 100644 (file)
@@ -17,7 +17,11 @@ namespace System.Globalization
             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 @@ namespace System.Globalization
             }
             return invariantEnabled;
         }
+
+        private static void LoadAppLocalIcuCore(ReadOnlySpan<char> version, ReadOnlySpan<char> 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);
+        }
     }
 }
index 50285f0..1fa21bc 100644 (file)
@@ -12,6 +12,50 @@ namespace System.Globalization
 
         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<char> version, ReadOnlySpan<char> 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<char> 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);
+        }
     }
 }
index 1433e52..dd460b1 100644 (file)
@@ -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 @@ namespace System.Globalization
         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 @@ namespace System.Globalization
 
             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<char> icuSuffix = default;
+            ReadOnlySpan<char> 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<char> baseName, ReadOnlySpan<char> suffix, ReadOnlySpan<char> extension, ReadOnlySpan<char> 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;
+        }
     }
 }
index 68553e9..c0dc639 100644 (file)
@@ -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)
index ece19be..07cf42b 100644 (file)
@@ -27,6 +27,7 @@ internal static partial class Interop
 
         [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);
 
index 0dce072..fb8b56e 100644 (file)
@@ -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 Interop
         [DllImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_LoadICU")]
         internal static extern int LoadICU();
 
+        internal static void InitICUFunctions(IntPtr icuuc, IntPtr icuin, ReadOnlySpan<char> version, ReadOnlySpan<char> 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();
     }
index fbea9cf..1a594c4 100644 (file)
 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()
index 49db552..ef940e2 100644 (file)
@@ -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);
index a13ea5f..33617e7 100644 (file)
@@ -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) \
     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) \
     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) \
     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__)
index 5a3ac55..17e1b8d 100644 (file)
@@ -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);
index c7885ac..58da0a9 100644 (file)
@@ -30,7 +30,7 @@ namespace System.Globalization.Tests
         [Fact]
         public static void IcuShouldNotBeLoaded()
         {
-            Assert.False(PlatformDetection.IsIcuGlobalization);
+            Assert.False(PlatformDetection.IsIcuGlobalization, $"Found ICU: {PlatformDetection.ICUVersion}");
         }
     }
 }