diff --git a/src/UniGetUI.Avalonia/Infrastructure/AppShortcutAumidStamper.cs b/src/UniGetUI.Avalonia/Infrastructure/AppShortcutAumidStamper.cs index faecf7cabe..2c9ef2f5fb 100644 --- a/src/UniGetUI.Avalonia/Infrastructure/AppShortcutAumidStamper.cs +++ b/src/UniGetUI.Avalonia/Infrastructure/AppShortcutAumidStamper.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using UniGetUI.Core.Logging; @@ -31,6 +32,12 @@ public static void EnsureStamped() if (!OperatingSystem.IsWindows()) return; + if (!RuntimeFeature.IsDynamicCodeSupported) + { + Logger.Info("Skipping Start Menu shortcut AUMID stamping because NativeAOT does not support legacy COM activation."); + return; + } + try { string? shortcutPath = ResolveStartMenuShortcut(); diff --git a/src/UniGetUI.Avalonia/Infrastructure/WindowsAvaloniaRenderingPolicy.cs b/src/UniGetUI.Avalonia/Infrastructure/WindowsAvaloniaRenderingPolicy.cs index f29d3c8101..948a097231 100644 --- a/src/UniGetUI.Avalonia/Infrastructure/WindowsAvaloniaRenderingPolicy.cs +++ b/src/UniGetUI.Avalonia/Infrastructure/WindowsAvaloniaRenderingPolicy.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using Avalonia.Controls; @@ -63,6 +64,14 @@ private static bool HasHardwareGpu Justification = "The DXGI COM interfaces are declared in full and referenced directly here, so their members are preserved by trimming.")] private static bool DetectHardwareGpu() { + if (!RuntimeFeature.IsDynamicCodeSupported) + { + Logger.Info( + "Skipping DXGI hardware GPU detection because NativeAOT does not support legacy COM activation." + ); + return true; + } + try { Guid factoryIid = typeof(IDXGIFactory1).GUID; diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativePackageHandler.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativePackageHandler.cs index cc04985058..929e1f9be1 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativePackageHandler.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativePackageHandler.cs @@ -69,7 +69,10 @@ public static void AddPackage(IPackage package, CatalogPackage catalogPackage) if (catalogPackage is null) return null; - var availableVersions = catalogPackage.AvailableVersions?.ToArray() ?? []; + var availableVersions = + catalogPackage.AvailableVersions is { } versions + ? NativeWinGetCollection.Copy(versions) + : []; if (availableVersions.Length > 0) { metadata = catalogPackage diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativeWinGetCollection.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativeWinGetCollection.cs new file mode 100644 index 0000000000..5b0809a564 --- /dev/null +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativeWinGetCollection.cs @@ -0,0 +1,17 @@ +namespace UniGetUI.PackageEngine.Managers.WingetManager; + +internal static class NativeWinGetCollection +{ + internal static T[] Copy(IReadOnlyList collection) + { + ArgumentNullException.ThrowIfNull(collection); + + T[] copy = new T[collection.Count]; + for (int index = 0; index < copy.Length; index++) + { + copy[index] = collection[index]; + } + + return copy; + } +} diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativeWinGetHelper.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativeWinGetHelper.cs index c223e4ebb5..4a15715009 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativeWinGetHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativeWinGetHelper.cs @@ -195,7 +195,7 @@ public IReadOnlyList FindPackages_UnSafe(string query) // Spawn Tasks to find packages on catalogs logger.Log("Spawning catalog fetching tasks..."); - foreach (PackageCatalogReference CatalogReference in AvailableCatalogs.ToArray()) + foreach (PackageCatalogReference CatalogReference in NativeWinGetCollection.Copy(AvailableCatalogs)) { logger.Log($"Begin search on catalog {CatalogReference.Info.Name}"); // Connect to catalog @@ -266,7 +266,9 @@ var filter_type in new[] ); FindPackagesResult FoundPackages = CatalogTaskPair.Value.Result; - foreach (MatchResult matchResult in FoundPackages.Matches.ToArray()) + foreach ( + MatchResult matchResult in NativeWinGetCollection.Copy(FoundPackages.Matches) + ) { CatalogPackage nativePackage = matchResult.CatalogPackage; // Create the Package item and add it to the list @@ -396,7 +398,10 @@ public IReadOnlyList GetInstalledPackages_UnSafe() try { IManagerSource source; - var availableVersions = nativePackage.AvailableVersions?.ToArray() ?? []; + var availableVersions = + nativePackage.AvailableVersions is { } versions + ? NativeWinGetCollection.Copy(versions) + : []; if (availableVersions.Length > 0) { var installPackage = nativePackage.GetPackageVersionInfo(availableVersions[0]); @@ -582,7 +587,7 @@ private IReadOnlyList GetLocalWinGetPackages() } List foundPackages = []; - foreach (var match in TaskResult.Matches.ToArray()) + foreach (var match in NativeWinGetCollection.Copy(TaskResult.Matches)) { foundPackages.Add(match.CatalogPackage); } @@ -601,7 +606,7 @@ INativeTaskLogger logger ) { return SelectReachableCatalogs( - WinGetManager.GetPackageCatalogs().ToArray(), + NativeWinGetCollection.Copy(WinGetManager.GetPackageCatalogs()), static catalogRef => catalogRef.Info.Name, catalogRef => TryConnectLocalCatalog(catalogRef, logger), catalogName => @@ -688,7 +693,11 @@ public IReadOnlyList GetSources_UnSafe() List sources = []; INativeTaskLogger logger = Manager.TaskLogger.CreateNew(LoggableTaskType.ListSources); - foreach (PackageCatalogReference catalog in WinGetManager.GetPackageCatalogs().ToList()) + foreach ( + PackageCatalogReference catalog in NativeWinGetCollection.Copy( + WinGetManager.GetPackageCatalogs() + ) + ) { try { @@ -728,7 +737,12 @@ public IReadOnlyList GetInstallableVersions_Unsafe(IPackage package) if (nativePackage is null) return []; - string[] versions = nativePackage.AvailableVersions.Select(x => x.Version).ToArray(); + var nativeVersions = NativeWinGetCollection.Copy(nativePackage.AvailableVersions); + string[] versions = new string[nativeVersions.Length]; + for (int index = 0; index < nativeVersions.Length; index++) + { + versions[index] = nativeVersions[index].Version; + } foreach (string? version in versions) { logger.Log(version); @@ -800,7 +814,7 @@ public void GetPackageDetails_UnSafe(IPackageDetails details) details.ReleaseNotesUrl = new Uri(NativeDetails.ReleaseNotesUrl); if (NativeDetails.Tags is not null) - details.Tags = NativeDetails.Tags.ToArray(); + details.Tags = NativeWinGetCollection.Copy(NativeDetails.Tags); bool metadataLoaded = _pingetPackageDetailsProvider.LoadPackageDetails(details, logger); diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/WinGetIconsHelper.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/WinGetIconsHelper.cs index d6769b6960..30d39c2d68 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/WinGetIconsHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/WinGetIconsHelper.cs @@ -126,7 +126,7 @@ internal static class WinGetIconsHelper return null; // Get the actual icon and return it - foreach (Icon? icon in NativeDetails.Icons.ToArray()) + foreach (Icon? icon in NativeWinGetCollection.Copy(NativeDetails.Icons)) if (icon is not null && icon.Url is not null) // Logger.Debug($"Found WinGet native icon for {package.Id} with URL={icon.Url}"); return new CacheableIcon(new Uri(icon.Url), icon.Sha256); diff --git a/src/UniGetUI.PackageEngine.Tests/WinGetManagerTests.cs b/src/UniGetUI.PackageEngine.Tests/WinGetManagerTests.cs index 4862a21134..2a559f9f49 100644 --- a/src/UniGetUI.PackageEngine.Tests/WinGetManagerTests.cs +++ b/src/UniGetUI.PackageEngine.Tests/WinGetManagerTests.cs @@ -145,6 +145,18 @@ public void NativeWinGetHelperPrefersSystemComBeforeBundledActivation() ); } + [Fact] + public void NativeWinGetCollectionCopiesWithoutEnumeratingWinRtCollections() + { + IReadOnlyList nativeCollection = new EnumeratorThrowingReadOnlyList( + ["winget", "msstore"] + ); + + var collectionCopy = NativeWinGetCollection.Copy(nativeCollection); + + Assert.Equal(["winget", "msstore"], collectionCopy); + } + [Fact] public void GetBundledPingetExecutablePathPrefersRootExecutable() { @@ -1227,6 +1239,19 @@ public bool LoadPackageDetails(IPackageDetails details, INativeTaskLogger logger } } + private sealed class EnumeratorThrowingReadOnlyList(IReadOnlyList items) : IReadOnlyList + { + public int Count => items.Count; + + public T this[int index] => items[index]; + + IEnumerator IEnumerable.GetEnumerator() => + throw new InvalidCastException("Enumeration is not available for this WinRT projection."); + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => + throw new InvalidCastException("Enumeration is not available for this WinRT projection."); + } + private sealed class TestNativeTaskLogger : INativeTaskLogger { public List Lines { get; } = []; diff --git a/src/WindowsPackageManager.Interop/WindowsPackageManager/WindowsPackageManagerStandardFactory.cs b/src/WindowsPackageManager.Interop/WindowsPackageManager/WindowsPackageManagerStandardFactory.cs index 20ac87a9de..632b59f266 100644 --- a/src/WindowsPackageManager.Interop/WindowsPackageManager/WindowsPackageManagerStandardFactory.cs +++ b/src/WindowsPackageManager.Interop/WindowsPackageManager/WindowsPackageManagerStandardFactory.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Runtime.Versioning; using Windows.Win32.System.Com; @@ -18,40 +17,8 @@ public WindowsPackageManagerStandardFactory( ) : base(clsidContext, allowLowerTrustRegistration) { } - [UnconditionalSuppressMessage( - "Trimming", - "IL2072", - Justification = "WinGet COM projected activation types come from the Windows Package Manager WinMD and registered COM server; this path does not depend on app-owned trimmed constructors.")] protected override T CreateInstance(Guid clsid, Guid iid) { - if (!_allowLowerTrustRegistration) - { - Type? projectedType = Type.GetTypeFromCLSID(clsid); - if (projectedType is null) - { - throw new WinGetComActivationException( - clsid, - iid, - unchecked((int)0x80040154), - _allowLowerTrustRegistration - ); - } - - object? activatedInstance = Activator.CreateInstance(projectedType); - if (activatedInstance is null) - { - throw new WinGetComActivationException( - clsid, - iid, - unchecked((int)0x80004003), - _allowLowerTrustRegistration - ); - } - - IntPtr pointer = Marshal.GetIUnknownForObject(activatedInstance); - return MarshalGeneric.FromAbi(pointer); - } - CLSCTX clsctx = CLSCTX.CLSCTX_LOCAL_SERVER; if (_allowLowerTrustRegistration) { @@ -76,7 +43,17 @@ out IntPtr instance ); } - return MarshalGeneric.FromAbi(instance); + try + { + return MarshalGeneric.FromAbi(instance); + } + finally + { + if (instance != IntPtr.Zero) + { + Marshal.Release(instance); + } + } } [DllImport(