Skip to content

Commit 1d9c402

Browse files
authored
Support loading ICU data from managed Interop (#49406)
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.
1 parent 6791d05 commit 1d9c402

File tree

12 files changed

+122
-50
lines changed

12 files changed

+122
-50
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
internal static partial class Interop
8+
{
9+
internal static partial class Globalization
10+
{
11+
[DllImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_LoadICUData")]
12+
internal static extern int LoadICUData(string path);
13+
}
14+
}

src/libraries/Native/Unix/System.Globalization.Native/entrypoints.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ static const Entry s_globalizationNative[] =
5050
DllImportEntry(GlobalizationNative_IsPredefinedLocale)
5151
DllImportEntry(GlobalizationNative_LastIndexOf)
5252
DllImportEntry(GlobalizationNative_LoadICU)
53+
#if defined(STATIC_ICU)
54+
DllImportEntry(GlobalizationNative_LoadICUData)
55+
#endif
5356
DllImportEntry(GlobalizationNative_NormalizeString)
5457
DllImportEntry(GlobalizationNative_StartsWith)
5558
DllImportEntry(GlobalizationNative_ToAscii)

src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ PALEXPORT int32_t GlobalizationNative_GetICUVersion(void);
1111

1212
#if defined(STATIC_ICU)
1313

14-
PALEXPORT int32_t GlobalizationNative_LoadICUData(char* path);
14+
PALEXPORT int32_t GlobalizationNative_LoadICUData(const char* path);
1515

1616
PALEXPORT const char* GlobalizationNative_GetICUDTName(const char* culture);
1717

src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_static.c

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,19 @@
2222
static int32_t isLoaded = 0;
2323
static int32_t isDataSet = 0;
2424

25+
static void log_shim_error(const char* format, ...)
26+
{
27+
va_list args;
28+
29+
va_start(args, format);
30+
vfprintf(stderr, format, args);
31+
va_end(args);
32+
}
33+
2534
static void log_icu_error(const char* name, UErrorCode status)
2635
{
2736
const char * statusText = u_errorName(status);
28-
fprintf(stderr, "ICU call %s failed with error #%d '%s'.\n", name, status, statusText);
37+
log_shim_error("ICU call %s failed with error #%d '%s'.\n", name, status, statusText);
2938
}
3039

3140
static void U_CALLCONV icu_trace_data(const void* context, int32_t fnNumber, int32_t level, const char* fmt, va_list args)
@@ -89,45 +98,60 @@ static int32_t load_icu_data(void* pData)
8998
}
9099
}
91100

92-
int32_t GlobalizationNative_LoadICUData(char* path)
101+
int32_t GlobalizationNative_LoadICUData(const char* path)
93102
{
94103
int32_t ret = -1;
95104
char* icu_data;
96105

97-
FILE *fp = fopen (path, "rb");
106+
FILE *fp = fopen(path, "rb");
98107
if (fp == NULL) {
99-
fprintf (stderr, "Unable to load ICU dat file '%s'.", path);
108+
log_shim_error("Unable to load ICU dat file '%s'.", path);
100109
return ret;
101110
}
102111

103-
if (fseek (fp, 0L, SEEK_END) != 0) {
104-
fprintf (stderr, "Unable to determine size of the dat file");
112+
if (fseek(fp, 0L, SEEK_END) != 0) {
113+
fclose(fp);
114+
log_shim_error("Unable to determine size of the dat file");
105115
return ret;
106116
}
107117

108-
long bufsize = ftell (fp);
118+
long bufsize = ftell(fp);
109119

110120
if (bufsize == -1) {
111-
fprintf (stderr, "Unable to determine size of the ICU dat file.");
121+
fclose(fp);
122+
log_shim_error("Unable to determine size of the ICU dat file.");
112123
return ret;
113124
}
114125

115-
icu_data = malloc (sizeof (char) * (bufsize + 1));
126+
icu_data = malloc(sizeof(char) * (bufsize + 1));
127+
128+
if (icu_data == NULL) {
129+
fclose(fp);
130+
log_shim_error("Unable to allocate enough to read the ICU dat file");
131+
return ret;
132+
}
116133

117-
if (fseek (fp, 0L, SEEK_SET) != 0) {
118-
fprintf (stderr, "Unable to seek ICU dat file.");
134+
if (fseek(fp, 0L, SEEK_SET) != 0) {
135+
fclose(fp);
136+
log_shim_error("Unable to seek ICU dat file.");
119137
return ret;
120138
}
121139

122-
fread (icu_data, sizeof (char), bufsize, fp);
123-
if (ferror ( fp ) != 0 ) {
124-
fprintf (stderr, "Unable to read ICU dat file");
140+
fread(icu_data, sizeof(char), bufsize, fp);
141+
if (ferror( fp ) != 0 ) {
142+
fclose(fp);
143+
log_shim_error("Unable to read ICU dat file");
125144
return ret;
126145
}
127146

128-
fclose (fp);
147+
fclose(fp);
148+
149+
if (load_icu_data(icu_data) == 0) {
150+
log_shim_error("ICU BAD EXIT %d.", ret);
151+
return ret;
152+
}
129153

130-
return load_icu_data (icu_data);
154+
return GlobalizationNative_LoadICU();
131155
}
132156

133157
const char* GlobalizationNative_GetICUDTName(const char* culture)

src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<PropertyGroup>
1212
<Nullable>enable</Nullable>
1313
<IsOSXLike Condition="'$(TargetsOSX)' == 'true' or '$(TargetsMacCatalyst)' == 'true' or '$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true'">true</IsOSXLike>
14+
<IsiOSLike Condition="'$(TargetsMacCatalyst)' == 'true' or '$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true'">true</IsiOSLike>
1415
<SupportsArmIntrinsics Condition="'$(Platform)' == 'arm64'">true</SupportsArmIntrinsics>
1516
<SupportsX86Intrinsics Condition="'$(Platform)' == 'x64' or ('$(Platform)' == 'x86' and '$(TargetsUnix)' != 'true')">true</SupportsX86Intrinsics>
1617
<ILLinkSharedDirectory>$(MSBuildThisFileDirectory)ILLink\</ILLinkSharedDirectory>
@@ -1082,6 +1083,9 @@
10821083
<Compile Include="$(CommonPath)Interop\Interop.ICU.cs">
10831084
<Link>Common\Interop\Interop.ICU.cs</Link>
10841085
</Compile>
1086+
<Compile Include="$(CommonPath)Interop\Interop.ICU.iOS.cs" Condition="'$(IsiOSLike)' == 'true'">
1087+
<Link>Common\Interop\Interop.ICU.iOS.cs</Link>
1088+
</Compile>
10851089
<Compile Include="$(CommonPath)Interop\Interop.Idna.cs">
10861090
<Link>Common\Interop\Interop.Idna.cs</Link>
10871091
</Compile>
@@ -1831,11 +1835,13 @@
18311835
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.NoRegistry.cs" />
18321836
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.UnixOrBrowser.cs" />
18331837
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.OSVersion.OSX.cs" Condition="'$(IsOSXLike)' == 'true'" />
1834-
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.GetFolderPathCore.Unix.cs" Condition="'$(TargetsMacCatalyst)' != 'true' and '$(TargetsiOS)' != 'true' and '$(TargetstvOS)' != 'true'" />
1838+
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.GetFolderPathCore.Unix.cs" Condition="'$(IsiOSLike)' != 'true'" />
18351839
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CalendarData.Unix.cs" />
18361840
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureData.Unix.cs" />
18371841
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureInfo.Unix.cs" />
18381842
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\GlobalizationMode.Unix.cs" />
1843+
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\GlobalizationMode.LoadICU.Unix.cs" Condition="'$(IsiOSLike)' != 'true'" />
1844+
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\GlobalizationMode.LoadICU.iOS.cs" Condition="'$(IsiOSLike)' == 'true'" />
18391845
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\HijriCalendar.Unix.cs" />
18401846
<Compile Include="$(MSBuildThisFileDirectory)System\Guid.Unix.cs" />
18411847
<Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStreamHelpers.Unix.cs" />
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Globalization
5+
{
6+
internal static partial class GlobalizationMode
7+
{
8+
private static int LoadICU() => Interop.Globalization.LoadICU();
9+
}
10+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Globalization
5+
{
6+
internal static partial class GlobalizationMode
7+
{
8+
private static int LoadICU()
9+
{
10+
object? datPath = AppContext.GetData("ICU_DAT_FILE_PATH");
11+
return (datPath != null) ? Interop.Globalization.LoadICUData(datPath!.ToString()!) : Interop.Globalization.LoadICU();
12+
}
13+
}
14+
}

src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.Unix.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ private static bool GetGlobalizationInvariantMode()
2222
}
2323
else
2424
{
25-
int loaded = Interop.Globalization.LoadICU();
25+
int loaded = LoadICU();
2626
if (loaded == 0 && !OperatingSystem.IsBrowser())
2727
{
2828
// This can't go into resources, because a resource lookup requires globalization, which requires ICU

src/mono/mono/mini/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ if(HAVE_SYS_ICU)
5050
if(STATIC_ICU)
5151
set(pal_icushim_sources_base
5252
pal_icushim_static.c)
53+
add_definitions(-DSTATIC_ICU=1)
5354
else()
5455
set(pal_icushim_sources_base
5556
pal_icushim.c)

src/tasks/AppleAppBuilder/AppleAppBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ public override bool Execute()
182182

183183
if (GenerateXcodeProject)
184184
{
185-
Xcode generator = new Xcode(TargetOS);
185+
Xcode generator = new Xcode(TargetOS, Arch);
186186
generator.EnableRuntimeLogging = EnableRuntimeLogging;
187187

188188
XcodeProjectPath = generator.GenerateXCode(ProjectName, MainLibraryFileName, assemblerFiles,

src/tasks/AppleAppBuilder/Templates/runtime.m

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
#define MONO_ENTER_GC_UNSAFE
2525
#define MONO_EXIT_GC_UNSAFE
2626

27+
#define APPLE_RUNTIME_IDENTIFIER "//%APPLE_RUNTIME_IDENTIFIER%"
28+
2729
const char *
2830
get_bundle_path (void)
2931
{
@@ -203,23 +205,6 @@
203205
//%DllMap%
204206
}
205207

206-
int32_t GlobalizationNative_LoadICUData(char *path);
207-
208-
static int32_t load_icu_data ()
209-
{
210-
char path [1024];
211-
int res;
212-
213-
const char *dname = "icudt.dat";
214-
const char *bundle = get_bundle_path ();
215-
216-
os_log_info (OS_LOG_DEFAULT, "Loading ICU data file '%s'.", dname);
217-
res = snprintf (path, sizeof (path) - 1, "%s/%s", bundle, dname);
218-
assert (res > 0);
219-
220-
return GlobalizationNative_LoadICUData(path);
221-
}
222-
223208
#if FORCE_INTERPRETER || FORCE_AOT || (!TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST)
224209
void mono_jit_set_aot_mode (MonoAotMode mode);
225210
void register_aot_modules (void);
@@ -237,16 +222,6 @@ static int32_t load_icu_data ()
237222
setenv ("MONO_LOG_MASK", "all", TRUE);
238223
#endif
239224

240-
#if !INVARIANT_GLOBALIZATION
241-
int32_t ret = load_icu_data ();
242-
243-
if (ret == 0) {
244-
os_log_info (OS_LOG_DEFAULT, "ICU BAD EXIT %d.", ret);
245-
exit (ret);
246-
return;
247-
}
248-
#endif
249-
250225
id args_array = [[NSProcessInfo processInfo] arguments];
251226
assert ([args_array count] <= 128);
252227
const char *managed_argv [128];
@@ -261,8 +236,29 @@ static int32_t load_icu_data ()
261236
const char* bundle = get_bundle_path ();
262237
chdir (bundle);
263238

239+
char icu_dat_path [1024];
240+
int res;
241+
242+
res = snprintf (icu_dat_path, sizeof (icu_dat_path) - 1, "%s/%s", bundle, "icudt.dat");
243+
assert (res > 0);
244+
264245
// TODO: set TRUSTED_PLATFORM_ASSEMBLIES, APP_PATHS and NATIVE_DLL_SEARCH_DIRECTORIES
265-
monovm_initialize(0, NULL, NULL);
246+
const char *appctx_keys [] = {
247+
"RUNTIME_IDENTIFIER",
248+
"APP_CONTEXT_BASE_DIRECTORY",
249+
#ifndef INVARIANT_GLOBALIZATION
250+
"ICU_DAT_FILE_PATH"
251+
#endif
252+
};
253+
const char *appctx_values [] = {
254+
APPLE_RUNTIME_IDENTIFIER,
255+
bundle,
256+
#ifndef INVARIANT_GLOBALIZATION
257+
icu_dat_path
258+
#endif
259+
};
260+
261+
monovm_initialize (sizeof (appctx_keys) / sizeof (appctx_keys [0]), appctx_keys, appctx_values);
266262

267263
#if FORCE_INTERPRETER
268264
os_log_info (OS_LOG_DEFAULT, "INTERP Enabled");
@@ -300,7 +296,7 @@ static int32_t load_icu_data ()
300296
assert (assembly);
301297
os_log_info (OS_LOG_DEFAULT, "Executable: %{public}s", executable);
302298

303-
int res = mono_jit_exec (mono_domain_get (), assembly, argi, managed_argv);
299+
res = mono_jit_exec (mono_domain_get (), assembly, argi, managed_argv);
304300
// Print this so apps parsing logs can detect when we exited
305301
os_log_info (OS_LOG_DEFAULT, "Exit code: %d.", res);
306302

src/tasks/AppleAppBuilder/Xcode.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99

1010
internal class Xcode
1111
{
12+
private string RuntimeIdentifier { get; set; }
1213
private string SysRoot { get; set; }
1314
private string Target { get; set; }
1415

15-
public Xcode(string target)
16+
public Xcode(string target, string arch)
1617
{
1718
Target = target;
1819
switch (Target)
@@ -27,6 +28,8 @@ public Xcode(string target)
2728
SysRoot = Utils.RunProcess("xcrun", "--sdk macosx --show-sdk-path");
2829
break;
2930
}
31+
32+
RuntimeIdentifier = $"{Target}-{arch}";
3033
}
3134

3235
public bool EnableRuntimeLogging { get; set; }
@@ -175,6 +178,7 @@ public string GenerateXCode(
175178
File.WriteAllText(Path.Combine(binDir, "runtime.m"),
176179
Utils.GetEmbeddedResource("runtime.m")
177180
.Replace("//%DllMap%", dllMap.ToString())
181+
.Replace("//%APPLE_RUNTIME_IDENTIFIER%", RuntimeIdentifier)
178182
.Replace("%EntryPointLibName%", Path.GetFileName(entryPointLib)));
179183

180184
Utils.RunProcess("cmake", cmakeArgs.ToString(), workingDir: binDir);

0 commit comments

Comments
 (0)