Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Native AOT Compatibility! #29

Merged
merged 43 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
8c6b4e6
Drop support for NetStandard 2.0, also means NetFx isn't supported fo…
shmuelie Aug 16, 2024
8bf89d2
Update dependencies
shmuelie Aug 25, 2024
3d0d151
Initial prototype of using COM Source Generators and removing usage o…
shmuelie Aug 25, 2024
98f7bd6
Update sample projects references and make sample server AOT
shmuelie Aug 25, 2024
221d09b
Fix project dependencies
shmuelie Aug 25, 2024
ceabee5
Fix for CsWinRT
shmuelie Aug 26, 2024
3fa3851
Fix build
shmuelie Aug 27, 2024
3ed9ff4
Need to use powershell escape
shmuelie Aug 27, 2024
3d82d38
Standardize args
shmuelie Aug 27, 2024
4d107a5
Specify RIDs
shmuelie Aug 27, 2024
14c7122
Cast to RIID, not to factory CLSID
shmuelie Aug 27, 2024
b30dd0e
Add support for different ComWrapper implementations, and helpers for…
shmuelie Aug 27, 2024
3ef785a
Use instance field, not parameter
shmuelie Aug 27, 2024
ce0b791
Increment vesion to 2.0.0
shmuelie Aug 27, 2024
6b9db19
Remove NetFX server
shmuelie Aug 27, 2024
6954ad3
Remove redundant attributes
shmuelie Aug 29, 2024
eefac81
Fixup projects
shmuelie Aug 29, 2024
ae2889d
Code fixes
shmuelie Aug 29, 2024
02ed788
Whitespace
shmuelie Aug 29, 2024
a026566
More attribute cleaning
shmuelie Aug 29, 2024
d283acc
Simplify namespaces
shmuelie Aug 29, 2024
52a09d4
Sorting
shmuelie Aug 29, 2024
330ff90
Update release notes
shmuelie Aug 29, 2024
a7178d5
Release unknown after QI
shmuelie Aug 29, 2024
19874ec
Change to use ComPtr<T>
shmuelie Aug 29, 2024
e9a2463
Moved internal Windows stuff under the internal namespace
shmuelie Aug 29, 2024
037212b
Fix type namespaces
shmuelie Aug 29, 2024
9669f42
Update WinRtServer to support other ComWrappers
shmuelie Aug 29, 2024
6354f06
Revert using ComPtr<T>
shmuelie Aug 29, 2024
43c86af
Fix whitespace
shmuelie Aug 29, 2024
0b5c8bd
Use COM wrappers type over CsWin32 type
shmuelie Aug 29, 2024
328d505
Update XML docs and add additional argument checks
shmuelie Aug 30, 2024
860e622
Improved XML docs
shmuelie Aug 30, 2024
8144e8d
Use ArgumentNullException.ThrowIfNull
shmuelie Aug 31, 2024
c60e318
Fix docs
shmuelie Aug 31, 2024
a4bf46a
Add missing argument checks
shmuelie Aug 31, 2024
379cdda
Cleanup usings
shmuelie Aug 31, 2024
9972eb2
Supress or respect analysis
shmuelie Aug 31, 2024
71d86a2
Remove supressions
shmuelie Aug 31, 2024
7fcb993
Use ObjectDisposedException.ThrowIf
shmuelie Aug 31, 2024
2fa7bfb
Suppress CA1708: Identifiers should differ by more than case
shmuelie Aug 31, 2024
15c951d
Enable analyzers in Library
shmuelie Aug 31, 2024
6848a5b
Simplify QI
shmuelie Sep 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,5 @@
# IDE0046: Convert to conditional expression
dotnet_style_prefer_conditional_expression_over_return = false

# CA1510: Use ArgumentNullException throw helper
dotnet_diagnostic.CA1510.severity = none

# CA1513: Use ObjectDisposedException throw helper
dotnet_diagnostic.CA1513.severity = none
# CA1708: Identifiers should differ by more than case
dotnet_diagnostic.CA1708.severity = none
6 changes: 3 additions & 3 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: NuGet Restore
run: nuget restore
- name: Build
run: msbuild -t:restore,build /p:Configuration=${{matrix.configuration}} /p:Platform=x64 /bl
run: msbuild /t:restore`;build /p:Configuration=${{matrix.configuration}} /p:Platform=x64 /bl
- name: Upload MSBuild binary log
uses: actions/upload-artifact@v3
with:
Expand All @@ -47,9 +47,9 @@ jobs:
- name: NuGet Restore
run: nuget restore
- name: Build
run: msbuild .\src\Shmuelie.WinRTServer\Shmuelie.WinRTServer.csproj -t:restore,build -p:Configuration=Release
run: msbuild .\src\Shmuelie.WinRTServer\Shmuelie.WinRTServer.csproj /t:restore`;build /p:Configuration=Release
- name: Pack
run: msbuild .\src\Shmuelie.WinRTServer\Shmuelie.WinRTServer.csproj -t:pack -p:Configuration=Release
run: msbuild .\src\Shmuelie.WinRTServer\Shmuelie.WinRTServer.csproj /t:pack /p:Configuration=Release
- name: Upload package artifacts
uses: actions/upload-artifact@v3
with:
Expand Down
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
As such, this needs to be changed before a new release as well.
-->
<PropertyGroup>
<ShmuelieWinRTServerPackageVersion>1.2.0</ShmuelieWinRTServerPackageVersion>
<ShmuelieWinRTServerPackageVersion>2.0.0</ShmuelieWinRTServerPackageVersion>
<IsCommitOnReleaseBranch>false</IsCommitOnReleaseBranch>
</PropertyGroup>

Expand Down Expand Up @@ -70,7 +70,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="DotNet.ReproducibleBuilds" Version="1.1.1" PrivateAssets="all" />
<PackageReference Include="DotNet.ReproducibleBuilds" Version="1.2.4" PrivateAssets="all" />
</ItemGroup>

<!-- Needed for deterministic builds -->
Expand Down
331 changes: 166 additions & 165 deletions Shmuelie.WinRTServer.sln

Large diffs are not rendered by default.

69 changes: 36 additions & 33 deletions src/Shmuelie.WinRTServer/ComServer.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using System.Timers;
using Shmuelie.Interop.Windows;
using Shmuelie.WinRTServer.Internal;
using Shmuelie.WinRTServer.Internal.Windows;
using Windows.Win32.Foundation;
using Windows.Win32.System.Com;
using static Windows.Win32.PInvoke;
Expand All @@ -13,6 +16,20 @@ namespace Shmuelie.WinRTServer;
/// <summary>
/// An Out of Process COM Server.
/// </summary>
/// <remarks>
/// <para>Allows for types to be created using COM activation instead of WinRT activation like <see cref="WinRtServer"/>.</para>
/// <para>Typical usage is to call from an <see langword="await"/> <see langword="using"/> block, using <see cref="WaitForFirstObjectAsync"/> to not close until it is safe to do so.</para>
/// <code language="cs">
/// <![CDATA[
/// await using (ComServer server = new ComServer())
/// {
/// server.RegisterClass<RemoteThing, IRemoteThing>();
/// server.Start();
/// await server.WaitForFirstObjectAsync();
/// }
/// ]]>
/// </code>
/// </remarks>
/// <see cref="IAsyncDisposable"/>
/// <threadsafety static="true" instance="false"/>
[SupportedOSPlatform("windows6.0.6000")]
Expand All @@ -33,6 +50,8 @@ public sealed class ComServer : IAsyncDisposable
/// </summary>
private readonly Timer lifetimeCheckTimer;

private readonly StrategyBasedComWrappers comWrappers = new();

/// <summary>
/// Tracks the creation of the first instance after server is started.
/// </summary>
Expand Down Expand Up @@ -97,22 +116,18 @@ private void LifetimeCheckTimer_Elapsed(object? sender, ElapsedEventArgs e)
/// Register a class factory with the server.
/// </summary>
/// <param name="factory">The class factory to register.</param>
/// <param name="comWrappers">The implementation of <see cref="ComWrappers"/> to use for wrapping.</param>
/// <returns><see langword="true"/> if <paramref name="factory"/> was registered; otherwise, <see langword="false"/>.</returns>
/// <remarks>Only one factory can be registered for a CLSID.</remarks>
/// <exception cref="ObjectDisposedException">The instance is disposed.</exception>
/// <exception cref="ArgumentNullException"><paramref name="factory"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="factory"/> or <paramref name="comWrappers"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">The server is running.</exception>
/// <seealso cref="UnregisterClassFactory(Guid)"/>
public unsafe bool RegisterClassFactory(BaseClassFactory factory)
public unsafe bool RegisterClassFactory(BaseClassFactory factory, ComWrappers comWrappers)
{
if (IsDisposed)
{
throw new ObjectDisposedException(nameof(ComServer));
}
if (factory is null)
{
throw new ArgumentNullException(nameof(factory));
}
ObjectDisposedException.ThrowIf(IsDisposed, this);
ArgumentNullException.ThrowIfNull(factory);
ArgumentNullException.ThrowIfNull(comWrappers);
if (lifetimeCheckTimer.Enabled)
{
throw new InvalidOperationException("Can only add class factories when server is not running.");
Expand All @@ -127,11 +142,10 @@ public unsafe bool RegisterClassFactory(BaseClassFactory factory)

factory.InstanceCreated += Factory_InstanceCreated;

using ComPtr<BaseClassFactoryProxy> proxy = default;
proxy.Attach(BaseClassFactoryProxy.Create(factory));
nint wrapper = this.comWrappers.GetOrCreateComInterfaceForObject(new BaseClassFactoryWrapper(factory, comWrappers), CreateComInterfaceFlags.None);

uint cookie;
CoRegisterClassObject(&clsid, (IUnknown*)proxy.Get(), CLSCTX.CLSCTX_LOCAL_SERVER, (REGCLS.REGCLS_MULTIPLEUSE | REGCLS.REGCLS_SUSPENDED), &cookie).ThrowOnFailure();
CoRegisterClassObject(&clsid, (IUnknown*)wrapper, CLSCTX.CLSCTX_LOCAL_SERVER, (REGCLS.REGCLS_MULTIPLEUSE | REGCLS.REGCLS_SUSPENDED), &cookie).ThrowOnFailure();

factories.Add(clsid, (factory, cookie));
return true;
Expand All @@ -144,13 +158,10 @@ public unsafe bool RegisterClassFactory(BaseClassFactory factory)
/// <returns><see langword="true"/> if the server was removed; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ObjectDisposedException">The instance is disposed.</exception>
/// <exception cref="InvalidOperationException">The server is running.</exception>
/// <seealso cref="RegisterClassFactory(BaseClassFactory)"/>
/// <seealso cref="RegisterClassFactory(BaseClassFactory, ComWrappers)"/>
public unsafe bool UnregisterClassFactory(Guid clsid)
{
if (IsDisposed)
{
throw new ObjectDisposedException(nameof(ComServer));
}
ObjectDisposedException.ThrowIf(IsDisposed, this);
if (lifetimeCheckTimer.Enabled)
{
throw new InvalidOperationException("Can only remove class factories when server is not running.");
Expand Down Expand Up @@ -188,13 +199,11 @@ private void Factory_InstanceCreated(object? sender, InstanceCreatedEventArgs e)
/// <summary>
/// Starts the server.
/// </summary>
/// <remarks>Calling <see cref="Start"/> is non-blocking.</remarks>
/// <exception cref="ObjectDisposedException">The instance is disposed.</exception>
public void Start()
{
if (IsDisposed)
{
throw new ObjectDisposedException(nameof(ComServer));
}
ObjectDisposedException.ThrowIf(IsDisposed, this);
if (lifetimeCheckTimer.Enabled)
{
return;
Expand All @@ -211,10 +220,7 @@ public void Start()
/// <exception cref="ObjectDisposedException">The instance is disposed.</exception>
public void Stop()
{
if (IsDisposed)
{
throw new ObjectDisposedException(nameof(ComServer));
}
ObjectDisposedException.ThrowIf(IsDisposed, this);
if (!lifetimeCheckTimer.Enabled)
{
return;
Expand All @@ -232,17 +238,14 @@ public void Stop()
/// <exception cref="ObjectDisposedException">The instance is disposed.</exception>
public async Task<object?> WaitForFirstObjectAsync()
{
if (IsDisposed)
{
throw new ObjectDisposedException(nameof(ComServer));
}
ObjectDisposedException.ThrowIf(IsDisposed, this);

TaskCompletionSource<object>? local = firstInstanceCreated;
if (local is null)
{
return null;
}
return await local.Task;
return await local.Task.ConfigureAwait(false);
}

/// <summary>
Expand Down Expand Up @@ -272,7 +275,7 @@ void Ended(object? sender, EventArgs e)
}

Empty += Ended;
await tcs.Task;
await tcs.Task.ConfigureAwait(false);
Empty -= Ended;
}

Expand Down
48 changes: 26 additions & 22 deletions src/Shmuelie.WinRTServer/ComServerExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

namespace Shmuelie.WinRTServer;
Expand All @@ -15,19 +16,19 @@ public static class ComServerExtensions
/// <typeparam name="T">The type to register.</typeparam>
/// <typeparam name="TInterface">The interface that <typeparamref name="T"/> implements.</typeparam>
/// <param name="server">The instance.</param>
/// <param name="comWrappers">The implementation of <see cref="ComWrappers"/> to use for wrapping.</param>
/// <returns><see langword="true"/> if type was registered; otherwise, <see langword="false"/>.</returns>
/// <remarks>Type can only be registered once.</remarks>
/// <exception cref="ObjectDisposedException">The instance is disposed.</exception>
/// <exception cref="ArgumentNullException"><paramref name="server"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="server"/> or <paramref name="comWrappers"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">The server is running.</exception>
public static bool RegisterClass<T, TInterface>(this ComServer server) where T : class, TInterface, new()
public static bool RegisterClass<T, TInterface>(this ComServer server, ComWrappers comWrappers) where T : class, TInterface, new()
{
if (server is null)
{
throw new ArgumentNullException(nameof(server));
}
ArgumentNullException.ThrowIfNull(server);
ArgumentNullException.ThrowIfNull(comWrappers);

return server.RegisterClassFactory(new GeneralClassFactory<T, TInterface>());

return server.RegisterClassFactory(new GeneralClassFactory<T, TInterface>(), comWrappers);
}

/// <summary>
Expand All @@ -37,39 +38,38 @@ public static class ComServerExtensions
/// <typeparam name="TInterface">The interface that <typeparamref name="T"/> implements.</typeparam>
/// <param name="server">The instance.</param>
/// <param name="factory">Method to create instance of <typeparamref name="T"/>.</param>
/// <param name="comWrappers">The implementation of <see cref="ComWrappers"/> to use for wrapping.</param>
/// <returns><see langword="true"/> if type was registered; otherwise, <see langword="false"/>.</returns>
/// <remarks>Type can only be registered once.</remarks>
/// <exception cref="ObjectDisposedException">The instance is disposed.</exception>
/// <exception cref="ArgumentNullException"><paramref name="server"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="server"/>, <paramref name="factory"/>, or <paramref name="comWrappers"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">The server is running.</exception>
public static bool RegisterClass<T, TInterface>(this ComServer server, Func<T> factory) where T : class, TInterface
public static bool RegisterClass<T, TInterface>(this ComServer server, Func<T> factory, ComWrappers comWrappers) where T : class, TInterface
{
if (server is null)
{
throw new ArgumentNullException(nameof(server));
}
ArgumentNullException.ThrowIfNull(server);
ArgumentNullException.ThrowIfNull(factory);
ArgumentNullException.ThrowIfNull(comWrappers);

return server.RegisterClassFactory(new DelegateClassFactory<T, TInterface>(factory));
return server.RegisterClassFactory(new DelegateClassFactory<T, TInterface>(factory), comWrappers);
}

/// <summary>
/// Register a class factory with the server.
/// </summary>
/// <typeparam name="T">The type of the factory to register.</typeparam>
/// /// <param name="server">The instance.</param>
/// <param name="server">The instance.</param>
/// <param name="comWrappers">The implementation of <see cref="ComWrappers"/> to use for wrapping.</param>
/// <returns><see langword="true"/> if an instance of <typeparamref name="T"/> was registered; otherwise, <see langword="false"/>.</returns>
/// <remarks>Only one factory can be registered for a CLSID.</remarks>
/// <exception cref="ObjectDisposedException">The instance is disposed.</exception>
/// <exception cref="ArgumentNullException"><paramref name="server"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="server"/> or <paramref name="comWrappers"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">The server is running.</exception>
public static bool RegisterClassFactory<T>(this ComServer server) where T : BaseClassFactory, new()
public static bool RegisterClassFactory<T>(this ComServer server, ComWrappers comWrappers) where T : BaseClassFactory, new()
{
if (server is null)
{
throw new ArgumentNullException(nameof(server));
}
ArgumentNullException.ThrowIfNull(server);
ArgumentNullException.ThrowIfNull(comWrappers);

return server.RegisterClassFactory(new T());
return server.RegisterClassFactory(new T(), comWrappers);
}

/// <summary>
Expand All @@ -80,8 +80,12 @@ public static bool RegisterClass<T, TInterface>(this ComServer server, Func<T> f
/// <returns><see langword="true"/> if the server was removed; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ObjectDisposedException">The instance is disposed.</exception>
/// <exception cref="InvalidOperationException">The server is running.</exception>
/// <exception cref="ArgumentNullException"><paramref name="server"/> or <paramref name="factory"/> is <see langword="null"/>.</exception>
public static bool UnregisterClassFactory(this ComServer server, BaseClassFactory factory)
{
ArgumentNullException.ThrowIfNull(server);
ArgumentNullException.ThrowIfNull(factory);

return server.UnregisterClassFactory(factory.Clsid);
}
}
86 changes: 86 additions & 0 deletions src/Shmuelie.WinRTServer/CsWinRT/ComServerExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System;
using System.Runtime.Versioning;
using WinRT;

namespace Shmuelie.WinRTServer.CsWinRT;

/// <summary>
/// Extensions for <see cref="ComServer"/> when using <see cref="DefaultComWrappers"/>.
/// </summary>
[SupportedOSPlatform("windows6.0.6000")]
public static class ComServerExtensions
{
private static readonly DefaultComWrappers comWrappers = new();

/// <summary>
/// Register a type with the server.
/// </summary>
/// <typeparam name="T">The type to register.</typeparam>
/// <typeparam name="TInterface">The interface that <typeparamref name="T"/> implements.</typeparam>
/// <param name="server">The instance.</param>
/// <returns><see langword="true"/> if type was registered; otherwise, <see langword="false"/>.</returns>
/// <remarks>Type can only be registered once.</remarks>
/// <exception cref="ObjectDisposedException">The instance is disposed.</exception>
/// <exception cref="ArgumentNullException"><paramref name="server"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">The server is running.</exception>
public static bool RegisterClass<T, TInterface>(this ComServer server) where T : class, TInterface, new()
{
ArgumentNullException.ThrowIfNull(server);

return server.RegisterClassFactory(new GeneralClassFactory<T, TInterface>(), comWrappers);
}

/// <summary>
/// Register a type with the server.
/// </summary>
/// <typeparam name="T">The type to register.</typeparam>
/// <typeparam name="TInterface">The interface that <typeparamref name="T"/> implements.</typeparam>
/// <param name="server">The instance.</param>
/// <param name="factory">Method to create instance of <typeparamref name="T"/>.</param>
/// <returns><see langword="true"/> if type was registered; otherwise, <see langword="false"/>.</returns>
/// <remarks>Type can only be registered once.</remarks>
/// <exception cref="ObjectDisposedException">The instance is disposed.</exception>
/// <exception cref="ArgumentNullException"><paramref name="server"/> or <paramref name="factory"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">The server is running.</exception>
public static bool RegisterClass<T, TInterface>(this ComServer server, Func<T> factory) where T : class, TInterface
{
ArgumentNullException.ThrowIfNull(server);
ArgumentNullException.ThrowIfNull(factory);

return server.RegisterClassFactory(new DelegateClassFactory<T, TInterface>(factory), comWrappers);
}

/// <summary>
/// Register a class factory with the server.
/// </summary>
/// <typeparam name="T">The type of the factory to register.</typeparam>
/// <param name="server">The instance.</param>
/// <returns><see langword="true"/> if an instance of <typeparamref name="T"/> was registered; otherwise, <see langword="false"/>.</returns>
/// <remarks>Only one factory can be registered for a CLSID.</remarks>
/// <exception cref="ObjectDisposedException">The instance is disposed.</exception>
/// <exception cref="ArgumentNullException"><paramref name="server"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">The server is running.</exception>
public static bool RegisterClassFactory<T>(this ComServer server) where T : BaseClassFactory, new()
{
ArgumentNullException.ThrowIfNull(server);

return server.RegisterClassFactory(new T(), comWrappers);
}

/// <summary>
/// Unregister a class factory.
/// </summary>
/// <param name="server">The instance.</param>
/// <param name="factory">The class factory to unregister.</param>
/// <returns><see langword="true"/> if the server was removed; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ObjectDisposedException">The instance is disposed.</exception>
/// <exception cref="InvalidOperationException">The server is running.</exception>
/// <exception cref="ArgumentNullException"><paramref name="server"/> or <paramref name="factory"/> is <see langword="null"/>.</exception>
public static bool UnregisterClassFactory(this ComServer server, BaseClassFactory factory)
{
ArgumentNullException.ThrowIfNull(server);
ArgumentNullException.ThrowIfNull(factory);

return server.UnregisterClassFactory(factory.Clsid);
}
}
Loading
Loading