In iOS we support loading a custom dat file when working with ICU. The way this worked originally was the mono runtime exported a function that native code would call into (similar to wasm). After thinking about it a bit, it makes more sense to load this the same way we do on desktop, but with the ability to provide the path to an ICU dat file via an AppContext key `ICU_DAT_FILE_PATH`. This can be provided before Xamarin iOS calls `monovm_initialize` and they won't have to worry about calling some special function.
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Globalization
+ {
+ [DllImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_LoadICUData")]
+ internal static extern int LoadICUData(string path);
+ }
+}
DllImportEntry(GlobalizationNative_IsPredefinedLocale)
DllImportEntry(GlobalizationNative_LastIndexOf)
DllImportEntry(GlobalizationNative_LoadICU)
+#if defined(STATIC_ICU)
+ DllImportEntry(GlobalizationNative_LoadICUData)
+#endif
DllImportEntry(GlobalizationNative_NormalizeString)
DllImportEntry(GlobalizationNative_StartsWith)
DllImportEntry(GlobalizationNative_ToAscii)
#if defined(STATIC_ICU)
-PALEXPORT int32_t GlobalizationNative_LoadICUData(char* path);
+PALEXPORT int32_t GlobalizationNative_LoadICUData(const char* path);
PALEXPORT const char* GlobalizationNative_GetICUDTName(const char* culture);
static int32_t isLoaded = 0;
static int32_t isDataSet = 0;
+static void log_shim_error(const char* format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+}
+
static void log_icu_error(const char* name, UErrorCode status)
{
const char * statusText = u_errorName(status);
- fprintf(stderr, "ICU call %s failed with error #%d '%s'.\n", name, status, statusText);
+ log_shim_error("ICU call %s failed with error #%d '%s'.\n", name, status, statusText);
}
static void U_CALLCONV icu_trace_data(const void* context, int32_t fnNumber, int32_t level, const char* fmt, va_list args)
}
}
-int32_t GlobalizationNative_LoadICUData(char* path)
+int32_t GlobalizationNative_LoadICUData(const char* path)
{
int32_t ret = -1;
char* icu_data;
- FILE *fp = fopen (path, "rb");
+ FILE *fp = fopen(path, "rb");
if (fp == NULL) {
- fprintf (stderr, "Unable to load ICU dat file '%s'.", path);
+ log_shim_error("Unable to load ICU dat file '%s'.", path);
return ret;
}
- if (fseek (fp, 0L, SEEK_END) != 0) {
- fprintf (stderr, "Unable to determine size of the dat file");
+ if (fseek(fp, 0L, SEEK_END) != 0) {
+ fclose(fp);
+ log_shim_error("Unable to determine size of the dat file");
return ret;
}
- long bufsize = ftell (fp);
+ long bufsize = ftell(fp);
if (bufsize == -1) {
- fprintf (stderr, "Unable to determine size of the ICU dat file.");
+ fclose(fp);
+ log_shim_error("Unable to determine size of the ICU dat file.");
return ret;
}
- icu_data = malloc (sizeof (char) * (bufsize + 1));
+ icu_data = malloc(sizeof(char) * (bufsize + 1));
+
+ if (icu_data == NULL) {
+ fclose(fp);
+ log_shim_error("Unable to allocate enough to read the ICU dat file");
+ return ret;
+ }
- if (fseek (fp, 0L, SEEK_SET) != 0) {
- fprintf (stderr, "Unable to seek ICU dat file.");
+ if (fseek(fp, 0L, SEEK_SET) != 0) {
+ fclose(fp);
+ log_shim_error("Unable to seek ICU dat file.");
return ret;
}
- fread (icu_data, sizeof (char), bufsize, fp);
- if (ferror ( fp ) != 0 ) {
- fprintf (stderr, "Unable to read ICU dat file");
+ fread(icu_data, sizeof(char), bufsize, fp);
+ if (ferror( fp ) != 0 ) {
+ fclose(fp);
+ log_shim_error("Unable to read ICU dat file");
return ret;
}
- fclose (fp);
+ fclose(fp);
+
+ if (load_icu_data(icu_data) == 0) {
+ log_shim_error("ICU BAD EXIT %d.", ret);
+ return ret;
+ }
- return load_icu_data (icu_data);
+ return GlobalizationNative_LoadICU();
}
const char* GlobalizationNative_GetICUDTName(const char* culture)
<PropertyGroup>
<Nullable>enable</Nullable>
<IsOSXLike Condition="'$(TargetsOSX)' == 'true' or '$(TargetsMacCatalyst)' == 'true' or '$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true'">true</IsOSXLike>
+ <IsiOSLike Condition="'$(TargetsMacCatalyst)' == 'true' or '$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true'">true</IsiOSLike>
<SupportsArmIntrinsics Condition="'$(Platform)' == 'arm64'">true</SupportsArmIntrinsics>
<SupportsX86Intrinsics Condition="'$(Platform)' == 'x64' or ('$(Platform)' == 'x86' and '$(TargetsUnix)' != 'true')">true</SupportsX86Intrinsics>
<ILLinkSharedDirectory>$(MSBuildThisFileDirectory)ILLink\</ILLinkSharedDirectory>
<Compile Include="$(CommonPath)Interop\Interop.ICU.cs">
<Link>Common\Interop\Interop.ICU.cs</Link>
</Compile>
+ <Compile Include="$(CommonPath)Interop\Interop.ICU.iOS.cs" Condition="'$(IsiOSLike)' == 'true'">
+ <Link>Common\Interop\Interop.ICU.iOS.cs</Link>
+ </Compile>
<Compile Include="$(CommonPath)Interop\Interop.Idna.cs">
<Link>Common\Interop\Interop.Idna.cs</Link>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.NoRegistry.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.UnixOrBrowser.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.OSVersion.OSX.cs" Condition="'$(IsOSXLike)' == 'true'" />
- <Compile Include="$(MSBuildThisFileDirectory)System\Environment.GetFolderPathCore.Unix.cs" Condition="'$(TargetsMacCatalyst)' != 'true' and '$(TargetsiOS)' != 'true' and '$(TargetstvOS)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Environment.GetFolderPathCore.Unix.cs" Condition="'$(IsiOSLike)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CalendarData.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureData.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureInfo.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\GlobalizationMode.Unix.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\GlobalizationMode.LoadICU.Unix.cs" Condition="'$(IsiOSLike)' != 'true'" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Globalization\GlobalizationMode.LoadICU.iOS.cs" Condition="'$(IsiOSLike)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\HijriCalendar.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Guid.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStreamHelpers.Unix.cs" />
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Globalization
+{
+ internal static partial class GlobalizationMode
+ {
+ private static int LoadICU() => Interop.Globalization.LoadICU();
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Globalization
+{
+ internal static partial class GlobalizationMode
+ {
+ private static int LoadICU()
+ {
+ object? datPath = AppContext.GetData("ICU_DAT_FILE_PATH");
+ return (datPath != null) ? Interop.Globalization.LoadICUData(datPath!.ToString()!) : Interop.Globalization.LoadICU();
+ }
+ }
+}
}
else
{
- int loaded = Interop.Globalization.LoadICU();
+ int loaded = LoadICU();
if (loaded == 0 && !OperatingSystem.IsBrowser())
{
// This can't go into resources, because a resource lookup requires globalization, which requires ICU
if(STATIC_ICU)
set(pal_icushim_sources_base
pal_icushim_static.c)
+ add_definitions(-DSTATIC_ICU=1)
else()
set(pal_icushim_sources_base
pal_icushim.c)
if (GenerateXcodeProject)
{
- Xcode generator = new Xcode(TargetOS);
+ Xcode generator = new Xcode(TargetOS, Arch);
generator.EnableRuntimeLogging = EnableRuntimeLogging;
XcodeProjectPath = generator.GenerateXCode(ProjectName, MainLibraryFileName, assemblerFiles,
#define MONO_ENTER_GC_UNSAFE
#define MONO_EXIT_GC_UNSAFE
+#define APPLE_RUNTIME_IDENTIFIER "//%APPLE_RUNTIME_IDENTIFIER%"
+
const char *
get_bundle_path (void)
{
//%DllMap%
}
-int32_t GlobalizationNative_LoadICUData(char *path);
-
-static int32_t load_icu_data ()
-{
- char path [1024];
- int res;
-
- const char *dname = "icudt.dat";
- const char *bundle = get_bundle_path ();
-
- os_log_info (OS_LOG_DEFAULT, "Loading ICU data file '%s'.", dname);
- res = snprintf (path, sizeof (path) - 1, "%s/%s", bundle, dname);
- assert (res > 0);
-
- return GlobalizationNative_LoadICUData(path);
-}
-
#if FORCE_INTERPRETER || FORCE_AOT || (!TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST)
void mono_jit_set_aot_mode (MonoAotMode mode);
void register_aot_modules (void);
setenv ("MONO_LOG_MASK", "all", TRUE);
#endif
-#if !INVARIANT_GLOBALIZATION
- int32_t ret = load_icu_data ();
-
- if (ret == 0) {
- os_log_info (OS_LOG_DEFAULT, "ICU BAD EXIT %d.", ret);
- exit (ret);
- return;
- }
-#endif
-
id args_array = [[NSProcessInfo processInfo] arguments];
assert ([args_array count] <= 128);
const char *managed_argv [128];
const char* bundle = get_bundle_path ();
chdir (bundle);
+ char icu_dat_path [1024];
+ int res;
+
+ res = snprintf (icu_dat_path, sizeof (icu_dat_path) - 1, "%s/%s", bundle, "icudt.dat");
+ assert (res > 0);
+
// TODO: set TRUSTED_PLATFORM_ASSEMBLIES, APP_PATHS and NATIVE_DLL_SEARCH_DIRECTORIES
- monovm_initialize(0, NULL, NULL);
+ const char *appctx_keys [] = {
+ "RUNTIME_IDENTIFIER",
+ "APP_CONTEXT_BASE_DIRECTORY",
+#ifndef INVARIANT_GLOBALIZATION
+ "ICU_DAT_FILE_PATH"
+#endif
+ };
+ const char *appctx_values [] = {
+ APPLE_RUNTIME_IDENTIFIER,
+ bundle,
+#ifndef INVARIANT_GLOBALIZATION
+ icu_dat_path
+#endif
+ };
+
+ monovm_initialize (sizeof (appctx_keys) / sizeof (appctx_keys [0]), appctx_keys, appctx_values);
#if FORCE_INTERPRETER
os_log_info (OS_LOG_DEFAULT, "INTERP Enabled");
assert (assembly);
os_log_info (OS_LOG_DEFAULT, "Executable: %{public}s", executable);
- int res = mono_jit_exec (mono_domain_get (), assembly, argi, managed_argv);
+ res = mono_jit_exec (mono_domain_get (), assembly, argi, managed_argv);
// Print this so apps parsing logs can detect when we exited
os_log_info (OS_LOG_DEFAULT, "Exit code: %d.", res);
internal class Xcode
{
+ private string RuntimeIdentifier { get; set; }
private string SysRoot { get; set; }
private string Target { get; set; }
- public Xcode(string target)
+ public Xcode(string target, string arch)
{
Target = target;
switch (Target)
SysRoot = Utils.RunProcess("xcrun", "--sdk macosx --show-sdk-path");
break;
}
+
+ RuntimeIdentifier = $"{Target}-{arch}";
}
public bool EnableRuntimeLogging { get; set; }
File.WriteAllText(Path.Combine(binDir, "runtime.m"),
Utils.GetEmbeddedResource("runtime.m")
.Replace("//%DllMap%", dllMap.ToString())
+ .Replace("//%APPLE_RUNTIME_IDENTIFIER%", RuntimeIdentifier)
.Replace("%EntryPointLibName%", Path.GetFileName(entryPointLib)));
Utils.RunProcess("cmake", cmakeArgs.ToString(), workingDir: binDir);