Skip to content

ahmed605/DynamicXAML

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DynamicXAML

DynamicXaml is a library that allows you to dynamically load XAML resources (such as XAML/XBF files embedded inside PRI files) at runtime in UWP and WinUI 3 applications.

With DynamicXaml you can dynamically load external XAML libraries at runtime, which is useful for scenarios like plugin systems, theming, or modular applications where you want to load UI components that aren't part of the application itself nor compiled with it.

Installation

UWP version

UWP

WinUI 3 version

WinUI

Usage

On libraries side

Libraries have to be either native Windows Runtime Components or .NET Class Libraries.

Libraries should add the following property to their project (e.g. csproj, vcxproj, ...) file:

<DisableEmbeddedXbf>false</DisableEmbeddedXbf>

This property will ensure that all XAML resources are embedded into the library's PRI file, which is required for DynamicXaml to work.

If you have no control over the library or cannot add the above property for any other reason, you can use MrmLib library to embed resources referenced by path into the PRI at runtime, the following example demonstrates how to do this:

using MrmLib;

...

StorageFile priStorageFile = ...;
var priFile = await PriFile.LoadAsync(priStorageFile);

// PRI root folder is the folder where the resources referenced by path in the PRI are located,
// it usually has the same name as the PRI file (without the .pri extension).

StorageFolder priRootFolder = ...;
var result = await priFile.ReplacePathCandidatesWithEmbeddedDataAsync(priRootFolder);

StorageFile modifiedPriStorageFile = ...;
await priFile.WriteAsync(modifiedPriStorageFile);

// You can also do this selectively by manually iterating over priFile.ResourceCandidates,
// and then setting DataValue on the desired resource candidates only.

Additional considerations for UWP libraries

Important

These considerations are only applicable to UWP libraries and only to manual usages of ms-appx:/// URIs, they do NOT apply to WinUI 3 libraries nor code generated by Xaml Compiler.

If the library is manually accessing its resources via ms-appx:/// URIs, these should be changed to include the library's PRI index name after the second slash like the following example:

<!-- Before -->
<ResourceDictionary Source="ms-appx:///My.Dynamic.Lib/Themes/Generic.xaml" />
<!-- After -->
<ResourceDictionary Source="ms-appx://My.Dynamic.Lib/My.Dynamic.Lib/Themes/Generic.xaml" />

This limitation exists because Windows.UI.Xaml (aka UWP XAML) uses a private MRM API to load these resources and DynamicXaml aims to avoid using or hooking any private APIs by default, this limitation however does NOT exist in WinUI 3 since it uses a public API (the WinRT MRT Core API) for loading these resources and DynamicXaml hooks that API and makes the required redirections for it to work.

However the library includes an opt-in feature to automatically handle and redirect these URIs at runtime without manual edits, this feature can be enabled by setting the DynamicLoader.EnableUnsafeHooks property to true before loading any PRI files.

Warning

Enabling this feature (EnableUnsafeHooks) will cause the library to use and hook private APIs, and despite these private APIs never changed before, it's still not 100% guaranteed that this will stay the same in the future, which may lead to unexpected behavior or crashes if the private APIs change in future Windows versions, use it at your own risk.

On application side

To use DynamicXaml in your application, you need to add a reference to the library and then after initializing XAML, call DynamicLoader.LoadPri with a PRI file StorageFile, on WinUI 3 there's also an overload that accepts a path to the PRI file and a Try variant of the LoadPri function.

Example usage in C#:

using DynamicXaml;

...

// Load the PRI file
StorageFile priFile = await ApplicationData.Current.LocalFolder.GetFileAsync("My.Dynamic.Lib.pri");
DynamicLoader.LoadPri(priFile);

Then you can load the library dll and use the XAML resources defined in it, this is done via either NativeLibrary.Load (C#) or LoadLibrary (Native) in case of native libraries, or Assembly.Load in case of .NET libraries, in case of native libraries you can activate the XAML classes defined in it by calling the DllGetActivationFactory function exported by the dll.

There are sample projects in the repository that demonstrate how to use DynamicXaml for both Native and .NET libraries in both UWP and WinUI 3 applications.

XamlReader Support

To use DynamicXaml with XamlReader you have to register XAML metadata providers for the loaded libraries by calling DynamicLoader.RegisterXamlMetadataProvider with an instance of IXamlMetadataProvider for for each metadata provider in the loaded libraries.

DynamicXaml providers a helper method XamlMetadataProviderHelper.GetProviderTypeNamesFromAssemblyAsync to list all available metadata provider type names in a library given its assembly dll in case of managed libraries or its WinMD file in case of native libraries.

Example usage in C#:

using DynamicXaml;

...

// Managed Library

StorageFile dllAssembly = ...;
var typeNames = await XamlMetadataProviderHelper.GetProviderTypeNamesFromAssemblyAsync(dllAssembly);

if (typeNames.Count > 0)
{
    Assembly assembly = Assembly.LoadFrom(dllAssembly.Path);

    foreach (var typeName in typeNames)
    {
        var providerType = assembly.GetType(typeName, true, false);
        var providerInstance = (IXamlMetadataProvider)Activator.CreateInstance(providerType);
        var token = DynamicLoader.RegisterXamlMetadataProvider(providerInstance);
        // Store the token if you want to unregister the provider later
    }
})


...


// Native Library

StorageFile winmdFile = ...;
var typeNames = await XamlMetadataProviderHelper.GetProviderTypeNamesFromAssemblyAsync(winmdFile);

if (typeNames.Count > 0 &&
    NativeLibrary.TryLoad(nativeDllPath, out nint handle) &&
    NativeLibrary.TryGetExport(handle, "DllGetActivationFactory", out pDllGetActivationFactory))
{
    var DllGetActivationFactory = (delegate* unmanaged[Stdcall]<void*, void**, int>)pDllGetActivationFactory;

    foreach (var typeName in typeNames)
    {
        void* factoryPtr = default;
        using var typeNameString = new DisposableMarshalString(typeName);
        if (DllGetActivationFactory(typeNameString.Abi, &factoryPtr) >= 0)
        {
            var factory = MarshalInspectable<IActivationFactory>.FromAbi((nint)factoryPtr);
            Marshal.Release((nint)factoryPtr);

            var providerTypePtr = factory.ActivateInstance();
            Marshal.ThrowExceptionForHR(Marshal.QueryInterface(providerTypePtr, typeof(IXamlMetadataProvider).GUID, out nint providerPtr));
            Marshal.Release(providerTypePtr);

            var providerInstance = MarshalInspectable<IXamlMetadataProvider>.FromAbi(providerPtr);
            Marshal.Release(providerPtr);

            var token = DynamicLoader.RegisterXamlMetadataProvider(providerInstance);
            // Store the token if you want to unregister the provider later
        }
    }
})

...

unsafe partial class DisposableMarshalString(string str) : IDisposable
{
    private MarshalString _str = MarshalString.CreateMarshaler(str);

    public void* Abi => (void*)_str.GetAbi();

    public void Dispose()
    {
        _str.Dispose();
        _str = null;
    }
}

Additional considerations for Legacy .NET UWP applications under Debug mode

Important

These considerations are only applicable to Legacy .NET Native UWP applications and only under Debug mode, they don't apply to Release mode nor modern .NET UWP applications.

Legacy .NET Native UWP applications rely on Reflection for XAML metadata by default under Debug mode (but not under Release mode), which results in it ignoring XAML metadata providers registered via DynamicLoader.RegisterXamlMetadataProvider, to fix this add the following property to your application's .csproj project file:

<EnableTypeInfoReflection>false</EnableTypeInfoReflection>

About

Dynamically loading XAML libraries in UWP and WinUI

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

  •  

Packages

No packages published