This example incorporates advanced ICU capabilities in DevExpress-powered Blazor WASM apps through custom native wrappers/ICU data files and integrates them with DevExpress ICU interop.
This configuration/description addresses the following:
- How to prevent WASM trimming of advanced ICU functions.
- How to add a native C wrapper to your Blazor WASM project.
- How to supply full ICU data files required for complex script rendering.
- How to implement and register a custom ICU interop provider compatible with DevExpress product libraries.
With this strategy, your DevExpress-powered Blazor WASM application can work with complex typography, bidirectional text, advanced script detection, and other ICU-powered features.
The default WASM toolchain strips any unused native ICU functions. If your application processes text that requires additional ICU features (BiDi runs, script runs, break iterators), these functions must be preserved manually.
The most reliable way to force the compiler to retain them is to expose the required ICU methods through a C wrapper. This wrapper marks the methods as used and prevents trimming.
Create a wrapper file (for example, native/wrapper.c) and include all ICU functions you intend to use. Each wrapper method must directly call the corresponding ICU export.
<ItemGroup>
<NativeFileReference Include="native\wrapper.c" />
</ItemGroup>The following code snippet illustrates the basic pattern for creating a C wrapper around ICU functions:
#include <stdio.h>
#include <dirent.h>
// Declare the original ICU function with extern.
extern int ubidi_countRuns(void* pBiDi, int* errorCode);
// Define a _wrapper function that calls it.
int ubidi_countRuns_wrapper(void* pBiDi, int* errorCode) {
return ubidi_countRuns(pBiDi, errorCode);
}
...File to review: wrapper.c
The .NET SDK strips ICU data files and does not contain an option to include full data. For correct interop, you must download the full ICU data package that matches the ICU version used by your .NET SDK.
Download the corresponding archive from GitHub:
| .NET SDK | ICU version | Download |
|---|---|---|
| .NET 8 | 68 | icu4c-68.2-data-bin-l.zip |
| .NET 9 | 68 | icu4c-68.2-data-bin-l.zip |
| .NET 10 | 72 | icu4c-72_1-data-bin-l.zip |
Extract the .dat file from the archive and include it in your project.
<PropertyGroup>
<WasmNativeStrip>false</WasmNativeStrip>
<BlazorIcuDataFileName>native/icudt_custom.dat</BlazorIcuDataFileName>
</PropertyGroup>Note
The file name must start with icudt (for example, icudt_custom.dat) because this is required by the WASM toolchain.
File to review: icudt_custom.dat
DevExpress supplies an ICustomICUInterop interface that allows you to route ICU calls to your native implementation.
Your C# wrapper should use DllImport to reference functions exposed by the native wrapper.c file as illustrated in the following example:
public class CustomInterop : ICustomICUInterop {
[DllImport("wrapper")]
static extern int ubidi_countRuns_wrapper(IntPtr pBiDi, out UErrorCode errorCode);
...
public int UBiDiCountRuns(IntPtr pBiDi, out UErrorCode errorCode) {
return ubidi_countRuns_wrapper(pBiDi, out errorCode);
}
...
}File to review: IcuInterop.cs
Important
The library name in DllImport must match the wrapper file name without extension.
If native function names differ, specify the entry point:
[DllImport("wrapper", EntryPoint = "my_custom_function")]
Assign your custom interop implementation during application initialization:
ICUInterop.CustomIcuInterop = new CustomInterop();File to review: Program.cs
Once this is complete, DevExpress components will route all ICU dependent operations through your implementation and use the full ICU data you supplied.
(you will be redirected to DevExpress.com to submit your response)