Skip to content

Commit e417ab5

Browse files
Fix NativeAOT WinGet and startup COM paths (#5087)
1 parent 2987b12 commit e417ab5

8 files changed

Lines changed: 96 additions & 44 deletions

File tree

src/UniGetUI.Avalonia/Infrastructure/AppShortcutAumidStamper.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System;
33
using System.Diagnostics.CodeAnalysis;
44
using System.IO;
5+
using System.Runtime.CompilerServices;
56
using System.Runtime.InteropServices;
67
using UniGetUI.Core.Logging;
78

@@ -31,6 +32,12 @@ public static void EnsureStamped()
3132
if (!OperatingSystem.IsWindows())
3233
return;
3334

35+
if (!RuntimeFeature.IsDynamicCodeSupported)
36+
{
37+
Logger.Info("Skipping Start Menu shortcut AUMID stamping because NativeAOT does not support legacy COM activation.");
38+
return;
39+
}
40+
3441
try
3542
{
3643
string? shortcutPath = ResolveStartMenuShortcut();

src/UniGetUI.Avalonia/Infrastructure/WindowsAvaloniaRenderingPolicy.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Diagnostics;
22
using System.Diagnostics.CodeAnalysis;
3+
using System.Runtime.CompilerServices;
34
using System.Runtime.InteropServices;
45
using System.Runtime.Versioning;
56
using Avalonia.Controls;
@@ -63,6 +64,14 @@ private static bool HasHardwareGpu
6364
Justification = "The DXGI COM interfaces are declared in full and referenced directly here, so their members are preserved by trimming.")]
6465
private static bool DetectHardwareGpu()
6566
{
67+
if (!RuntimeFeature.IsDynamicCodeSupported)
68+
{
69+
Logger.Info(
70+
"Skipping DXGI hardware GPU detection because NativeAOT does not support legacy COM activation."
71+
);
72+
return true;
73+
}
74+
6675
try
6776
{
6877
Guid factoryIid = typeof(IDXGIFactory1).GUID;

src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativePackageHandler.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ public static void AddPackage(IPackage package, CatalogPackage catalogPackage)
6969
if (catalogPackage is null)
7070
return null;
7171

72-
var availableVersions = catalogPackage.AvailableVersions?.ToArray() ?? [];
72+
var availableVersions =
73+
catalogPackage.AvailableVersions is { } versions
74+
? NativeWinGetCollection.Copy(versions)
75+
: [];
7376
if (availableVersions.Length > 0)
7477
{
7578
metadata = catalogPackage
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace UniGetUI.PackageEngine.Managers.WingetManager;
2+
3+
internal static class NativeWinGetCollection
4+
{
5+
internal static T[] Copy<T>(IReadOnlyList<T> collection)
6+
{
7+
ArgumentNullException.ThrowIfNull(collection);
8+
9+
T[] copy = new T[collection.Count];
10+
for (int index = 0; index < copy.Length; index++)
11+
{
12+
copy[index] = collection[index];
13+
}
14+
15+
return copy;
16+
}
17+
}

src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativeWinGetHelper.cs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ public IReadOnlyList<Package> FindPackages_UnSafe(string query)
195195

196196
// Spawn Tasks to find packages on catalogs
197197
logger.Log("Spawning catalog fetching tasks...");
198-
foreach (PackageCatalogReference CatalogReference in AvailableCatalogs.ToArray())
198+
foreach (PackageCatalogReference CatalogReference in NativeWinGetCollection.Copy(AvailableCatalogs))
199199
{
200200
logger.Log($"Begin search on catalog {CatalogReference.Info.Name}");
201201
// Connect to catalog
@@ -266,7 +266,9 @@ var filter_type in new[]
266266
);
267267

268268
FindPackagesResult FoundPackages = CatalogTaskPair.Value.Result;
269-
foreach (MatchResult matchResult in FoundPackages.Matches.ToArray())
269+
foreach (
270+
MatchResult matchResult in NativeWinGetCollection.Copy(FoundPackages.Matches)
271+
)
270272
{
271273
CatalogPackage nativePackage = matchResult.CatalogPackage;
272274
// Create the Package item and add it to the list
@@ -396,7 +398,10 @@ public IReadOnlyList<Package> GetInstalledPackages_UnSafe()
396398
try
397399
{
398400
IManagerSource source;
399-
var availableVersions = nativePackage.AvailableVersions?.ToArray() ?? [];
401+
var availableVersions =
402+
nativePackage.AvailableVersions is { } versions
403+
? NativeWinGetCollection.Copy(versions)
404+
: [];
400405
if (availableVersions.Length > 0)
401406
{
402407
var installPackage = nativePackage.GetPackageVersionInfo(availableVersions[0]);
@@ -582,7 +587,7 @@ private IReadOnlyList<CatalogPackage> GetLocalWinGetPackages()
582587
}
583588

584589
List<CatalogPackage> foundPackages = [];
585-
foreach (var match in TaskResult.Matches.ToArray())
590+
foreach (var match in NativeWinGetCollection.Copy(TaskResult.Matches))
586591
{
587592
foundPackages.Add(match.CatalogPackage);
588593
}
@@ -601,7 +606,7 @@ INativeTaskLogger logger
601606
)
602607
{
603608
return SelectReachableCatalogs(
604-
WinGetManager.GetPackageCatalogs().ToArray(),
609+
NativeWinGetCollection.Copy(WinGetManager.GetPackageCatalogs()),
605610
static catalogRef => catalogRef.Info.Name,
606611
catalogRef => TryConnectLocalCatalog(catalogRef, logger),
607612
catalogName =>
@@ -688,7 +693,11 @@ public IReadOnlyList<IManagerSource> GetSources_UnSafe()
688693
List<ManagerSource> sources = [];
689694
INativeTaskLogger logger = Manager.TaskLogger.CreateNew(LoggableTaskType.ListSources);
690695

691-
foreach (PackageCatalogReference catalog in WinGetManager.GetPackageCatalogs().ToList())
696+
foreach (
697+
PackageCatalogReference catalog in NativeWinGetCollection.Copy(
698+
WinGetManager.GetPackageCatalogs()
699+
)
700+
)
692701
{
693702
try
694703
{
@@ -728,7 +737,12 @@ public IReadOnlyList<string> GetInstallableVersions_Unsafe(IPackage package)
728737
if (nativePackage is null)
729738
return [];
730739

731-
string[] versions = nativePackage.AvailableVersions.Select(x => x.Version).ToArray();
740+
var nativeVersions = NativeWinGetCollection.Copy(nativePackage.AvailableVersions);
741+
string[] versions = new string[nativeVersions.Length];
742+
for (int index = 0; index < nativeVersions.Length; index++)
743+
{
744+
versions[index] = nativeVersions[index].Version;
745+
}
732746
foreach (string? version in versions)
733747
{
734748
logger.Log(version);
@@ -800,7 +814,7 @@ public void GetPackageDetails_UnSafe(IPackageDetails details)
800814
details.ReleaseNotesUrl = new Uri(NativeDetails.ReleaseNotesUrl);
801815

802816
if (NativeDetails.Tags is not null)
803-
details.Tags = NativeDetails.Tags.ToArray();
817+
details.Tags = NativeWinGetCollection.Copy(NativeDetails.Tags);
804818

805819
bool metadataLoaded = _pingetPackageDetailsProvider.LoadPackageDetails(details, logger);
806820

src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/WinGetIconsHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ internal static class WinGetIconsHelper
126126
return null;
127127

128128
// Get the actual icon and return it
129-
foreach (Icon? icon in NativeDetails.Icons.ToArray())
129+
foreach (Icon? icon in NativeWinGetCollection.Copy(NativeDetails.Icons))
130130
if (icon is not null && icon.Url is not null)
131131
// Logger.Debug($"Found WinGet native icon for {package.Id} with URL={icon.Url}");
132132
return new CacheableIcon(new Uri(icon.Url), icon.Sha256);

src/UniGetUI.PackageEngine.Tests/WinGetManagerTests.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,18 @@ public void NativeWinGetHelperPrefersSystemComBeforeBundledActivation()
145145
);
146146
}
147147

148+
[Fact]
149+
public void NativeWinGetCollectionCopiesWithoutEnumeratingWinRtCollections()
150+
{
151+
IReadOnlyList<string> nativeCollection = new EnumeratorThrowingReadOnlyList<string>(
152+
["winget", "msstore"]
153+
);
154+
155+
var collectionCopy = NativeWinGetCollection.Copy(nativeCollection);
156+
157+
Assert.Equal(["winget", "msstore"], collectionCopy);
158+
}
159+
148160
[Fact]
149161
public void GetBundledPingetExecutablePathPrefersRootExecutable()
150162
{
@@ -1227,6 +1239,19 @@ public bool LoadPackageDetails(IPackageDetails details, INativeTaskLogger logger
12271239
}
12281240
}
12291241

1242+
private sealed class EnumeratorThrowingReadOnlyList<T>(IReadOnlyList<T> items) : IReadOnlyList<T>
1243+
{
1244+
public int Count => items.Count;
1245+
1246+
public T this[int index] => items[index];
1247+
1248+
IEnumerator<T> IEnumerable<T>.GetEnumerator() =>
1249+
throw new InvalidCastException("Enumeration is not available for this WinRT projection.");
1250+
1251+
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() =>
1252+
throw new InvalidCastException("Enumeration is not available for this WinRT projection.");
1253+
}
1254+
12301255
private sealed class TestNativeTaskLogger : INativeTaskLogger
12311256
{
12321257
public List<string> Lines { get; } = [];

src/WindowsPackageManager.Interop/WindowsPackageManager/WindowsPackageManagerStandardFactory.cs

Lines changed: 11 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
using System.Diagnostics.CodeAnalysis;
54
using System.Runtime.InteropServices;
65
using System.Runtime.Versioning;
76
using Windows.Win32.System.Com;
@@ -18,40 +17,8 @@ public WindowsPackageManagerStandardFactory(
1817
)
1918
: base(clsidContext, allowLowerTrustRegistration) { }
2019

21-
[UnconditionalSuppressMessage(
22-
"Trimming",
23-
"IL2072",
24-
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.")]
2520
protected override T CreateInstance<T>(Guid clsid, Guid iid)
2621
{
27-
if (!_allowLowerTrustRegistration)
28-
{
29-
Type? projectedType = Type.GetTypeFromCLSID(clsid);
30-
if (projectedType is null)
31-
{
32-
throw new WinGetComActivationException(
33-
clsid,
34-
iid,
35-
unchecked((int)0x80040154),
36-
_allowLowerTrustRegistration
37-
);
38-
}
39-
40-
object? activatedInstance = Activator.CreateInstance(projectedType);
41-
if (activatedInstance is null)
42-
{
43-
throw new WinGetComActivationException(
44-
clsid,
45-
iid,
46-
unchecked((int)0x80004003),
47-
_allowLowerTrustRegistration
48-
);
49-
}
50-
51-
IntPtr pointer = Marshal.GetIUnknownForObject(activatedInstance);
52-
return MarshalGeneric<T>.FromAbi(pointer);
53-
}
54-
5522
CLSCTX clsctx = CLSCTX.CLSCTX_LOCAL_SERVER;
5623
if (_allowLowerTrustRegistration)
5724
{
@@ -76,7 +43,17 @@ out IntPtr instance
7643
);
7744
}
7845

79-
return MarshalGeneric<T>.FromAbi(instance);
46+
try
47+
{
48+
return MarshalGeneric<T>.FromAbi(instance);
49+
}
50+
finally
51+
{
52+
if (instance != IntPtr.Zero)
53+
{
54+
Marshal.Release(instance);
55+
}
56+
}
8057
}
8158

8259
[DllImport(

0 commit comments

Comments
 (0)