diff --git a/.editorconfig b/.editorconfig
index 85b0d0be..50dbcc3a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -12,3 +12,15 @@ csharp_space_after_cast = true
[*.cs]
indent_style = space
indent_size = 4
+
+[*.csproj]
+indent_style = space
+indent_size = 4
+
+[*.props]
+indent_style = space
+indent_size = 4
+
+[*.targets]
+indent_style = space
+indent_size = 4
diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml
deleted file mode 100644
index 8630fb6a..00000000
--- a/.github/release-drafter.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-template: |
- ## Changes since $PREVIOUS_TAG:
-
- $CHANGES
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..c15b9fe7
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,36 @@
+name: Build
+
+on:
+ push:
+ branches:
+ - 'main'
+ paths-ignore:
+ - 'docs/**'
+ - '*.md'
+ pull_request:
+ paths-ignore:
+ - 'docs/**'
+ - '*.md'
+
+jobs:
+ build:
+ runs-on: windows-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup .NET SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ global-json-file: ./global.json
+
+ - name: Pack
+ run: dotnet pack SharpGenTools.sln --configuration Release -p:Packing=true
+
+ - name: Pack SharpGen.Runtime.COM
+ run: dotnet pack SharpGen.Runtime.COM/SharpGen.Runtime.COM.sln --configuration Release -p:Packing=true
+
+ - name: Publish to NuGet
+ if: github.event_name == 'push'
+ run: dotnet nuget push artifacts/**/*.nupkg -k ${{secrets.NUGET_TOKEN}} --skip-duplicate --source https://api.nuget.org/v3/index.json
diff --git a/Directory.Build.props b/Directory.Build.props
index 2e198172..0e1462fe 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,66 +1,60 @@
-
- 2.0.0
-
-
-
-
-
- $(VersionPrefix)-local
-
-
-
-
- $(ReleaseTag)
-
-
-
-
- $(VersionPrefix)-ci.$(BuildNumber)
-
-
-
-
-
- 10
- 00240000048000009400000006020000002400005253413100040000010001003dab93dc845fe6b52b20d86918a54f7300fa6959d56e9743c6f721857346811cd6a82d12132856755ab87e014127322421694fb522ad98fc3c6b65b389ab18ee3bbdec5c2ad5a8bef05599a3615c3e6afdade7eb2cf571b5ede7feb026b099fa94ee73f2f8dadcb6b1be62f7c984226eb0508d5ca6c3e394605c5cb0fa0851a2
- $(MSBuildThisFileDirectory)SharpGenTools.snk
- $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
-
-
-
- false
- true
-
-
-
- false
- false
- true
-
-
-
- jkoritzinsky
- (c) 2010-2017 Alexandre Mutel, 2017-2018 Jeremy Koritzinsky
- MIT
- https://github.com/SharpGenTools/SharpGenTools
- SharpGen;CodeGen;CPlusPlus;PInvoke;Native;COM
- https://github.com/SharpGenTools/SharpGenTools
-
-
- true
-
-
- true
-
-
- true
- snupkg
-
-
-
-
-
+
+ 2.4.2
+ beta
+ $(VersionPrefix)-$(VersionSuffix)
+
+
+
+ $(MSBuildThisFileDirectory)NuGet.config
+ true
+
+
+
+ $(MSBuildThisFileDirectory)
+
+
+
+ latest
+ 00240000048000009400000006020000002400005253413100040000010001003dab93dc845fe6b52b20d86918a54f7300fa6959d56e9743c6f721857346811cd6a82d12132856755ab87e014127322421694fb522ad98fc3c6b65b389ab18ee3bbdec5c2ad5a8bef05599a3615c3e6afdade7eb2cf571b5ede7feb026b099fa94ee73f2f8dadcb6b1be62f7c984226eb0508d5ca6c3e394605c5cb0fa0851a2
+ $(MSBuildThisFileDirectory)SharpGenTools.snk
+ $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
+
+
+
+ false
+ true
+
+
+
+ false
+ false
+ true
+
+
+
+ jkoritzinsky
+ (c) 2010-2017 Alexandre Mutel, 2017-2023 Jeremy Koritzinsky, 2023-2024 Amer Koleci
+ MIT
+ https://github.com/SharpGenTools/SharpGenTools
+ $(MSBuildThisFileDirectory)artifacts/
+ SharpGen;CodeGen;CPlusPlus;PInvoke;Native;COM
+ https://github.com/SharpGenTools/SharpGenTools
+
+
+ true
+
+
+ true
+
+
+ true
+ snupkg
+
+
+
+ $(NoWarn);NETSDK1212
+
diff --git a/Directory.Build.targets b/Directory.Build.targets
index ac0efe94..10038fbd 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -1,7 +1,8 @@
-
+
+
+
+ $(DefineConstants);SIGNED_BUILD
+
-
- $(DefineConstants);SIGNED_BUILD
-
\ No newline at end of file
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 00000000..24bfa93a
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,38 @@
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/NuGet.config b/NuGet.config
index 77d88787..4db1370a 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -3,5 +3,6 @@
+
\ No newline at end of file
diff --git a/Packages.props b/Packages.props
deleted file mode 100644
index 47d138fe..00000000
--- a/Packages.props
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
- runtime; build; native; contentfiles; analyzers
- all
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index 40f42d62..d7d5c364 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
# SharpGenTools
-[](https://dev.azure.com/SharpGenTools/SharpGenTools/_build/latest?definitionId=1&branchName=master) [](https://www.myget.org/feed/Packages/sharpgentools) [](https://www.nuget.org/packages/SharpGenTools.Sdk) [](https://sharpgentools.readthedocs.io/en/latest/) [](https://codecov.io/gh/SharpGenTools/SharpGenTools) [](https://www.codefactor.io/repository/github/sharpgentools/sharpgentools)
+[](https://github.com/SharpGenTools/SharpGenTools/actions)
+[](https://www.nuget.org/packages/SharpGenTools.Sdk) [](https://sharpgentools.readthedocs.io/en/latest/) [](https://codecov.io/gh/SharpGenTools/SharpGenTools) [](https://www.codefactor.io/repository/github/sharpgentools/sharpgentools)
Accurate and high performance C++ interop code generator for C#.
@@ -31,25 +32,13 @@ Accurate and high performance C++ interop code generator for C#.
* SDK-style (CPS) MSBuild projects
* .NET environment, at least one of the following:
- * .NET SDK (5 or newer)
- * .NET Core SDK (2.1 or newer)
- * Visual Studio 2017.3 with desktop .NET workload, .NET Framework 4.7.2 SDK or newer
+ * .NET SDK (7 or newer)
+ * .NET Core SDK (3.1 or newer)
+ * Visual Studio 2019 with desktop .NET workload, .NET Framework 4.7.2 SDK or newer
* Make any mapping files a `SharpGenMapping` item in your `.csproj`.
### To Build
-* .NET SDK: 5.0 or newer.
+* .NET SDK: 7.0 or newer.
* CMake: 3.0 or newer.
-* SDK tests require x64 Windows, VS2019 with x86 and x64 C++ compilers, recent PowerShell version.
-
-## Nightly (CI) builds
-Add SharpGenTools MyGet feed to your NuGet.config:
-
-```xml
-
-
-
-
-
-
-```
\ No newline at end of file
+* SDK tests require x64 Windows, VS2022 with x86 and x64 C++ compilers, recent PowerShell version.
\ No newline at end of file
diff --git a/SdkTests/Directory.Build.props b/SdkTests/Directory.Build.props
index e54af4a2..cd917c52 100644
--- a/SdkTests/Directory.Build.props
+++ b/SdkTests/Directory.Build.props
@@ -6,8 +6,6 @@
true
-
-
diff --git a/SdkTests/Directory.Build.targets b/SdkTests/Directory.Build.targets
index 2ff7612c..7b5a698a 100644
--- a/SdkTests/Directory.Build.targets
+++ b/SdkTests/Directory.Build.targets
@@ -13,6 +13,4 @@
/>
-
-
diff --git a/SdkTests/Interface/Mapping.xml b/SdkTests/Interface/Mapping.xml
index 15719ba0..0c37d61f 100644
--- a/SdkTests/Interface/Mapping.xml
+++ b/SdkTests/Interface/Mapping.xml
@@ -29,7 +29,7 @@
-
+
@@ -39,10 +39,10 @@
-
+
-
+
diff --git a/SdkTests/Managed.props b/SdkTests/Managed.props
index c55d73c3..5175ab23 100644
--- a/SdkTests/Managed.props
+++ b/SdkTests/Managed.props
@@ -12,12 +12,12 @@
- net5.0
+ net6.0
x86
- netcoreapp2.1;net5.0
+ netcoreapp3.1;net6.0
@@ -59,7 +59,7 @@
+ Condition="'$(TargetFramework)' != 'net472' and '$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net6.0'"/>
diff --git a/SharpGen.Generator/SharpGen.Generator.csproj b/SharpGen.Generator/SharpGen.Generator.csproj
index 01238138..c45d9cdc 100644
--- a/SharpGen.Generator/SharpGen.Generator.csproj
+++ b/SharpGen.Generator/SharpGen.Generator.csproj
@@ -1,33 +1,28 @@
-
+
-
+
+ netstandard2.0
+ true
+ latest
+ false
+ enable
+ true
+ true
+ SHARPGEN_ROSLYN
+
-
- net472;net5.0
- true
- latest
- false
- enable
- true
- true
- SHARPGEN_ROSLYN
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
- StatementSyntaxList.cs
-
-
- SyntaxListBase.cs
-
-
+
+
+ StatementSyntaxList.cs
+
+
+ SyntaxListBase.cs
+
+
\ No newline at end of file
diff --git a/SharpGen.Generator/SharpGenModuleGenerator.GenerateModule.cs b/SharpGen.Generator/SharpGenModuleGenerator.GenerateModule.cs
index dcc0bcc2..120ccc24 100644
--- a/SharpGen.Generator/SharpGenModuleGenerator.GenerateModule.cs
+++ b/SharpGen.Generator/SharpGenModuleGenerator.GenerateModule.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using System.Linq.Expressions;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
@@ -19,9 +20,10 @@ private static void GenerateModule(GeneratorExecutionContext context)
List guidJobs = new();
List vtblJobs = new();
+ List preserveInterfaceJobs = new();
void HandleGuid(ITypeSymbol symbol, Guid parsedGuid) =>
- guidJobs.Add(new GuidJob(symbol, Utilities.GetGuidParameters(parsedGuid)));
+ guidJobs.Add(new GuidJob(symbol, parsedGuid, Utilities.GetGuidParameters(parsedGuid)));
void HandleVtbl(ITypeSymbol symbol, ITypeSymbol vtblTypeSymbol)
{
@@ -51,6 +53,9 @@ bool TypePredicate(INamedTypeSymbol x) =>
if (symbol.GetVtblAttribute() is { } vtblAttribute)
HandleVtbl(symbol, vtblAttribute);
+
+ if (symbol.HasBaseClass(CallbackBaseClassName))
+ preserveInterfaceJobs.Add(new LinkerPreserveInterfaceJob(symbol));
}
if (context.CancellationToken.IsCancellationRequested)
@@ -58,16 +63,23 @@ bool TypePredicate(INamedTypeSymbol x) =>
StatementSyntaxList body = new();
- body.AddRange(
- guidJobs,
- job => ExpressionStatement(
- AssignmentExpression(
- SyntaxKind.SimpleAssignmentExpression,
- StorageField(ParseName(job.Type.ToDisplayString()), IdentifierName("Guid")),
- ObjectCreationExpression(ParseTypeName("System.Guid")).WithArgumentList(job.Guid)
+ StatementSyntax GuidTransform(GuidJob job) =>
+ context.Compilation.IsSymbolAccessibleWithin(job.Type, context.Compilation.Assembly)
+ ? ExpressionStatement(
+ AssignmentExpression(
+ SyntaxKind.SimpleAssignmentExpression,
+ StorageField(ParseName(job.Type.ToDisplayString()), IdentifierName("Guid")),
+ ObjectCreationExpression(ParseTypeName("System.Guid"), job.GuidSyntax, default)
+ )
)
- )
- );
+ : Block()
+ .WithLeadingTrivia(
+ Comment(
+ $"// Type {job.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} is inaccessible, but has GUID {job.Guid}"
+ )
+ );
+
+ body.AddRange(guidJobs, GuidTransform);
if (context.CancellationToken.IsCancellationRequested)
return;
@@ -173,6 +185,48 @@ localVtbl is not null
}
}
+ foreach (var preserveInterfaceJob in preserveInterfaceJobs)
+ {
+ var accessibility = preserveInterfaceJob.Type.DeclaredAccessibility;
+ if (Utilities.IsAnyOfFollowing(accessibility, Accessibility.Private, Accessibility.ProtectedOrInternal, Accessibility.ProtectedOrFriend, Accessibility.ProtectedAndInternal, Accessibility.NotApplicable, Accessibility.Protected))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor
+ (
+ "SG0000",
+ "Privately accessible classes that inherit from `CallbackBase` cannot be protected from Assembly Trimming.",
+ "Class {0} inheriting from `CallbackBase` should be marked with an accessibility modifier that makes it accessible from other classes (e.g. `internal`). A private accessibility modifier prevents SharpGenTools from protecting your callbacks against IL Linker/Assembly Trimmer.",
+ "SharpGenTools",
+ DiagnosticSeverity.Warning,
+ true,
+ null,
+ null, WellKnownDiagnosticTags.Build
+ ), null, preserveInterfaceJob.Type.ToDisplayString()));
+ }
+ else
+ {
+ body.Add(ExpressionStatement(
+ InvocationExpression(
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName("SharpGen"),
+ IdentifierName("Runtime")),
+ IdentifierName("Trimming")),
+ IdentifierName("TrimmingHelpers")),
+ GenericName(
+ Identifier("PreserveMe"))
+ .WithTypeArgumentList(
+ TypeArgumentList(
+ SingletonSeparatedList(
+ IdentifierName(preserveInterfaceJob.Type.ToDisplayString()))))))));
+ }
+ }
+
if (body.Count == 0)
return;
@@ -184,31 +238,37 @@ localVtbl is not null
.WithModifiers(staticModifier)
.AddModifiers(Token(SyntaxKind.UnsafeKeyword))
.WithMembers(
- SingletonList(
- MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), "Initialize")
- .WithModifiers(staticModifier)
- .AddAttributeLists(ModuleInitializerAttributeList)
- .WithBody(body.ToBlock())
+ List(
+ new MemberDeclarationSyntax[]
+ {
+ SuppressWarningsStatement,
+ MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), "Initialize")
+ .WithModifiers(staticModifier)
+ .AddAttributeLists(ModuleInitializerAttributeList)
+ .WithBody(body.ToBlock())
+ }
)
);
context.AddSource(
- "SharpGen.g.cs",
+ "SharpGen.Module.g.cs",
SourceText.From(GenerateCompilationUnit(clazz).ToString(), Encoding.UTF8)
);
}
- private sealed record GuidJob(ITypeSymbol Type, ArgumentListSyntax Guid)
+ private sealed record GuidJob(ITypeSymbol Type, Guid Guid, ArgumentListSyntax GuidSyntax)
{
public readonly ITypeSymbol Type = Type ?? throw new ArgumentNullException(nameof(Type));
- public readonly ArgumentListSyntax Guid = Guid ?? throw new ArgumentNullException(nameof(Guid));
+ public readonly ArgumentListSyntax GuidSyntax = GuidSyntax ?? throw new ArgumentNullException(nameof(GuidSyntax));
}
+ private sealed record LinkerPreserveInterfaceJob(ITypeSymbol Type);
+
private sealed class VtblJob
{
public readonly ITypeSymbol InterfaceType;
public readonly ITypeSymbol VtblType;
- public INamedTypeSymbol[] CallbackInterfaces { get; init; }
+ public INamedTypeSymbol[]? CallbackInterfaces { get; init; }
public VtblJob(ITypeSymbol interfaceType, ITypeSymbol vtblType)
{
diff --git a/SharpGen.Generator/SharpGenModuleGenerator.GenerateUtilities.cs b/SharpGen.Generator/SharpGenModuleGenerator.GenerateUtilities.cs
index d51371ff..c5afd455 100644
--- a/SharpGen.Generator/SharpGenModuleGenerator.GenerateUtilities.cs
+++ b/SharpGen.Generator/SharpGenModuleGenerator.GenerateUtilities.cs
@@ -45,43 +45,4 @@ public sealed partial class SharpGenModuleGenerator
)
)
);
-
- private static void GenerateUtilities(GeneratorExecutionContext context)
- {
- List attributes = new(1);
-
- if (context.Compilation.GetTypeByMetadataName(ModuleInitializerAttributeName) is not
- { IsReferenceType: true, IsGenericType: false })
- attributes.Add(ModuleInitializerAttribute);
-
- if (attributes.Count == 0)
- return;
-
- context.AddSource(
- "SourceGeneratorUtilities.g.cs",
- SourceText.From(GenerateCompilationUnit(attributes).ToString(), Encoding.UTF8)
- );
- }
-
- private static NamespaceDeclarationSyntax ModuleInitializerAttribute =>
- NamespaceDeclaration(
- QualifiedName(
- QualifiedName(IdentifierName("System"), IdentifierName("Runtime")),
- IdentifierName("CompilerServices")
- )
- )
- .AddMembers(
- ClassDeclaration("ModuleInitializerAttribute")
- .AddAttributeLists(
- AttributeList(SingletonSeparatedList(MethodAttributeUsage)),
- AttributeList(SingletonSeparatedList(DebugConditionalAttribute))
- )
- .AddModifiers(Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.SealedKeyword))
- .AddBaseListTypes(SimpleBaseType(Attribute))
- .AddMembers(
- ConstructorDeclaration(Identifier("ModuleInitializerAttribute"))
- .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword)))
- .WithBody(Block())
- )
- );
}
\ No newline at end of file
diff --git a/SharpGen.Generator/SharpGenModuleGenerator.cs b/SharpGen.Generator/SharpGenModuleGenerator.cs
index eec840d9..0070d33b 100644
--- a/SharpGen.Generator/SharpGenModuleGenerator.cs
+++ b/SharpGen.Generator/SharpGenModuleGenerator.cs
@@ -3,6 +3,7 @@
using System.Diagnostics;
using System.Threading;
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
@@ -12,10 +13,30 @@ namespace SharpGen.Generator;
public sealed partial class SharpGenModuleGenerator : ISourceGenerator
{
private const string CallbackableInterfaceName = "SharpGen.Runtime.ICallbackable";
+ private const string CallbackBaseClassName = "SharpGen.Runtime.CallbackBase";
private const string ModuleInitializerAttributeName = "System.Runtime.CompilerServices.ModuleInitializerAttribute";
- private static readonly AttributeListSyntax ModuleInitializerAttributeList = AttributeList(
- SingletonSeparatedList(Attribute(ParseName(ModuleInitializerAttributeName)))
+ private static readonly AttributeListSyntax[] ModuleInitializerAttributeList = new[]
+ {
+ // Module Initializer.
+ AttributeList(
+ SingletonSeparatedList(Attribute(ParseName(ModuleInitializerAttributeName)))
+ )
+ };
+
+ private static readonly GlobalStatementSyntax SuppressWarningsStatement = GlobalStatement
+ (
+ ExpressionStatement(IdentifierName(
+ Identifier(
+ TriviaList
+ (
+ Trivia(IfDirectiveTrivia(IdentifierName("NET6_0_OR_GREATER"), true, false, false)),
+ DisabledText(@"[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage(""ReflectionAnalysis"", ""IL2111"")]
+[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage(""ReflectionAnalysis"", ""IL2110"")]
+"),
+ Trivia(EndIfDirectiveTrivia(true))
+ ),
+ "", TriviaList()))).WithSemicolonToken(MissingToken(SyntaxKind.SemicolonToken))
);
private static readonly NameSyntax TypeDataStorage = ParseName("SharpGen.Runtime.TypeDataStorage");
@@ -42,8 +63,6 @@ public void Execute(GeneratorExecutionContext context)
if (context.CancellationToken.IsCancellationRequested)
return;
- GenerateUtilities(context);
-
if (context.CancellationToken.IsCancellationRequested)
return;
diff --git a/SharpGen.Generator/Utilities.cs b/SharpGen.Generator/Utilities.cs
index 47b5b843..9755dc14 100644
--- a/SharpGen.Generator/Utilities.cs
+++ b/SharpGen.Generator/Utilities.cs
@@ -150,4 +150,39 @@ static ArgumentSyntax Literal2(short value) =>
static ArgumentSyntax Literal4(int value) =>
LiteralArgument(Literal("0x" + value.ToString("X8"), value));
}
+
+ ///
+ /// Returns true if the given symbol contains a base class with a given type name.
+ ///
+ /// The class symbol.
+ /// Full name of the type, e.g. \"SharpGen.Runtime.CallbackBase\"
+ ///
+ public static bool HasBaseClass(this ITypeSymbol symbol, string fullTypeName)
+ {
+ var baseClass = symbol.BaseType;
+
+ while (baseClass != null)
+ {
+ if (baseClass.ToDisplayString() == fullTypeName)
+ return true;
+
+ baseClass = baseClass.BaseType;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Checks if an enum is any of the following given param values.
+ ///
+ public static bool IsAnyOfFollowing(T value, params T[] values) where T : Enum
+ {
+ foreach (var val in values)
+ {
+ if (value.Equals(val))
+ return true;
+ }
+
+ return false;
+ }
}
\ No newline at end of file
diff --git a/SharpGen.Platform/IncludeDirectoryResolver.cs b/SharpGen.Platform/IncludeDirectoryResolver.cs
index b07113f6..4feabcf0 100644
--- a/SharpGen.Platform/IncludeDirectoryResolver.cs
+++ b/SharpGen.Platform/IncludeDirectoryResolver.cs
@@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
using Microsoft.Win32;
using SharpGen.Config;
using SharpGen.Logging;
@@ -75,7 +76,11 @@ public IReadOnlyList- IncludePaths
// Is Using registry?
if (path.StartsWith("="))
{
+#if NET6_0_OR_GREATER
+ if (OperatingSystem.IsWindows())
+#else
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+#endif
{
var registryPath = path.Substring(1);
var indexOfSubPath = directory.Path.IndexOf(";");
@@ -119,6 +124,7 @@ public Item(string path, IncludeDirRule rule)
public IncludeDirRule Rule { get; }
}
+ [SupportedOSPlatform("windows")]
private (string path, bool success) ResolveRegistryDirectory(string registryPath)
{
string path = null;
diff --git a/SharpGen.Platform/SharpGen.Platform.csproj b/SharpGen.Platform/SharpGen.Platform.csproj
index 26844435..db9a2364 100644
--- a/SharpGen.Platform/SharpGen.Platform.csproj
+++ b/SharpGen.Platform/SharpGen.Platform.csproj
@@ -1,53 +1,53 @@
-
+
-
+
+ Library
+ netstandard2.0;net8.0
+ true
+ true
+
-
- Library
- net472;net5.0
- true
-
+
+
+
+
+
-
-
-
-
+
+
+ <_Parameter1>SharpGen.UnitTests, PublicKey=$(SharpGenPublicKey)
+
+
+ <_Parameter1>SharpGenTools.Sdk, PublicKey=$(SharpGenPublicKey)
+
+
-
-
- <_Parameter1>SharpGen.UnitTests, PublicKey=$(SharpGenPublicKey)
-
-
- <_Parameter1>SharpGenTools.Sdk, PublicKey=$(SharpGenPublicKey)
-
-
+
+
+
-
-
-
+
+
+ Code
+
+
+ Code
+
+
+ Code
+
+
+ Code
+
+
+ Code
+
+
+ Code
+
+
+ Code
+
+
-
-
- Code
-
-
- Code
-
-
- Code
-
-
- Code
-
-
- Code
-
-
- Code
-
-
- Code
-
-
-
-
+
\ No newline at end of file
diff --git a/SharpGen.Runtime.COM/NuGet.config b/SharpGen.Runtime.COM/NuGet.config
deleted file mode 100644
index 36c68fbb..00000000
--- a/SharpGen.Runtime.COM/NuGet.config
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/SharpGen.Runtime.COM/SharpGen.Runtime.COM.Trim.Dummy/Program.cs b/SharpGen.Runtime.COM/SharpGen.Runtime.COM.Trim.Dummy/Program.cs
new file mode 100644
index 00000000..2b1bd9b3
--- /dev/null
+++ b/SharpGen.Runtime.COM/SharpGen.Runtime.COM.Trim.Dummy/Program.cs
@@ -0,0 +1,6 @@
+// See https://aka.ms/new-console-template for more information
+
+// I'm a dummy project for testing trimmability, since the analyzer outside of publish time isn't fully perfect yet
+// test my trimming with `dotnet publish -r win-x64`
+
+Console.WriteLine("Hello SharpGen.Runtime.COM");
\ No newline at end of file
diff --git a/SharpGen.Runtime.COM/SharpGen.Runtime.COM.Trim.Dummy/SharpGen.Runtime.COM.Trim.Dummy.csproj b/SharpGen.Runtime.COM/SharpGen.Runtime.COM.Trim.Dummy/SharpGen.Runtime.COM.Trim.Dummy.csproj
new file mode 100644
index 00000000..4b012e62
--- /dev/null
+++ b/SharpGen.Runtime.COM/SharpGen.Runtime.COM.Trim.Dummy/SharpGen.Runtime.COM.Trim.Dummy.csproj
@@ -0,0 +1,21 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj b/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj
deleted file mode 100644
index ce070309..00000000
--- a/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
-
-
-
- net5.0;netstandard2.0;net45;netstandard1.3
- SharpGen.Runtime
- C# COM Interop classes for use with SharpGenTools generated libraries
- true
-
- true
- $(CoreCompileDependsOn);SharpGenSetRoslynGeneratedPath
-
-
-
- true
- false
-
-
-
-
-
-
-
-
-
-
- $(IntermediateOutputPath)Generated
-
-
-
-
-
- true
-
-
-
-
-
- false
-
-
-
-
-
-
-
-
diff --git a/SharpGen.Runtime.COM/SharpGen.Runtime.COM.sln b/SharpGen.Runtime.COM/SharpGen.Runtime.COM.sln
index fcf62b36..3f37b139 100644
--- a/SharpGen.Runtime.COM/SharpGen.Runtime.COM.sln
+++ b/SharpGen.Runtime.COM/SharpGen.Runtime.COM.sln
@@ -1,9 +1,11 @@
-
+
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2036
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpGen.Runtime.COM", "SharpGen.Runtime.COM.csproj", "{1E2AA0D7-49F8-4E21-966D-60899298214C}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpGen.Runtime.COM", "SharpGen.Runtime.COM\SharpGen.Runtime.COM.csproj", "{1E2AA0D7-49F8-4E21-966D-60899298214C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpGen.Runtime.COM.Trim.Dummy", "SharpGen.Runtime.COM.Trim.Dummy\SharpGen.Runtime.COM.Trim.Dummy.csproj", "{E4721BB6-1428-47B8-A6C9-A0B622D50FFB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -15,6 +17,10 @@ Global
{1E2AA0D7-49F8-4E21-966D-60899298214C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E2AA0D7-49F8-4E21-966D-60899298214C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E2AA0D7-49F8-4E21-966D-60899298214C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E4721BB6-1428-47B8-A6C9-A0B622D50FFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E4721BB6-1428-47B8-A6C9-A0B622D50FFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E4721BB6-1428-47B8-A6C9-A0B622D50FFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E4721BB6-1428-47B8-A6C9-A0B622D50FFB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/SharpGen.Runtime.COM/.gitignore b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/.gitignore
similarity index 100%
rename from SharpGen.Runtime.COM/.gitignore
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/.gitignore
diff --git a/SharpGen.Runtime.COM/ComActivationHelpers.cs b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/ComActivationHelpers.cs
similarity index 100%
rename from SharpGen.Runtime.COM/ComActivationHelpers.cs
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/ComActivationHelpers.cs
diff --git a/SharpGen.Runtime.COM/ComContext.cs b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/ComContext.cs
similarity index 100%
rename from SharpGen.Runtime.COM/ComContext.cs
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/ComContext.cs
diff --git a/SharpGen.Runtime.COM/ComUtilities.cs b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/ComUtilities.cs
similarity index 100%
rename from SharpGen.Runtime.COM/ComUtilities.cs
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/ComUtilities.cs
diff --git a/SharpGen.Runtime.COM/Mapping.xml b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Mapping.xml
similarity index 99%
rename from SharpGen.Runtime.COM/Mapping.xml
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/Mapping.xml
index ed0f1206..14329eee 100644
--- a/SharpGen.Runtime.COM/Mapping.xml
+++ b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Mapping.xml
@@ -126,7 +126,7 @@
-
+
diff --git a/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj
new file mode 100644
index 00000000..9a201955
--- /dev/null
+++ b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj
@@ -0,0 +1,57 @@
+
+
+
+
+
+ netstandard2.0;netstandard2.1;net8.0;net9.0;net462;net471
+ SharpGen.Runtime
+ C# COM Interop classes for use with SharpGenTools generated libraries
+ true
+
+ true
+ true
+ true
+ $(CoreCompileDependsOn);SharpGenSetRoslynGeneratedPath
+
+
+
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+ $(IntermediateOutputPath)Generated
+
+
+
+
+
+ true
+
+
+
+
+
+ false
+
+
+
+
+
+
+
+
diff --git a/SharpGen.Runtime.COM/Win32/ComObjectEnumerator.cs b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/ComObjectEnumerator.cs
similarity index 100%
rename from SharpGen.Runtime.COM/Win32/ComObjectEnumerator.cs
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/ComObjectEnumerator.cs
diff --git a/SharpGen.Runtime.COM/Win32/ComStreamProxy.cs b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/ComStreamProxy.cs
similarity index 100%
rename from SharpGen.Runtime.COM/Win32/ComStreamProxy.cs
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/ComStreamProxy.cs
diff --git a/SharpGen.Runtime.COM/Win32/ComStringEnumerator.cs b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/ComStringEnumerator.cs
similarity index 100%
rename from SharpGen.Runtime.COM/Win32/ComStringEnumerator.cs
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/ComStringEnumerator.cs
diff --git a/SharpGen.Runtime.COM/Win32/ErrorCodeHelper.cs b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/ErrorCodeHelper.cs
similarity index 100%
rename from SharpGen.Runtime.COM/Win32/ErrorCodeHelper.cs
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/ErrorCodeHelper.cs
diff --git a/SharpGen.Runtime.COM/Win32/ExceptionInfo.cs b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/ExceptionInfo.cs
similarity index 100%
rename from SharpGen.Runtime.COM/Win32/ExceptionInfo.cs
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/ExceptionInfo.cs
diff --git a/SharpGen.Runtime.COM/Win32/IEnumString.cs b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/IEnumString.cs
similarity index 100%
rename from SharpGen.Runtime.COM/Win32/IEnumString.cs
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/IEnumString.cs
diff --git a/SharpGen.Runtime.COM/Win32/IEnumUnknown.cs b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/IEnumUnknown.cs
similarity index 100%
rename from SharpGen.Runtime.COM/Win32/IEnumUnknown.cs
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/IEnumUnknown.cs
diff --git a/SharpGen.Runtime.COM/Win32/IPropertyBag2.cs b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/IPropertyBag2.cs
similarity index 100%
rename from SharpGen.Runtime.COM/Win32/IPropertyBag2.cs
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/IPropertyBag2.cs
diff --git a/SharpGen.Runtime.COM/Win32/IPropertyStore.cs b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/IPropertyStore.cs
similarity index 100%
rename from SharpGen.Runtime.COM/Win32/IPropertyStore.cs
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/IPropertyStore.cs
diff --git a/SharpGen.Runtime.COM/Win32/PropertyKey.cs b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/PropertyKey.cs
similarity index 100%
rename from SharpGen.Runtime.COM/Win32/PropertyKey.cs
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/PropertyKey.cs
diff --git a/SharpGen.Runtime.COM/Win32/Variant.cs b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/Variant.cs
similarity index 100%
rename from SharpGen.Runtime.COM/Win32/Variant.cs
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/Variant.cs
diff --git a/SharpGen.Runtime.COM/Win32/VariantElementType.cs b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/VariantElementType.cs
similarity index 100%
rename from SharpGen.Runtime.COM/Win32/VariantElementType.cs
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/VariantElementType.cs
diff --git a/SharpGen.Runtime.COM/Win32/VariantFullType.cs b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/VariantFullType.cs
similarity index 100%
rename from SharpGen.Runtime.COM/Win32/VariantFullType.cs
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/VariantFullType.cs
diff --git a/SharpGen.Runtime.COM/Win32/VariantType.cs b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/VariantType.cs
similarity index 100%
rename from SharpGen.Runtime.COM/Win32/VariantType.cs
rename to SharpGen.Runtime.COM/SharpGen.Runtime.COM/Win32/VariantType.cs
diff --git a/SharpGen.Runtime.Trim.Dummy.CallbackTest/CallbackHandlers.cs b/SharpGen.Runtime.Trim.Dummy.CallbackTest/CallbackHandlers.cs
new file mode 100644
index 00000000..8b567562
--- /dev/null
+++ b/SharpGen.Runtime.Trim.Dummy.CallbackTest/CallbackHandlers.cs
@@ -0,0 +1,55 @@
+namespace SharpGen.Runtime.Trim.Dummy.CallbackTest;
+
+// Hi, if you remove CallbackBase from me, I will be trimmed away!
+// Otherwise my interfaces are preserved
+public class PublicCallbackHandler : CallbackBase, IHello
+{
+ public void SayHello() => Console.WriteLine("We're no strangers to love");
+}
+
+public class PublicCallbackHandlerEx : PublicCallbackHandler, IGoodbye
+{
+ public void SayGoodbye() => Console.WriteLine("Never gonna");
+}
+
+// I shouldn't be trimmed away either.
+internal class InternalCallbackHandler : CallbackBase, IHello
+{
+ public void SayHello() => Console.WriteLine("You know the rules, and so do I");
+}
+
+internal class NestedCallbackHandlers
+{
+ // I should throw a compilation warning! [and not be preserved, because I can't]
+ private class PrivateNestedCallbackHandler : CallbackBase, IHello
+ {
+ public void SayHello() => Console.WriteLine("A full commitment's what I'm thinking of");
+ }
+
+ // I should be preserved
+ internal class NestedCallbackHandler : CallbackBase, IHello
+ {
+ public void SayHello() => Console.WriteLine("You wouldn't get this from any other guy");
+ }
+
+ // I can't should be preserved
+ protected class ProtectedCallbackHandler : CallbackBase, IHello
+ {
+ public void SayHello() => Console.WriteLine("You wouldn't get this from any other guy");
+ }
+}
+
+public interface IHello
+{
+ void SayHello();
+}
+
+public interface IGoodbye
+{
+ void SayGoodbye();
+}
+
+
+
+// To prevent SharpGen.Runtime.Trim.Dummy.CallbackTest from being trimmed away from Dummy;
+public class UnrelatedClass { }
\ No newline at end of file
diff --git a/SharpGen.Runtime.Trim.Dummy.CallbackTest/SharpGen.Runtime.Trim.Dummy.CallbackTest.csproj b/SharpGen.Runtime.Trim.Dummy.CallbackTest/SharpGen.Runtime.Trim.Dummy.CallbackTest.csproj
new file mode 100644
index 00000000..bc27816a
--- /dev/null
+++ b/SharpGen.Runtime.Trim.Dummy.CallbackTest/SharpGen.Runtime.Trim.Dummy.CallbackTest.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0
+ enable
+ enable
+ true
+ true
+ true
+ false
+
+
+
+
+
+
+
+
diff --git a/SharpGen.Runtime.Trim.Dummy/Program.cs b/SharpGen.Runtime.Trim.Dummy/Program.cs
new file mode 100644
index 00000000..1bd94517
--- /dev/null
+++ b/SharpGen.Runtime.Trim.Dummy/Program.cs
@@ -0,0 +1,12 @@
+// See https://aka.ms/new-console-template for more information
+
+// I'm a dummy project for testing trimmability, since the analyzer outside of publish time isn't fully perfect yet
+// test my trimming with `dotnet publish -r win-x64`
+
+using SharpGen.Runtime.Trim.Dummy.CallbackTest;
+
+Console.WriteLine("Hello SharpGen.Runtime");
+
+// The purpose of this is to prevent `SharpGen.Runtime.Trim.Dummy.CallbackTest` from being linked away.
+// as we need to test trimming on the callback.
+var unrelated = new UnrelatedClass();
\ No newline at end of file
diff --git a/SharpGen.Runtime.Trim.Dummy/SharpGen.Runtime.Trim.Dummy.csproj b/SharpGen.Runtime.Trim.Dummy/SharpGen.Runtime.Trim.Dummy.csproj
new file mode 100644
index 00000000..12049858
--- /dev/null
+++ b/SharpGen.Runtime.Trim.Dummy/SharpGen.Runtime.Trim.Dummy.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SharpGen.Runtime/COM/ComObject.cs b/SharpGen.Runtime/COM/ComObject.cs
index f4849bc7..b1ce6bd7 100644
--- a/SharpGen.Runtime/COM/ComObject.cs
+++ b/SharpGen.Runtime/COM/ComObject.cs
@@ -18,7 +18,10 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
+#nullable enable
+
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
@@ -31,11 +34,14 @@ namespace SharpGen.Runtime;
[Guid("00000000-0000-0000-C000-000000000046")]
public class ComObject : CppObject, IUnknown
{
- public ComObject(IntPtr nativePtr): base(nativePtr)
+ public ComObject(IntPtr nativePtr) : base(nativePtr)
{
}
- public static explicit operator ComObject(IntPtr nativePtr) => nativePtr == IntPtr.Zero ? null : new ComObject(nativePtr);
+ public static explicit operator ComObject?(IntPtr nativePtr)
+ {
+ return nativePtr == IntPtr.Zero ? null : new ComObject(nativePtr);
+ }
/// HRESULT IUnknown::QueryInterface([In] const GUID& riid, [Out] void** ppvObject)
/// IUnknown::QueryInterface
@@ -43,7 +49,7 @@ public unsafe Result QueryInterface(Guid riid, out IntPtr ppvObject)
{
Result __result__;
fixed (void* ppvObject_ = &ppvObject)
- __result__ = ((delegate* unmanaged[Stdcall] )this[0U])(NativePointer, &riid, ppvObject_);
+ __result__ = ((delegate* unmanaged[Stdcall]) this[0U])(NativePointer, &riid, ppvObject_);
return __result__;
}
@@ -52,7 +58,7 @@ public unsafe Result QueryInterface(Guid riid, out IntPtr ppvObject)
public unsafe uint AddRef()
{
uint __result__;
- __result__ = ((delegate* unmanaged[Stdcall] )this[1U])(NativePointer);
+ __result__ = ((delegate* unmanaged[Stdcall]) this[1U])(NativePointer);
return __result__;
}
@@ -61,7 +67,7 @@ public unsafe uint AddRef()
public unsafe uint Release()
{
uint __result__;
- __result__ = ((delegate* unmanaged[Stdcall] )this[2U])(NativePointer);
+ __result__ = ((delegate* unmanaged[Stdcall]) this[2U])(NativePointer);
return __result__;
}
@@ -69,7 +75,7 @@ public unsafe uint Release()
/// Initializes a new instance of the class from a IUnknown object.
///
/// Reference to a IUnknown object
-#if NET5_0_OR_GREATER
+#if NET6_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
public ComObject(object iunknownObject) : base(Marshal.GetIUnknownForObject(iunknownObject))
@@ -105,10 +111,14 @@ public virtual IntPtr QueryInterfaceOrNull(Guid guid)
/// ms682521
/// IUnknown::QueryInterface
/// IUnknown::QueryInterface
- public virtual T QueryInterface() where T : ComObject
+ public virtual T QueryInterface<
+#if NET6_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+#endif
+ T>() where T : ComObject
{
QueryInterface(typeof(T).GetTypeInfo().GUID, out var parentPtr).CheckError();
- return MarshallingHelpers.FromPointer(parentPtr);
+ return MarshallingHelpers.FromPointer(parentPtr)!;
}
///
@@ -120,10 +130,14 @@ public virtual T QueryInterface() where T : ComObject
/// ms682521
/// IUnknown::QueryInterface
/// IUnknown::QueryInterface
-#if NET5_0_OR_GREATER
+#if NET6_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
- public static T As(object comObject) where T : ComObject => As(Marshal.GetIUnknownForObject(comObject));
+ public static T As<
+#if NET6_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+#endif
+ T>(object comObject) where T : ComObject => As(Marshal.GetIUnknownForObject(comObject));
///
/// Queries a managed object for a particular COM interface support (This method is a shortcut to )
@@ -134,7 +148,11 @@ public virtual T QueryInterface() where T : ComObject
/// ms682521
/// IUnknown::QueryInterface
/// IUnknown::QueryInterface
- public static T As(IntPtr iunknownPtr) where T : ComObject
+ public static T As<
+#if NET6_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+#endif
+ T>(IntPtr iunknownPtr) where T : ComObject
{
using var tempObject = new ComObject(iunknownPtr);
return tempObject.QueryInterface();
@@ -149,10 +167,14 @@ public static T As(IntPtr iunknownPtr) where T : ComObject
/// ms682521
/// IUnknown::QueryInterface
/// IUnknown::QueryInterface
-#if NET5_0_OR_GREATER
+#if NET6_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
- public static T QueryInterface(object comObject) where T : ComObject =>
+ public static T QueryInterface<
+#if NET6_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+#endif
+ T>(object comObject) where T : ComObject =>
As(Marshal.GetIUnknownForObject(comObject));
///
@@ -164,7 +186,11 @@ public static T QueryInterface(object comObject) where T : ComObject =>
/// ms682521
/// IUnknown::QueryInterface
/// IUnknown::QueryInterface
- public static T QueryInterfaceOrNull(IntPtr comPointer) where T : ComObject
+ public static T? QueryInterfaceOrNull<
+#if NET6_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+#endif
+ T>(IntPtr comPointer) where T : ComObject
{
using var tempObject = new ComObject(comPointer);
return tempObject.QueryInterfaceOrNull();
@@ -178,16 +204,24 @@ public static T QueryInterfaceOrNull(IntPtr comPointer) where T : ComObject
/// ms682521
/// IUnknown::QueryInterface
/// IUnknown::QueryInterface
- public virtual T QueryInterfaceOrNull() where T : ComObject =>
- MarshallingHelpers.FromPointer(QueryInterfaceOrNull(typeof(T).GetTypeInfo().GUID));
+ public virtual T? QueryInterfaceOrNull<
+#if NET6_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+#endif
+ T>() where T : ComObject
+ {
+ return MarshallingHelpers.FromPointer(QueryInterfaceOrNull(typeof(T).GetTypeInfo().GUID));
+ }
///
/// Query Interface for a particular interface support and attach to the given instance.
///
///
///
- protected void QueryInterfaceFrom(T fromObject) where T : ComObject =>
+ protected void QueryInterfaceFrom(T fromObject) where T : ComObject
+ {
NativePointer = fromObject.QueryInterfaceOrNull(GetType().GetTypeInfo().GUID);
+ }
protected override void DisposeCore(IntPtr nativePointer, bool disposing)
{
diff --git a/SharpGen.Runtime/COM/ComObjectVtbl.cs b/SharpGen.Runtime/COM/ComObjectVtbl.cs
index 6a16153c..f986f34b 100644
--- a/SharpGen.Runtime/COM/ComObjectVtbl.cs
+++ b/SharpGen.Runtime/COM/ComObjectVtbl.cs
@@ -26,12 +26,12 @@ namespace SharpGen.Runtime;
public static unsafe class ComObjectVtbl
{
-#if NET5_0_OR_GREATER
+#if NET6_0_OR_GREATER
public static readonly IntPtr[] Vtbl =
{
- (IntPtr) (delegate* unmanaged[Stdcall]) (&QueryInterfaceImpl),
- (IntPtr) (delegate* unmanaged[Stdcall]) (&AddRefImpl),
- (IntPtr) (delegate* unmanaged[Stdcall]) (&ReleaseImpl)
+ (IntPtr) (delegate* unmanaged) (&QueryInterfaceImpl),
+ (IntPtr) (delegate* unmanaged) (&AddRefImpl),
+ (IntPtr) (delegate* unmanaged) (&ReleaseImpl)
};
#else
private static readonly QueryInterfaceDelegate QueryInterfaceDelegateCache = QueryInterfaceImpl;
@@ -45,11 +45,11 @@ public static unsafe class ComObjectVtbl
};
#endif
-#if !NET5_0_OR_GREATER
+#if !NET6_0_OR_GREATER
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int QueryInterfaceDelegate(IntPtr thisObject, Guid* guid, void* output);
#else
- [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]
+ [UnmanagedCallersOnly]
#endif
private static int QueryInterfaceImpl(IntPtr thisObject, Guid* guid, void* output)
{
@@ -78,20 +78,20 @@ private static int QueryInterfaceImpl(IntPtr thisObject, Guid* guid, void* outpu
#endif
}
-#if !NET5_0_OR_GREATER
+#if !NET6_0_OR_GREATER
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate uint AddRefDelegate(IntPtr thisObject);
#else
- [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]
+ [UnmanagedCallersOnly]
#endif
private static uint AddRefImpl(IntPtr thisObject) =>
MarshallingHelpers.AddRef(CppObjectShadow.ToCallback(thisObject));
-#if !NET5_0_OR_GREATER
+#if !NET6_0_OR_GREATER
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate uint ReleaseDelegate(IntPtr thisObject);
#else
- [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]
+ [UnmanagedCallersOnly]
#endif
private static uint ReleaseImpl(IntPtr thisObject) =>
MarshallingHelpers.Release(CppObjectShadow.ToCallback(thisObject));
diff --git a/SharpGen.Runtime/COM/InspectableVtbl.cs b/SharpGen.Runtime/COM/InspectableVtbl.cs
index 6f82f9e5..b94ab70a 100644
--- a/SharpGen.Runtime/COM/InspectableVtbl.cs
+++ b/SharpGen.Runtime/COM/InspectableVtbl.cs
@@ -27,12 +27,12 @@ namespace SharpGen.Runtime;
public static unsafe class InspectableVtbl
{
-#if NET5_0_OR_GREATER
+#if NET6_0_OR_GREATER
public static readonly IntPtr[] Vtbl =
{
- (IntPtr) (delegate *unmanaged[Stdcall]) (&GetIids),
- (IntPtr) (delegate *unmanaged[Stdcall]) (&GetRuntimeClassName),
- (IntPtr) (delegate *unmanaged[Stdcall]) (&GetTrustLevel)
+ (IntPtr) (delegate *unmanaged) (&GetIids),
+ (IntPtr) (delegate *unmanaged) (&GetRuntimeClassName),
+ (IntPtr) (delegate *unmanaged) (&GetTrustLevel)
};
#else
private static readonly GetIidsDelegate GetIidsDelegateCache = GetIids;
@@ -52,11 +52,11 @@ public static unsafe class InspectableVtbl
/// /* [size_is][size_is][out] */ __RPC__deref_out_ecount_full_opt(*iidCount) IID **iids
/// )
///
-#if !NET5_0_OR_GREATER
+#if !NET6_0_OR_GREATER
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int GetIidsDelegate(IntPtr thisPtr, int* iidCount, IntPtr** iids);
#else
- [UnmanagedCallersOnly(CallConvs = new[]{typeof(CallConvStdcall)})]
+ [UnmanagedCallersOnly]
#endif
private static int GetIids(IntPtr thisPtr, int* iidCount, IntPtr** iids)
{
@@ -85,11 +85,11 @@ private static int GetIids(IntPtr thisPtr, int* iidCount, IntPtr** iids)
///
/// HRESULT STDMETHODCALLTYPE GetRuntimeClassName([out] __RPC__deref_out_opt HSTRING *className)
///
-#if !NET5_0_OR_GREATER
+#if !NET6_0_OR_GREATER
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int GetRuntimeClassNameDelegate(IntPtr thisPtr, IntPtr* className);
#else
- [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]
+ [UnmanagedCallersOnly]
#endif
private static int GetRuntimeClassName(IntPtr thisPtr, IntPtr* className)
{
@@ -118,11 +118,11 @@ private static int GetRuntimeClassName(IntPtr thisPtr, IntPtr* className)
///
/// HRESULT STDMETHODCALLTYPE GetTrustLevel(/* [out] */ __RPC__out TrustLevel *trustLevel);
///
-#if !NET5_0_OR_GREATER
+#if !NET6_0_OR_GREATER
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int GetTrustLevelDelegate(IntPtr thisPtr, int* trustLevel);
#else
- [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]
+ [UnmanagedCallersOnly]
#endif
private static int GetTrustLevel(IntPtr thisPtr, int* trustLevel)
{
diff --git a/SharpGen.Runtime/CallbackBase.Reflection.cs b/SharpGen.Runtime/CallbackBase.Reflection.cs
index 8e8f8158..4d3f75f0 100644
--- a/SharpGen.Runtime/CallbackBase.Reflection.cs
+++ b/SharpGen.Runtime/CallbackBase.Reflection.cs
@@ -1,111 +1,223 @@
+#nullable enable
+
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
+using SharpGen.Runtime.Trimming;
namespace SharpGen.Runtime;
public abstract partial class CallbackBase
{
- private static readonly Dictionary> TypeToShadowTypes = new();
-
- private static Guid[] BuildGuidList(Type type)
+ private readonly struct ImmediateShadowInterfaceInfo
{
- List guids = new();
+#if NET6_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
+#endif
+ public readonly TypeInfo Type;
+ public readonly List ImplementedInterfaces;
- // Associate all shadows with their interfaces.
- foreach (var item in GetUninheritedShadowedInterfaces(type))
+ public ImmediateShadowInterfaceInfo(
+#if NET6_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
+#endif
+ TypeInfo type)
{
- var itemType = item.Type;
- if (!ExcludeFromTypeListAttribute.Has(itemType))
- guids.Add(itemType.GUID);
+ Type = type;
+ ImplementedInterfaces = new(6);
- // Associate also inherited interface to this shadow
- foreach (var inheritInterface in item.ImplementedInterfaces)
+ foreach (var implementedInterface in type.ImplementedInterfaces)
{
- if (!ExcludeFromTypeListAttribute.Has(inheritInterface))
- guids.Add(inheritInterface.GUID);
+ var interfaceInfo = implementedInterface.GetTypeInfo();
+
+ // If there is no Vtbl attribute then this isn't a native interface.
+ if (!VtblAttribute.Has(interfaceInfo))
+ continue;
+
+ ImplementedInterfaces.Add(interfaceInfo);
}
}
+ }
-#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1 || NET472
- HashSet guidSet = new(guids.Count);
-#else
- HashSet guidSet = new();
+ // Cache reflection on interface inheritance
+ private class CallbackTypeInfo
+ {
+#if NET6_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
#endif
+ private readonly TypeInfo type;
+ private ImmediateShadowInterfaceInfo[]? _vtbls;
+ private TypeInfo[]? _shadows;
+ private Guid[]? _guids;
+
+ public CallbackTypeInfo(
+#if NET6_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
+#endif
+ Type type) : this(type.GetTypeInfo())
+ {
+ }
- return guids.Where(guid => guidSet.Add(guid)).ToArray();
- }
+ private CallbackTypeInfo(
+#if NET6_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
+#endif
+ TypeInfo type)
+ {
+ this.type = type ?? throw new ArgumentNullException(nameof(type));
+ }
+ ///
+ /// Gets a list of implemented interfaces that aren't inherited by any other [Vtbl] interfaces.
+ ///
+ /// The interface list.
+ public ImmediateShadowInterfaceInfo[] Vtbls
+ {
+ get
+ {
+ lock (this)
+ if (_vtbls is { } vtbls)
+ return vtbls;
- ///
- /// Gets a list of interfaces implemented by that aren't inherited by any other shadowed interfaces.
- ///
- /// The type for which to get the list.
- /// The interface list.
- private static List GetUninheritedShadowedInterfaces(Type type)
- {
- List list;
- var typeToShadowTypes = TypeToShadowTypes;
+ var list = BuildVtblList();
+
+ lock (this)
+ _vtbls = list;
- // Cache reflection on interface inheritance
- lock (typeToShadowTypes)
- if (typeToShadowTypes.TryGetValue(type, out list))
return list;
+ }
+ }
- list = BuildUninheritedShadowedInterfacesList(type);
+ public TypeInfo[] Shadows
+ {
+ get
+ {
+ lock (this)
+ if (_shadows is { } shadows)
+ return shadows;
- lock (typeToShadowTypes)
- typeToShadowTypes[type] = list;
+ var list = BuildShadowList();
- return list;
- }
+ lock (this)
+ _shadows = list;
- private static List BuildUninheritedShadowedInterfacesList(Type type)
- {
- HashSet removeQueue = new();
- List result = new();
+ return list;
+ }
+ }
- foreach (var implementedInterface in type.GetTypeInfo().ImplementedInterfaces)
+ public Guid[] Guids
{
- var item = implementedInterface.GetTypeInfo();
+ get
+ {
+ lock (this)
+ if (_guids is { } guids)
+ return guids;
- // Only process interfaces that are have vtbl
- if (!VtblAttribute.Has(item))
- continue;
+ var list = BuildGuidList();
- ImmediateShadowInterfaceInfo interfaceInfo = new(item);
- result.Add(interfaceInfo);
+ lock (this)
+ _guids = list;
- // Keep only final interfaces and not intermediate.
- foreach (var @interface in interfaceInfo.ImplementedInterfaces)
- removeQueue.Add(@interface);
+ return list;
+ }
}
- result.RemoveAll(item => removeQueue.Contains(item.Type));
- return result;
- }
+ private ImmediateShadowInterfaceInfo[] BuildVtblList()
+ {
+ HashSet removeQueue = new();
+ List result = new();
- private readonly struct ImmediateShadowInterfaceInfo
- {
- public readonly TypeInfo Type;
- public readonly List ImplementedInterfaces;
+ foreach (var implementedInterface in type.ImplementedInterfaces)
+ {
+ var item = implementedInterface.GetTypeInfoWithNestedPreservedInterfaces();
+
+ // Only process interfaces that have vtbl
+ if (!VtblAttribute.Has(item))
+ continue;
+
+ ImmediateShadowInterfaceInfo interfaceInfo = new(item);
+ result.Add(interfaceInfo);
+
+ // Keep only final interfaces and not intermediate.
+ foreach (var @interface in interfaceInfo.ImplementedInterfaces)
+ removeQueue.Add(@interface);
+ }
+
+ result.RemoveAll(item => removeQueue.Contains(item.Type));
+ return result.ToArray();
+ }
- public ImmediateShadowInterfaceInfo(TypeInfo type)
+ private Guid[] BuildGuidList()
{
- Type = type;
- ImplementedInterfaces = new(6);
+ List guids = new();
+
+ // Associate all shadows with their interfaces.
+ foreach (var item in Vtbls)
+ {
+ var itemType = item.Type;
+ if (!ExcludeFromTypeListAttribute.Has(itemType))
+ guids.Add(itemType.GUID);
+
+ // Associate also inherited interface to this shadow
+ foreach (var inheritInterface in item.ImplementedInterfaces)
+ {
+ if (!ExcludeFromTypeListAttribute.Has(inheritInterface))
+ guids.Add(inheritInterface.GUID);
+ }
+ }
+
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1 || NET472
+ HashSet guidSet = new(guids.Count);
+#else
+ HashSet guidSet = new();
+#endif
+
+ return guids.Where(guid => guidSet.Add(guid)).ToArray();
+ }
+
+ private TypeInfo[] BuildShadowList()
+ {
+ List shadows = new(), result;
foreach (var implementedInterface in type.ImplementedInterfaces)
{
- var interfaceInfo = implementedInterface.GetTypeInfo();
+ var attribute = ShadowAttribute.Get(implementedInterface);
- // If there is no Vtbl attribute then this isn't a native interface.
- if (!VtblAttribute.Has(interfaceInfo))
+ // Only process interfaces that have Shadow attribute
+ if (attribute is null)
continue;
- ImplementedInterfaces.Add(interfaceInfo);
+ shadows.Add(attribute.Type.GetTypeInfo());
}
+
+ var count = shadows.Count;
+ result = new List(count);
+
+ // Retain only shadows which have no other subtypes (none of the other shadows are children)
+ for (var i = 0; i < count; i++)
+ {
+ var item = shadows[i];
+
+ var any = false;
+ for (var j = 0; j < count; j++)
+ {
+ if (i == j)
+ continue;
+
+ if (item.IsAssignableFrom(shadows[j]))
+ {
+ any = true;
+ break;
+ }
+ }
+
+ if (!any)
+ result.Add(item);
+ }
+
+ return result.ToArray();
}
}
}
\ No newline at end of file
diff --git a/SharpGen.Runtime/CallbackBase.ReflectionCache.cs b/SharpGen.Runtime/CallbackBase.ReflectionCache.cs
new file mode 100644
index 00000000..63fe9a00
--- /dev/null
+++ b/SharpGen.Runtime/CallbackBase.ReflectionCache.cs
@@ -0,0 +1,32 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using SharpGen.Runtime.Trimming;
+
+namespace SharpGen.Runtime;
+
+public abstract partial class CallbackBase
+{
+ private static readonly Dictionary TypeReflectionCache = new();
+
+ private CallbackTypeInfo GetTypeInfo()
+ {
+ CallbackTypeInfo? info;
+ var type = this.GetTypeWithNestedPreservedInterfaces();
+ var cache = TypeReflectionCache;
+
+ lock (cache)
+ {
+ if (cache.TryGetValue(type, out info))
+ return info;
+ }
+
+ info = new CallbackTypeInfo(type);
+
+ lock (cache)
+ cache[type] = info;
+
+ return info;
+ }
+}
\ No newline at end of file
diff --git a/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs b/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs
new file mode 100644
index 00000000..139c39e5
--- /dev/null
+++ b/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs
@@ -0,0 +1,83 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace SharpGen.Runtime;
+
+public abstract unsafe partial class CallbackBase
+{
+ protected virtual Guid[] BuildGuidList() => GetTypeInfo().Guids;
+
+ private GCHandle CreateShadow(
+#if NET6_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
+#endif
+ TypeInfo type)
+ {
+ var shadow = (CppObjectShadow) Activator.CreateInstance(type.AsType())!;
+
+ // Initialize the shadow with the callback
+ shadow.Initialize(ThisHandle);
+
+ return GCHandle.Alloc(shadow, GCHandleType.Normal);
+ }
+
+#if NET6_0_OR_GREATER
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2062", Justification = $"{nameof(ShadowAttribute.Type)} is already marked `DynamicallyAccessedMemberTypes.PublicConstructors` and the existing check via `Debug.Assert(holder.GetTypeInfo().GetConstructor(Type.EmptyTypes)` will ensure correctness.")]
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2111", Justification = "Same as above.")]
+#endif
+ protected virtual void InitializeCallableWrappers(IDictionary ccw)
+ {
+ // Associate all shadows with their interfaces.
+ var typeInfo = GetTypeInfo();
+
+ var interfaces = typeInfo.Vtbls;
+ if (interfaces.Length == 0)
+ return;
+
+ // Lazy solution: a single shadow for the whole hierarchy.
+ // There are limitations to this approach in multi-inheritance scenarios,
+ // when there are multiple shadows inheriting one, and they are in separate vtbl trees.
+ // Then ToShadow methods.
+ var shadowTypes = typeInfo.Shadows;
+
+ var shadowHandle = shadowTypes.Length switch
+ {
+ 0 => ThisHandle,
+ 1 => CreateShadow(shadowTypes[0]),
+ _ => CreateMultiInheritanceShadow(shadowTypes.Select(CreateShadow).ToArray())
+ };
+
+ foreach (var item in interfaces)
+ {
+ Debug.Assert(VtblAttribute.Has(item.Type));
+
+ var success = TypeDataStorage.GetTargetVtbl(item.Type, out var vtbl);
+ Debug.Assert(success);
+
+ var wrapper = CreateCallableWrapper(vtbl, shadowHandle);
+
+ ccw[item.Type.GUID] = wrapper;
+
+ // Associate also inherited interface to this shadow
+ foreach (var inheritInterface in item.ImplementedInterfaces)
+ {
+ var guid = inheritInterface.GUID;
+
+ // If we have the same GUID as an already added interface,
+ // then there's already an accurate shadow for it, so we have nothing to do.
+ if (ccw.ContainsKey(guid))
+ continue;
+
+ // Use same CCW as derived
+ ccw[guid] = wrapper;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SharpGen.Runtime/CallbackBase.cs b/SharpGen.Runtime/CallbackBase.cs
index ae3b7151..0be15dff 100644
--- a/SharpGen.Runtime/CallbackBase.cs
+++ b/SharpGen.Runtime/CallbackBase.cs
@@ -18,10 +18,12 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
+#nullable enable
+
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Reflection;
+using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
@@ -35,10 +37,10 @@ namespace SharpGen.Runtime;
///
public abstract unsafe partial class CallbackBase : DisposeBase, ICallbackable
{
- private Dictionary _ccw;
+ private Dictionary? _ccw;
private IntPtr _guidPtr;
- private IntPtr[] _guids;
-#if NET5_0_OR_GREATER
+ private IntPtr[]? _guids;
+#if NET6_0_OR_GREATER
private uint _refCount = 1;
#else
private int _refCount = 1;
@@ -78,7 +80,7 @@ protected virtual void DisposeCore(bool disposing)
public uint AddRef()
{
-#if NET5_0_OR_GREATER
+#if NET6_0_OR_GREATER
return Interlocked.Increment(ref _refCount);
#else
return (uint)Interlocked.Increment(ref _refCount);
@@ -87,7 +89,7 @@ public uint AddRef()
public uint Release()
{
-#if NET5_0_OR_GREATER
+#if NET6_0_OR_GREATER
var newRefCount = Interlocked.Decrement(ref _refCount);
#else
var newRefCount = Interlocked.Decrement(ref _refCount);
@@ -110,7 +112,7 @@ public ReadOnlySpan Guids
if (_guids is { } guids)
return guids;
- var guidList = BuildGuidList(GetType());
+ var guidList = BuildGuidList();
var guidCount = guidList.Length;
var guidPtr = Marshal.AllocHGlobal(sizeof(Guid) * guidCount);
var pGuid = (Guid*) guidPtr;
@@ -134,7 +136,8 @@ public IntPtr Find(Guid guidType)
if (_ccw is not { } guidToShadow)
{
guidToShadow = _ccw = new(4);
- InitializeCallableWrapperStorage();
+ Debug.Assert(!_thisHandle.IsAllocated);
+ InitializeCallableWrappers(_ccw);
}
return guidToShadow.TryGetValue(guidType, out var shadow) ? shadow : IntPtr.Zero;
@@ -142,60 +145,34 @@ public IntPtr Find(Guid guidType)
public IntPtr Find() where TCallback : ICallbackable => Find(TypeDataStorage.GetGuid());
- private void InitializeCallableWrapperStorage()
+ protected GCHandle ThisHandle => _thisHandle switch
{
- var ccw = _ccw;
- Debug.Assert(ccw is not null);
- Debug.Assert(ccw.Count == 0);
- Debug.Assert(!_thisHandle.IsAllocated);
-
- // Associate all shadows with their interfaces.
- var interfaces = GetUninheritedShadowedInterfaces(GetType());
- if (interfaces.Count == 0)
- return;
+ { IsAllocated: true } handle => handle,
+ _ => _thisHandle = GCHandle.Alloc(this, GCHandleType.WeakTrackResurrection)
+ };
- var thisHandle = _thisHandle = GCHandle.Alloc(this, GCHandleType.WeakTrackResurrection);
+ protected static IntPtr CreateCallableWrapper(void* vtbl, GCHandle callback)
+ {
+ Debug.Assert(vtbl != (void*) 0);
+ Debug.Assert(callback.IsAllocated);
+ return CppObjectCallableWrapper.Create(vtbl, callback);
+ }
- foreach (var item in interfaces)
+ protected static GCHandle CreateMultiInheritanceShadow(params GCHandle[] shadows)
+ {
+ if (shadows == null) throw new ArgumentNullException(nameof(shadows));
+ return shadows.Length switch
{
- Debug.Assert(VtblAttribute.Has(item.Type));
-
- GCHandle shadowHandle;
- if (ShadowAttribute.Get(item.Type) is { Type: { } shadowType })
- {
- var shadow = (CppObjectShadow) Activator.CreateInstance(shadowType);
-
- // Initialize the shadow with the callback
- shadow.Initialize(thisHandle);
-
- shadowHandle = GCHandle.Alloc(shadow, GCHandleType.Normal);
- }
- else
- {
- shadowHandle = thisHandle;
- }
-
- var success = TypeDataStorage.GetVtbl(item.Type.GUID, out var vtbl);
- Debug.Assert(success);
-
- var wrapper = CppObjectShadow.CreateCallableWrapper(shadowHandle, vtbl);
-
- ccw[item.Type.GUID] = wrapper;
-
- // Associate also inherited interface to this shadow
- foreach (var inheritInterface in item.ImplementedInterfaces)
- {
- var guid = inheritInterface.GUID;
-
- // If we have the same GUID as an already added interface,
- // then there's already an accurate shadow for it, so we have nothing to do.
- if (ccw.ContainsKey(guid))
- continue;
-
- // Use same CCW as derived
- ccw[guid] = wrapper;
- }
- }
+ 0 => throw new ArgumentException(
+ "Multi-inheritance shadow cannot be constructed for empty shadow collection.",
+ nameof(shadows)
+ ),
+ 1 => throw new ArgumentException(
+ "Multi-inheritance shadow cannot be constructed for a single shadow.",
+ nameof(shadows)
+ ),
+ _ => GCHandle.Alloc(new CppObjectMultiShadow(shadows), GCHandleType.Normal)
+ };
}
private void DisposeCallableWrappers(bool disposing)
@@ -209,7 +186,7 @@ private void DisposeCallableWrappers(bool disposing)
#endif
foreach (var comObjectCallbackNative in shadows)
if (freed.Add(comObjectCallbackNative))
- CppObjectShadow.FreeCallableWrapper(comObjectCallbackNative, disposing);
+ CppObjectCallableWrapper.Free(comObjectCallbackNative, disposing);
}
if (Interlocked.Exchange(ref _guidPtr, default) is var pointer)
@@ -218,4 +195,37 @@ private void DisposeCallableWrappers(bool disposing)
if (_thisHandle.IsAllocated)
_thisHandle.Free();
}
+
+ internal IReadOnlyCollection Shadows
+ {
+ get
+ {
+ Debug.Assert(_ccw is not null);
+
+ HashSet shadows = new(ReferenceEqualityComparer.Instance);
+
+ foreach (CppObjectCallableWrapper* ccw in _ccw!.Values)
+ {
+ var handle = ccw->Shadow;
+ if (!handle.IsAllocated)
+ continue;
+
+ switch (handle.Target)
+ {
+ case CppObjectShadow shadow:
+ shadows.Add(shadow);
+ break;
+ case CppObjectMultiShadow multiShadow:
+ multiShadow.AddShadowsToSet(shadows);
+ break;
+ }
+ }
+
+#if NET45
+ return shadows.ToArray();
+#else
+ return shadows;
+#endif
+ }
+ }
}
\ No newline at end of file
diff --git a/SharpGen.Runtime/CppObjectCallableWrapper.cs b/SharpGen.Runtime/CppObjectCallableWrapper.cs
new file mode 100644
index 00000000..b465c0a4
--- /dev/null
+++ b/SharpGen.Runtime/CppObjectCallableWrapper.cs
@@ -0,0 +1,46 @@
+#nullable enable
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace SharpGen.Runtime;
+
+internal unsafe ref struct CppObjectCallableWrapper
+{
+ internal static readonly int Size = IntPtr.Size * 2;
+
+ // ReSharper disable once NotAccessedField.Local
+ private void* _vtbl;
+ private IntPtr _shadow;
+
+ public readonly GCHandle Shadow => GCHandle.FromIntPtr(_shadow);
+
+ public static IntPtr Create(void* vtbl, GCHandle callback)
+ {
+ // Allocate ptr to vtbl + ptr to callback together
+ var nativePointer = Marshal.AllocHGlobal(Size);
+ ref var native = ref *(CppObjectCallableWrapper*) nativePointer;
+
+ native._vtbl = vtbl;
+ native._shadow = GCHandle.ToIntPtr(callback);
+
+ return nativePointer;
+ }
+
+ public static void Free(IntPtr pointer, bool disposing)
+ {
+ // Free the callback
+ if (((CppObjectCallableWrapper*) pointer)->Shadow is { IsAllocated: true, Target: CppObjectShadow shadow } handle)
+ {
+ // Callback is a CppObjectShadow subtype. Dispose it if needed.
+ MemoryHelpers.Dispose(shadow, disposing);
+
+ // Free GCHandle if it points to a shadow, not the CallbackBase. Why?
+ // Same GCHandle is reused in multiple CCWs to lower the handle table pressure.
+ handle.Free();
+ }
+
+ // Free instance
+ Marshal.FreeHGlobal(pointer);
+ }
+}
\ No newline at end of file
diff --git a/SharpGen.Runtime/CppObjectMultiShadow.cs b/SharpGen.Runtime/CppObjectMultiShadow.cs
new file mode 100644
index 00000000..777810e5
--- /dev/null
+++ b/SharpGen.Runtime/CppObjectMultiShadow.cs
@@ -0,0 +1,81 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+namespace SharpGen.Runtime;
+
+internal sealed class CppObjectMultiShadow
+{
+ private readonly GCHandle[] _shadows;
+
+ public CppObjectMultiShadow(GCHandle[] shadows)
+ {
+ _shadows = shadows ?? throw new ArgumentNullException(nameof(shadows));
+
+#if DEBUG
+ foreach (var handle in _shadows)
+ {
+ Debug.Assert(handle.IsAllocated);
+ Debug.Assert(handle.Target is CppObjectShadow or CppObjectMultiShadow);
+ }
+#endif
+ }
+
+ public T? ToShadow() where T : CppObjectShadow
+ {
+ foreach (var handle in _shadows)
+ {
+ switch (handle.Target)
+ {
+ case T shadow:
+ return shadow;
+ case CppObjectMultiShadow multiShadow when multiShadow.ToShadow() is { } shadow:
+ return shadow;
+ }
+ }
+
+ return null;
+ }
+
+ public bool ToCallback([NotNullWhen(true)] out T? value) where T : ICallbackable
+ {
+ foreach (var handle in _shadows)
+ {
+ switch (handle.Target)
+ {
+ case CppObjectShadow shadow:
+ value = shadow.ToCallback();
+ return true;
+ case CppObjectMultiShadow multiShadow when multiShadow.ToShadow() is { } shadow:
+ value = shadow.ToCallback();
+ return true;
+ }
+ }
+
+ value = default;
+ return false;
+ }
+
+ internal void AddShadowsToSet(HashSet shadows)
+ {
+ foreach (var handle in _shadows)
+ {
+ if (!handle.IsAllocated)
+ continue;
+
+ switch (handle.Target)
+ {
+ case CppObjectShadow shadow:
+ shadows.Add(shadow);
+ break;
+ case CppObjectMultiShadow multiShadow:
+ multiShadow.AddShadowsToSet(shadows);
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SharpGen.Runtime/CppObjectShadow.cs b/SharpGen.Runtime/CppObjectShadow.cs
index ac327ac7..ea2be973 100644
--- a/SharpGen.Runtime/CppObjectShadow.cs
+++ b/SharpGen.Runtime/CppObjectShadow.cs
@@ -18,8 +18,13 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
+#nullable enable
+
using System;
+using System.Collections.Generic;
using System.Diagnostics;
+using System.Linq;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SharpGen.Runtime;
@@ -36,8 +41,7 @@ public abstract unsafe class CppObjectShadow
static CppObjectShadow()
{
- Debug.Assert(Marshal.SizeOf(typeof(CppObjectNative)) == CppObjectNative.Size);
- Debug.Assert(sizeof(CppObjectNative) == CppObjectNative.Size);
+ Debug.Assert(sizeof(CppObjectCallableWrapper) == CppObjectCallableWrapper.Size);
}
protected CppObjectShadow()
@@ -54,63 +58,44 @@ internal void Initialize(GCHandle callbackHandle)
this.callbackHandle = callbackHandle;
}
- internal static IntPtr CreateCallableWrapper(GCHandle callback, void* vtbl)
- {
- // Allocate ptr to vtbl + ptr to callback together
- var nativePointer = Marshal.AllocHGlobal(CppObjectNative.Size);
- ref var native = ref *(CppObjectNative*) nativePointer;
-
- Debug.Assert(callback.IsAllocated);
-
- native.VtblPointer = vtbl;
- native.Shadow = callback;
-
- return nativePointer;
- }
-
- internal static void FreeCallableWrapper(IntPtr pointer, bool disposing)
- {
- // Free the callback
- if (((CppObjectNative*) pointer)->Shadow is { IsAllocated: true, Target: CppObjectShadow shadow } handle)
- {
- // Callback is a CppObjectShadow subtype. Dispose it if needed.
- MemoryHelpers.Dispose(shadow, disposing);
-
- // Free GCHandle if it points to a shadow, not the CallbackBase. Why?
- // Same GCHandle is reused in multiple CCWs to lower the handle table pressure.
- handle.Free();
- }
-
- // Free instance
- Marshal.FreeHGlobal(pointer);
- }
-
- public static T ToShadow(IntPtr thisPtr) where T : CppObjectShadow
+ public static T ToAnyShadow(IntPtr thisPtr) where T : CppObjectShadow
{
Debug.Assert(thisPtr != IntPtr.Zero);
- var handle = ((CppObjectNative*) thisPtr)->Shadow;
+ var handle = ((CppObjectCallableWrapper*) thisPtr)->Shadow;
Debug.Assert(handle.IsAllocated);
return handle.Target switch
{
T shadow => shadow,
+ CppObjectMultiShadow multiShadow => multiShadow.ToShadow() ?? throw new Exception($"Shadow {typeof(T).FullName} not found in the inheritance graph"),
+ CallbackBase callback => callback.Shadows.OfType().FirstOrDefault() ?? throw new Exception($"Shadow {typeof(T).FullName} not found in the inheritance graph"),
null => throw new Exception($"Shadow {typeof(T).FullName} is dead"),
+ ICallbackable value => throw new Exception(
+ $"Shadow is of an unexpected {nameof(ICallbackable)} type {value.GetType().FullName}, expected {typeof(T).FullName}"
+ ),
{ } value => throw new Exception(
$"Shadow is of an unexpected type {value.GetType().FullName}, expected {typeof(T).FullName}"
)
};
}
+ public static IEnumerable ToAllShadows(IntPtr thisPtr) where T : CppObjectShadow =>
+ ToCallback(thisPtr).Shadows.OfType();
+
+ public IEnumerable ToAllShadows() where T : CppObjectShadow => ToCallback().Shadows.OfType();
+
public static T ToCallback(IntPtr thisPtr) where T : ICallbackable
{
Debug.Assert(thisPtr != IntPtr.Zero);
- var handle = ((CppObjectNative*) thisPtr)->Shadow;
+ var handle = ((CppObjectCallableWrapper*) thisPtr)->Shadow;
Debug.Assert(handle.IsAllocated);
return handle.Target switch
{
T value => value,
CppObjectShadow shadow => shadow.ToCallback(),
+ CppObjectMultiShadow multiShadow when multiShadow.ToCallback(out T? callback) => callback,
+ CppObjectMultiShadow => throw new Exception($"Shadow {typeof(T).FullName} is missing the callback in the whole inheritance graph"),
null => throw new Exception($"Shadow {typeof(T).FullName} is dead"),
{ } value => throw new Exception(
$"Shadow is of an unexpected type {value.GetType().FullName}, expected {typeof(T).FullName}"
@@ -131,19 +116,4 @@ public T ToCallback() where T : ICallbackable
)
};
}
-
- private ref struct CppObjectNative
- {
- internal static readonly int Size = IntPtr.Size * 2;
-
- // ReSharper disable once NotAccessedField.Local
- public void* VtblPointer;
- private IntPtr _shadowPointer;
-
- public GCHandle Shadow
- {
- readonly get => GCHandle.FromIntPtr(_shadowPointer);
- set => _shadowPointer = GCHandle.ToIntPtr(value);
- }
- }
}
\ No newline at end of file
diff --git a/SharpGen.Runtime/Diagnostics/ObjectTracker.cs b/SharpGen.Runtime/Diagnostics/ObjectTracker.cs
index 8d04d44a..fd4444b2 100644
--- a/SharpGen.Runtime/Diagnostics/ObjectTracker.cs
+++ b/SharpGen.Runtime/Diagnostics/ObjectTracker.cs
@@ -167,7 +167,7 @@ internal static void MigrateNativePointer(CppObject cppObject, IntPtr oldNativeP
///
/// Reports all COM object that are active and not yet disposed.
///
- private static List> FindActiveObjects()
+ public static List> FindActiveObjects()
{
List> activeObjects;
var objectReferences = ObjectReferences;
diff --git a/SharpGen.Runtime/InterfaceArray.cs b/SharpGen.Runtime/InterfaceArray.cs
index 74c5c4f7..cad61622 100644
--- a/SharpGen.Runtime/InterfaceArray.cs
+++ b/SharpGen.Runtime/InterfaceArray.cs
@@ -33,7 +33,11 @@ namespace SharpGen.Runtime;
[DebuggerTypeProxy(typeof(InterfaceArray<>.InterfaceArrayDebugView))]
[DebuggerDisplay("Count={" + nameof(Length) + "}")]
[SuppressMessage("ReSharper", "ConvertToAutoProperty")]
-public unsafe struct InterfaceArray : IReadOnlyList, IEnlightenedDisposable, IDisposable
+public unsafe struct InterfaceArray<
+#if NET6_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+#endif
+T> : IReadOnlyList, IEnlightenedDisposable, IDisposable
where T : CppObject
{
// .NET Native has issues with <...> in property backing fields in structs
diff --git a/SharpGen.Runtime/Mapping.xml b/SharpGen.Runtime/Mapping.xml
index 9c2933dd..754b58fc 100644
--- a/SharpGen.Runtime/Mapping.xml
+++ b/SharpGen.Runtime/Mapping.xml
@@ -1,48 +1,50 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SharpGen.Runtime/MarshallingHelpers.cs b/SharpGen.Runtime/MarshallingHelpers.cs
index a3aa2b93..708401d9 100644
--- a/SharpGen.Runtime/MarshallingHelpers.cs
+++ b/SharpGen.Runtime/MarshallingHelpers.cs
@@ -1,4 +1,7 @@
+#nullable enable
+
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace SharpGen.Runtime;
@@ -11,8 +14,14 @@ public static partial class MarshallingHelpers
/// The CppObject class that will be returned
/// The native pointer to a C++ object.
/// An instance of T bound to the native pointer
- public static T FromPointer(IntPtr cppObjectPtr) where T : CppObject =>
- cppObjectPtr == IntPtr.Zero ? null : (T) Activator.CreateInstance(typeof(T), cppObjectPtr);
+ public static T? FromPointer(IntPtr cppObjectPtr, Func factory) where T : CppObject
+ {
+ if (cppObjectPtr == IntPtr.Zero)
+ return default;
+
+ T? result = factory(cppObjectPtr);
+ return result;
+ }
///
/// Instantiate a CppObject from a native pointer.
@@ -20,25 +29,60 @@ public static T FromPointer(IntPtr cppObjectPtr) where T : CppObject =>
/// The CppObject class that will be returned
/// The native pointer to a C++ object.
/// An instance of T bound to the native pointer
- public static T FromPointer(UIntPtr cppObjectPtr) where T : CppObject =>
- cppObjectPtr == UIntPtr.Zero ? null : (T) Activator.CreateInstance(typeof(T), cppObjectPtr);
+ public static T? FromPointer<
+#if NET6_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+#endif
+ T>(IntPtr cppObjectPtr) where T : CppObject
+ {
+ if (cppObjectPtr == IntPtr.Zero)
+ return default;
+
+ object? result = Activator.CreateInstance(typeof(T), cppObjectPtr);
+ if (result is null)
+ return default;
+
+ return (T) result;
+ }
+
+ ///
+ /// Instantiate a CppObject from a native pointer.
+ ///
+ /// The CppObject class that will be returned
+ /// The native pointer to a C++ object.
+ /// An instance of T bound to the native pointer
+ public static T? FromPointer<
+#if NET6_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+#endif
+ T>(UIntPtr cppObjectPtr) where T : CppObject
+ {
+ if (cppObjectPtr == UIntPtr.Zero)
+ return default;
+
+ object? result = Activator.CreateInstance(typeof(T), cppObjectPtr);
+ if (result is null)
+ return default;
+
+ return (T) result;
+ }
[MethodImpl(Utilities.MethodAggressiveOptimization)]
public static uint AddRef(TCallback callback) where TCallback : ICallbackable =>
callback switch
{
- null => throw new NullReferenceException(),
ComObject cpp => cpp.AddRef(),
CallbackBase managed => managed.AddRef(),
+ _ => throw new NotImplementedException(),
};
[MethodImpl(Utilities.MethodAggressiveOptimization)]
public static uint Release(TCallback callback) where TCallback : ICallbackable =>
callback switch
{
- null => throw new NullReferenceException(),
ComObject cpp => cpp.Release(),
CallbackBase managed => managed.Release(),
+ _ => throw new NotImplementedException(),
};
///
@@ -47,12 +91,12 @@ public static uint Release(TCallback callback) where TCallback : ICal
/// The type of the callback.
/// The callback.
/// A pointer to the unmanaged C++ object of the callback
- public static IntPtr ToCallbackPtr(ICallbackable callback) where TCallback : ICallbackable =>
+ public static nint ToCallbackPtr(ICallbackable callback) where TCallback : ICallbackable =>
callback switch
{
- null => IntPtr.Zero,
CppObject cpp => cpp.NativePointer,
- CallbackBase managed => managed.Find()
+ CallbackBase managed => managed.Find(),
+ _ => 0,
};
///
@@ -63,7 +107,7 @@ public static IntPtr ToCallbackPtr(ICallbackable callback) where TCal
/// A pointer to the unmanaged C++ object of the callback
/// This method is meant as a fast-path for codegen to use to reduce the number of casts.
[MethodImpl(Utilities.MethodAggressiveOptimization)]
- public static IntPtr ToCallbackPtr(CppObject obj) where TCallback : ICallbackable
+ public static IntPtr ToCallbackPtr(CppObject? obj) where TCallback : ICallbackable
=> obj?.NativePointer ?? IntPtr.Zero;
///
@@ -73,5 +117,5 @@ public static IntPtr ToCallbackPtr(CppObject obj) where TCallback : I
/// A pointer to the unmanaged C++ object of the callback
/// This method is meant as a fast-path for codegen to use to reduce the number of casts.
[MethodImpl(Utilities.MethodAggressiveOptimization)]
- public static IntPtr ToCallbackPtr(CppObject obj) => obj?.NativePointer ?? IntPtr.Zero;
+ public static IntPtr ToCallbackPtr(CppObject? obj) => obj?.NativePointer ?? IntPtr.Zero;
}
\ No newline at end of file
diff --git a/SharpGen.Runtime/MemoryHelpers.Alloc.cs b/SharpGen.Runtime/MemoryHelpers.Alloc.cs
index 3850b93b..2addecf7 100644
--- a/SharpGen.Runtime/MemoryHelpers.Alloc.cs
+++ b/SharpGen.Runtime/MemoryHelpers.Alloc.cs
@@ -3,10 +3,11 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Diagnostics.CodeAnalysis;
namespace SharpGen.Runtime;
-public static partial class MemoryHelpers
+public static unsafe partial class MemoryHelpers
{
///
/// Allocate an aligned memory buffer.
@@ -17,14 +18,20 @@ public static partial class MemoryHelpers
///
/// To free this buffer, call .
///
- public static unsafe void* AllocateMemory(nuint sizeInBytes, uint align = 16)
+ public static void* AllocateMemory(nuint sizeInBytes, uint alignment = 16)
{
- nuint mask = align - 1u;
+#if NET6_0_OR_GREATER
+ var ptr = NativeMemory.AlignedAlloc(sizeInBytes, alignment);
+ Debug.Assert(IsMemoryAligned(ptr, alignment));
+ return ptr;
+#else
+ nuint mask = alignment - 1u;
var memPtr = Marshal.AllocHGlobal((nint) (sizeInBytes + mask) + sizeof(void*));
var ptr = (nuint) ((byte*) memPtr + sizeof(void*) + mask) & ~mask;
- Debug.Assert(IsMemoryAligned(ptr, align));
+ Debug.Assert(IsMemoryAligned(ptr, alignment));
((IntPtr*) ptr)[-1] = memPtr;
return (void*) ptr;
+#endif
}
///
@@ -38,9 +45,75 @@ public static partial class MemoryHelpers
///
[Obsolete("Use void*(nuint, uint) overload instead")]
[EditorBrowsable(EditorBrowsableState.Advanced)]
- public static unsafe IntPtr AllocateMemory(int sizeInBytes, int align = 16) =>
+ public static IntPtr AllocateMemory(int sizeInBytes, int align = 16) =>
new(AllocateMemory((nuint) sizeInBytes, (uint) align));
+ /// Allocates a chunk of unmanaged memory.
+ /// The count of elements contained in the allocation.
+ /// The size, in bytes, of the elements in the allocation.
+ /// true if the allocated memory should be zeroed; otherwise, false.
+ /// The address to an allocated chunk of memory that is at least bytes in length.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void* AllocateArray(nuint count, nuint size, bool zero = false)
+ {
+#if NET6_0_OR_GREATER
+ void* result = NativeMemory.Alloc(count, size);
+
+#else
+ void* result = (void*)Marshal.AllocHGlobal(checked((int) (count * size)));
+#endif
+
+ if (result == null)
+ {
+ ThrowOutOfMemoryException(count, size);
+ }
+
+ if(zero)
+ {
+#if NET6_0_OR_GREATER
+ NativeMemory.Clear(result, count * size);
+#else
+ ClearMemory(result, count * size);
+#endif
+ }
+
+ return result;
+ }
+
+ /// Allocates a chunk of unmanaged memory.
+ /// The type used to compute the size, in bytes, of the elements in the allocation.
+ /// The count of elements contained in the allocation.
+ /// true if the allocated memory should be zeroed; otherwise, false.
+ /// The address to an allocated chunk of memory that is at least sizeof() bytes in length.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T* AllocateArray(nuint count, bool zero = false)
+ where T : unmanaged
+ {
+
+#if NET6_0_OR_GREATER
+ T* result = (T*)NativeMemory.Alloc(count, SizeOf());
+
+#else
+ T* result = (T*) Marshal.AllocHGlobal(checked((int) (count * SizeOf())));
+#endif
+
+ if (result == null)
+ {
+ ThrowOutOfMemoryException(count, SizeOf());
+ }
+
+ if (zero)
+ {
+#if NET6_0_OR_GREATER
+ NativeMemory.Clear(result, count * SizeOf());
+#else
+ ClearMemory(result, count * SizeOf());
+#endif
+ }
+
+ return result;
+ }
+
///
/// Free an aligned memory buffer.
///
@@ -48,11 +121,15 @@ public static unsafe IntPtr AllocateMemory(int sizeInBytes, int align = 16) =>
///
/// The buffer must have been allocated with .
///
- public static unsafe void FreeMemory(void* alignedBuffer)
+ public static void FreeMemory(void* alignedBuffer)
{
if (alignedBuffer == default) return;
+#if NET6_0_OR_GREATER
+ NativeMemory.AlignedFree(alignedBuffer);
+#else
Marshal.FreeHGlobal(((IntPtr*) alignedBuffer)[-1]);
+#endif
}
///
@@ -63,7 +140,7 @@ public static unsafe void FreeMemory(void* alignedBuffer)
/// The buffer must have been allocated with .
///
[MethodImpl(Utilities.MethodAggressiveOptimization)]
- public static unsafe void FreeMemory(UIntPtr alignedBuffer) => FreeMemory(alignedBuffer.ToPointer());
+ public static void FreeMemory(UIntPtr alignedBuffer) => FreeMemory(alignedBuffer.ToPointer());
///
/// Free an aligned memory buffer.
@@ -73,5 +150,17 @@ public static unsafe void FreeMemory(void* alignedBuffer)
/// The buffer must have been allocated with .
///
[MethodImpl(Utilities.MethodAggressiveOptimization)]
- public static unsafe void FreeMemory(IntPtr alignedBuffer) => FreeMemory(alignedBuffer.ToPointer());
+ public static void FreeMemory(IntPtr alignedBuffer) => FreeMemory(alignedBuffer.ToPointer());
+
+ [DoesNotReturn]
+ private static void ThrowOutOfMemoryException(ulong size)
+ {
+ throw new OutOfMemoryException($"The allocation of '{size}' bytes failed");
+ }
+
+ [DoesNotReturn]
+ public static void ThrowOutOfMemoryException(ulong count, ulong size)
+ {
+ throw new OutOfMemoryException($"The allocation of '{count}x{size}' bytes failed");
+ }
}
\ No newline at end of file
diff --git a/SharpGen.Runtime/MemoryHelpers.cs b/SharpGen.Runtime/MemoryHelpers.cs
index 9ec69d65..57544eac 100644
--- a/SharpGen.Runtime/MemoryHelpers.cs
+++ b/SharpGen.Runtime/MemoryHelpers.cs
@@ -18,16 +18,257 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
+#nullable enable
+
using System;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
namespace SharpGen.Runtime;
///
/// Utility class.
///
-public static partial class MemoryHelpers
+public static unsafe partial class MemoryHelpers
{
+#pragma warning disable CS8500
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint SizeOf() => unchecked((uint) sizeof(T));
+#pragma warning restore CS8500
+
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [return: NotNullIfNotNull(nameof(o))]
+ public static T? As(this object? o)
+ where T : class?
+ {
+ Debug.Assert(o is null or T);
+ return Unsafe.As(o);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref TTo As(ref TFrom source)
+ => ref Unsafe.As(ref source);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span As(this Span span)
+ where TFrom : unmanaged
+ where TTo : unmanaged
+ {
+ Debug.Assert(SizeOf() == SizeOf());
+
+#if NET6_0_OR_GREATER
+ return MemoryMarshal.CreateSpan(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length);
+#else
+ return new(Unsafe.AsPointer(ref Unsafe.As(ref MemoryMarshal.GetReference(span))), span.Length);
+#endif
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan As(this ReadOnlySpan span)
+ where TFrom : unmanaged
+ where TTo : unmanaged
+ {
+ Debug.Assert(SizeOf() == SizeOf());
+
+
+#if NET6_0_OR_GREATER
+ return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length);
+#else
+ return new(Unsafe.AsPointer(ref Unsafe.As(ref MemoryMarshal.GetReference(span))), span.Length);
+#endif
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T* AsPointer(in T source)
+ where T : unmanaged => (T*) Unsafe.AsPointer(ref Unsafe.AsRef(in source));
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref readonly TTo AsReadonly(in TFrom source)
+ => ref Unsafe.As(ref Unsafe.AsRef(in source));
+
+ /// Reinterprets the given native integer as a reference.
+ /// The type of the reference.
+ /// The native integer to reinterpret.
+ /// A reference to a value of type .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T AsRef(nint source) => ref Unsafe.AsRef((void*) source);
+
+ /// Reinterprets the given native unsigned integer as a reference.
+ /// The type of the reference.
+ /// The native unsigned integer to reinterpret.
+ /// A reference to a value of type .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T AsRef(nuint source) => ref Unsafe.AsRef((void*) source);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T AsRef(in T source) => ref Unsafe.AsRef(in source);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref TTo AsRef(in TFrom source) => ref Unsafe.As(ref Unsafe.AsRef(in source));
+
+ /// Reinterprets the readonly span as a writeable span.
+ /// The type of items in
+ /// The readonly span to reinterpret.
+ /// A writeable span that points to the same items as .
+ public static Span AsSpan(this ReadOnlySpan span)
+ {
+#if NET6_0_OR_GREATER
+ return MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in MemoryMarshal.GetReference(span)), span.Length);
+#else
+ return new(Unsafe.AsPointer(ref Unsafe.AsRef(in MemoryMarshal.GetReference(span))), span.Length);
+#endif
+ }
+
+ ///
+ public static Span Cast(this Span span)
+ where TFrom : struct
+ where TTo : struct => MemoryMarshal.Cast(span);
+
+ ///
+ public static ReadOnlySpan Cast(this ReadOnlySpan span)
+ where TFrom : struct
+ where TTo : struct => MemoryMarshal.Cast(span);
+
+ //
+ public static void CopyBlock(ref TDestination destination, in TSource source, uint byteCount) => Unsafe.CopyBlock(ref Unsafe.As(ref destination), ref Unsafe.As(ref Unsafe.AsRef(in source)), byteCount);
+
+ ///
+ public static void CopyBlockUnaligned(ref TDestination destination, in TSource source, uint byteCount) => Unsafe.CopyBlockUnaligned(ref Unsafe.As(ref destination), ref Unsafe.As(ref Unsafe.AsRef(in source)), byteCount);
+
+ ///
+ public static Span CreateSpan(scoped ref T reference, int length)
+ {
+#if NET6_0_OR_GREATER
+ return MemoryMarshal.CreateSpan(ref reference, length);
+#else
+ return new(Unsafe.AsPointer(ref reference), length);
+#endif
+ }
+
+ ///
+ public static ReadOnlySpan CreateReadOnlySpan(scoped in T reference, int length)
+ {
+#if NET6_0_OR_GREATER
+ return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in reference), length);
+#else
+ return new(Unsafe.AsPointer(ref Unsafe.AsRef(in reference)), length);
+#endif
+ }
+
+ /// Returns a pointer to the element of the span at index zero.
+ /// The type of items in .
+ /// The span from which the pointer is retrieved.
+ /// A pointer to the item at index zero of .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T* GetPointerUnsafe(this Span span)
+ where T : unmanaged => (T*) Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
+
+ /// Returns a pointer to the element of the span at index zero.
+ /// The type of items in .
+ /// The span from which the pointer is retrieved.
+ /// A pointer to the item at index zero of .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T* GetPointerUnsafe(this ReadOnlySpan span)
+ where T : unmanaged => (T*) Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T GetReferenceUnsafe(this T[] array) => ref GetArrayDataReference(array);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T GetReferenceUnsafe(this T[] array, int index) => ref Unsafe.Add(ref GetArrayDataReference(array), index);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T GetReferenceUnsafe(this T[] array, nuint index) => ref Unsafe.Add(ref GetArrayDataReference(array), index);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T GetReferenceUnsafe(this Span span) => ref MemoryMarshal.GetReference(span);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T GetReferenceUnsafe(this Span span, int index) => ref Unsafe.Add(ref MemoryMarshal.GetReference(span), index);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T GetReferenceUnsafe(this Span span, nuint index) => ref Unsafe.Add(ref MemoryMarshal.GetReference(span), index);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref readonly T GetReferenceUnsafe(this ReadOnlySpan span) => ref MemoryMarshal.GetReference(span);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref readonly T GetReferenceUnsafe(this ReadOnlySpan span, int index) => ref Unsafe.Add(ref MemoryMarshal.GetReference(span), index);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref readonly T GetReferenceUnsafe(this ReadOnlySpan span, nuint index) => ref Unsafe.Add(ref MemoryMarshal.GetReference(span), index);
+
+ /// Determines if a given reference to a value of type is not a null reference.
+ /// The type of the reference
+ /// The reference to check.
+ /// true if is not a null reference; otherwise, false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsNotNullRef(in T source) => !Unsafe.IsNullRef(ref Unsafe.AsRef(in source));
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsNullRef(in T source) => Unsafe.IsNullRef(ref Unsafe.AsRef(in source));
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T NullRef() => ref Unsafe.NullRef();
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T ReadUnaligned(void* source)
+ where T : unmanaged => Unsafe.ReadUnaligned(source);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T ReadUnaligned(void* source, nuint offset)
+ where T : unmanaged => Unsafe.ReadUnaligned((void*) ((nuint) source + offset));
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUnaligned(void* source, T value)
+ where T : unmanaged => Unsafe.WriteUnaligned(source, value);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUnaligned(void* source, nuint offset, T value)
+ where T : unmanaged => Unsafe.WriteUnaligned((void*) ((nuint) source + offset), value);
+
+ ///
+ /// Returns a reference to the 0th element of . If the array is empty, returns a reference
+ /// to where the 0th element would have been stored. Such a reference may be used for pinning but must never be dereferenced.
+ ///
+ /// is .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T GetArrayDataReference(T[] array)
+ {
+#if NET6_0_OR_GREATER
+ return ref MemoryMarshal.GetArrayDataReference(array);
+#else
+ return ref MemoryMarshal.GetReference(array.AsSpan());
+#endif
+ }
+
///
/// Native memcpy.
///
@@ -35,7 +276,7 @@ public static partial class MemoryHelpers
/// The source memory location.
/// The byte count.
[MethodImpl(Utilities.MethodAggressiveOptimization)]
- public static unsafe void CopyMemory(IntPtr dest, IntPtr src, int sizeInBytesToCopy) =>
+ public static void CopyMemory(IntPtr dest, IntPtr src, int sizeInBytesToCopy) =>
Unsafe.CopyBlockUnaligned((void*) dest, (void*) src, (uint) sizeInBytesToCopy);
///
@@ -45,7 +286,7 @@ public static unsafe void CopyMemory(IntPtr dest, IntPtr src, int sizeInBytesToC
/// The source memory location.
/// The byte count.
[MethodImpl(Utilities.MethodAggressiveOptimization)]
- public static unsafe void CopyMemory(IntPtr dest, IntPtr src, uint sizeInBytesToCopy) =>
+ public static void CopyMemory(IntPtr dest, IntPtr src, uint sizeInBytesToCopy) =>
Unsafe.CopyBlockUnaligned((void*) dest, (void*) src, sizeInBytesToCopy);
///
@@ -54,9 +295,18 @@ public static unsafe void CopyMemory(IntPtr dest, IntPtr src, uint sizeInBytesTo
/// The destination memory location.
/// The source memory location.
[MethodImpl(Utilities.MethodAggressiveOptimization)]
- public static unsafe void CopyMemory(IntPtr dest, ReadOnlySpan src) where T : struct =>
+ public static void CopyMemory(IntPtr dest, ReadOnlySpan src) where T : struct =>
src.CopyTo(new Span((void*) dest, src.Length));
+ ///
+ /// Native memcpy.
+ ///
+ /// The destination memory location.
+ /// The source memory location.
+ [MethodImpl(Utilities.MethodAggressiveOptimization)]
+ public static void CopyMemory(IntPtr dest, Span src) where T : struct =>
+ src.CopyTo(new Span(dest.ToPointer(), src.Length));
+
///
/// Native memcpy.
///
@@ -64,7 +314,7 @@ public static unsafe void CopyMemory(IntPtr dest, ReadOnlySpan src) where
/// The source memory location.
/// The byte count.
[MethodImpl(Utilities.MethodAggressiveOptimization)]
- public static unsafe void CopyMemory(void* dest, void* src, int sizeInBytesToCopy) =>
+ public static void CopyMemory(void* dest, void* src, int sizeInBytesToCopy) =>
Unsafe.CopyBlockUnaligned(dest, src, (uint) sizeInBytesToCopy);
///
@@ -74,7 +324,7 @@ public static unsafe void CopyMemory(void* dest, void* src, int sizeInBytesToCop
/// The source memory location.
/// The byte count.
[MethodImpl(Utilities.MethodAggressiveOptimization)]
- public static unsafe void CopyMemory(void* dest, void* src, uint sizeInBytesToCopy) =>
+ public static void CopyMemory(void* dest, void* src, uint sizeInBytesToCopy) =>
Unsafe.CopyBlockUnaligned(dest, src, sizeInBytesToCopy);
///
@@ -83,7 +333,7 @@ public static unsafe void CopyMemory(void* dest, void* src, uint sizeInBytesToCo
/// The destination memory location.
/// The source memory location.
[MethodImpl(Utilities.MethodAggressiveOptimization)]
- public static unsafe void CopyMemory(void* dest, ReadOnlySpan src) where T : struct =>
+ public static void CopyMemory(void* dest, ReadOnlySpan src) where T : struct =>
src.CopyTo(new Span(dest, src.Length));
///
@@ -93,7 +343,7 @@ public static unsafe void CopyMemory(void* dest, ReadOnlySpan src) where T
/// The value to initialize the block to.
/// The byte count.
[MethodImpl(Utilities.MethodAggressiveOptimization)]
- public static unsafe void ClearMemory(IntPtr dest, byte value, int sizeInBytesToClear) =>
+ public static void ClearMemory(IntPtr dest, byte value, int sizeInBytesToClear) =>
Unsafe.InitBlockUnaligned(ref *(byte*) dest, value, (uint) sizeInBytesToClear);
///
@@ -103,7 +353,7 @@ public static unsafe void ClearMemory(IntPtr dest, byte value, int sizeInBytesTo
/// The value to initialize the block to.
/// The byte count.
[MethodImpl(Utilities.MethodAggressiveOptimization)]
- public static unsafe void ClearMemory(IntPtr dest, byte value, uint sizeInBytesToClear) =>
+ public static void ClearMemory(IntPtr dest, byte value, uint sizeInBytesToClear) =>
Unsafe.InitBlockUnaligned(ref *(byte*) dest, value, sizeInBytesToClear);
///
@@ -124,6 +374,16 @@ public static void ClearMemory(IntPtr dest, int sizeInBytesToClear) =>
public static void ClearMemory(IntPtr dest, uint sizeInBytesToClear) =>
ClearMemory(dest, 0, sizeInBytesToClear);
+ ///
+ /// Clears the memory.
+ ///
+ /// The address of the start of the memory block to initialize.
+ /// The value to initialize the block to.
+ /// The byte count.
+ [MethodImpl(Utilities.MethodAggressiveOptimization)]
+ public static void ClearMemory(void* dest, nuint sizeInBytes) =>
+ Unsafe.InitBlockUnaligned(dest, 0, (uint) sizeInBytes);
+
///
/// Reads the specified array T[] data from a memory location.
///
@@ -143,7 +403,7 @@ public static IntPtr Read(IntPtr source, T[] data, int offset, int count) whe
/// The target data pointer.
/// The byte count to read from the memory location.
/// source pointer + sizeInBytes
- public static unsafe IntPtr Read(IntPtr source, void* data, int sizeInBytes)
+ public static IntPtr Read(IntPtr source, void* data, int sizeInBytes)
{
Unsafe.CopyBlockUnaligned(data, (void*) source, (uint) sizeInBytes);
return source + sizeInBytes;
@@ -170,7 +430,7 @@ public static unsafe IntPtr Write(IntPtr destination, void* data, int sizeInByte
/// The data span to write to.
/// The number of T element to read from the memory location.
/// source pointer + sizeof(T) * count
- public static unsafe IntPtr Read(IntPtr source, ReadOnlySpan data, int count) where T : unmanaged
+ public static IntPtr Read(IntPtr source, ReadOnlySpan data, int count) where T : unmanaged
{
fixed (void* dataPtr = data)
return Read(source, dataPtr, count * sizeof(T));
@@ -184,7 +444,7 @@ public static unsafe IntPtr Read(IntPtr source, ReadOnlySpan data, int cou
/// The span of T data to write.
/// The number of T element to write to the memory location.
/// destination pointer + sizeof(T) * count
- public static unsafe IntPtr Write(IntPtr destination, Span data, int count) where T : unmanaged
+ public static IntPtr Write(IntPtr destination, Span data, int count) where T : unmanaged
{
fixed (void* dataPtr = data)
return Write(destination, dataPtr, count * sizeof(T));
@@ -197,7 +457,7 @@ public static unsafe IntPtr Write(IntPtr destination, Span data, int count
/// Memory location to read from.
/// The T to read to.
/// source pointer + sizeof(T)
- public static unsafe IntPtr Read(IntPtr source, ref T data) where T : unmanaged
+ public static IntPtr Read(IntPtr source, ref T data) where T : unmanaged
{
fixed (void* dataPtr = &data)
return Read(source, dataPtr, sizeof(T));
@@ -209,7 +469,7 @@ public static unsafe IntPtr Read(IntPtr source, ref T data) where T : unmanag
/// Type of a data to read.
/// Memory location to read from.
/// The T value read from the pointer.
- public static unsafe T Read(IntPtr source) where T : unmanaged
+ public static T Read(IntPtr source) where T : unmanaged
{
T data = default;
Read(source, &data, sizeof(T));
@@ -223,7 +483,7 @@ public static unsafe T Read(IntPtr source) where T : unmanaged
/// Memory location to write to.
/// The data structure to write.
/// destination pointer + sizeof(T)
- public static unsafe IntPtr Write(IntPtr destination, ref T data) where T : unmanaged
+ public static IntPtr Write(IntPtr destination, ref T data) where T : unmanaged
{
fixed (void* dataPtr = &data)
return Write(destination, dataPtr, sizeof(T));
@@ -236,7 +496,7 @@ public static unsafe IntPtr Write(IntPtr destination, ref T data) where T : u
/// Memory location to write to.
/// The data structure to write.
/// destination pointer + sizeof(T)
- public static unsafe IntPtr Write(IntPtr destination, T data) where T : unmanaged =>
+ public static IntPtr Write(IntPtr destination, T data) where T : unmanaged =>
Write(destination, &data, sizeof(T));
///
@@ -246,8 +506,8 @@ public static unsafe IntPtr Write(IntPtr destination, T data) where T : unman
/// The align.
/// true if the specified memory pointer is aligned in memory; otherwise, false.
[MethodImpl(Utilities.MethodAggressiveOptimization)]
- public static bool IsMemoryAligned(nint memoryPtr, uint align = 16) =>
- (memoryPtr & (align - 1)) == 0;
+ public static bool IsMemoryAligned(nint memoryPtr, uint alignment = 16) =>
+ (memoryPtr & (alignment - 1)) == 0;
///
/// Determines whether the specified memory pointer is aligned in memory.
@@ -256,8 +516,8 @@ public static bool IsMemoryAligned(nint memoryPtr, uint align = 16) =>
/// The align.
/// true if the specified memory pointer is aligned in memory; otherwise, false.
[MethodImpl(Utilities.MethodAggressiveOptimization)]
- public static bool IsMemoryAligned(nuint memoryPtr, uint align = 16) =>
- (memoryPtr & (align - 1)) == 0;
+ public static bool IsMemoryAligned(nuint memoryPtr, uint alignment = 16) =>
+ (memoryPtr & (alignment - 1)) == 0;
///
/// Determines whether the specified memory pointer is aligned in memory.
@@ -266,8 +526,8 @@ public static bool IsMemoryAligned(nuint memoryPtr, uint align = 16) =>
/// The align.
/// true if the specified memory pointer is aligned in memory; otherwise, false.
[MethodImpl(Utilities.MethodAggressiveOptimization)]
- public static unsafe bool IsMemoryAligned(void* memoryPtr, uint align = 16) =>
- IsMemoryAligned((nuint) memoryPtr, align);
+ public static bool IsMemoryAligned(void* memoryPtr, uint alignment = 16) =>
+ IsMemoryAligned((nuint) memoryPtr, alignment);
#nullable enable
diff --git a/SharpGen.Runtime/NativeLong.cs b/SharpGen.Runtime/NativeLong.cs
index 50be12c5..8ad204ae 100644
--- a/SharpGen.Runtime/NativeLong.cs
+++ b/SharpGen.Runtime/NativeLong.cs
@@ -5,7 +5,7 @@ namespace SharpGen.Runtime;
[StructLayout(LayoutKind.Explicit)]
public readonly struct NativeLong : IEquatable, IComparable, IComparable
-#if NET5_0
+#if NET6_0_OR_GREATER
, IFormattable
#endif
{
@@ -66,8 +66,8 @@ public override string ToString() =>
public string ToString(string format) =>
UseInt ? _intValue.ToString(format) : _pointerValue.ToString(format);
-#if NET5_0
- public string ToString(IFormatProvider formatProvider) =>
+#if NET6_0_OR_GREATER
+ public string ToString(IFormatProvider formatProvider) =>
UseInt ? _intValue.ToString(formatProvider) : _pointerValue.ToString(formatProvider);
///
diff --git a/SharpGen.Runtime/NativeULong.cs b/SharpGen.Runtime/NativeULong.cs
index 2386949b..60e41af6 100644
--- a/SharpGen.Runtime/NativeULong.cs
+++ b/SharpGen.Runtime/NativeULong.cs
@@ -5,7 +5,7 @@ namespace SharpGen.Runtime;
[StructLayout(LayoutKind.Explicit)]
public readonly struct NativeULong : IEquatable, IComparable, IComparable
-#if NET5_0
+#if NET6_0_OR_GREATER
, IFormattable
#endif
{
@@ -13,7 +13,7 @@ namespace SharpGen.Runtime;
private readonly nuint _pointerValue;
[FieldOffset(0)]
private readonly uint _intValue;
-
+
private static readonly bool UseInt = NativeLong.UseInt;
public NativeULong(uint value)
@@ -59,27 +59,33 @@ public NativeULong(UIntPtr value)
}
///
- public override string ToString() =>
+ public override string ToString() =>
UseInt ? _intValue.ToString() : _pointerValue.ToString();
-#if NET5_0
- public string ToString(string format) =>
- UseInt ? _intValue.ToString(format) : _pointerValue.ToString(format);
+#if NET6_0_OR_GREATER
+ public string ToString(string format)
+ {
+ return UseInt ? _intValue.ToString(format) : _pointerValue.ToString(format);
+ }
- public string ToString(IFormatProvider formatProvider) =>
- UseInt ? _intValue.ToString(formatProvider) : _pointerValue.ToString(formatProvider);
+ public string ToString(IFormatProvider formatProvider)
+ {
+ return UseInt ? _intValue.ToString(formatProvider) : _pointerValue.ToString(formatProvider);
+ }
- ///
- public string ToString(string format, IFormatProvider formatProvider) =>
- UseInt ? _intValue.ToString(format, formatProvider) : _pointerValue.ToString(format, formatProvider);
+ ///
+ public string ToString(string format, IFormatProvider formatProvider)
+ {
+ return UseInt ? _intValue.ToString(format, formatProvider) : _pointerValue.ToString(format, formatProvider);
+ }
#endif
-
+
///
- public override int GetHashCode() =>
+ public override int GetHashCode() =>
UseInt ? _intValue.GetHashCode() : _pointerValue.GetHashCode();
///
- public bool Equals(NativeULong other) =>
+ public bool Equals(NativeULong other) =>
UseInt ? _intValue == other._intValue : _pointerValue.Equals(other._pointerValue);
///
@@ -172,7 +178,7 @@ public override bool Equals(object obj) =>
///
/// The value.
/// The result of the conversion.
- public static explicit operator uint(NativeULong value) =>
+ public static explicit operator uint(NativeULong value) =>
UseInt ? value._intValue : (uint) value._pointerValue;
///
@@ -180,7 +186,7 @@ public static explicit operator uint(NativeULong value) =>
///
/// The value.
/// The result of the conversion.
- public static implicit operator ulong(NativeULong value) =>
+ public static implicit operator ulong(NativeULong value) =>
UseInt ? value._intValue : (ulong) value._pointerValue;
///
@@ -188,7 +194,7 @@ public static implicit operator ulong(NativeULong value) =>
///
/// The value.
/// The result of the conversion.
- public static implicit operator UIntPtr(NativeULong value) =>
+ public static implicit operator UIntPtr(NativeULong value) =>
UseInt ? (UIntPtr) value._intValue : (UIntPtr) value._pointerValue;
///
@@ -211,7 +217,7 @@ public static implicit operator UIntPtr(NativeULong value) =>
/// The value.
/// The result of the conversion.
public static explicit operator NativeULong(UIntPtr value) => new NativeULong(value);
-
+
///
public int CompareTo(NativeULong other)
{
@@ -219,7 +225,7 @@ public int CompareTo(NativeULong other)
return _intValue.CompareTo(other._intValue);
#if !NET5_0
- return ((ulong)_pointerValue).CompareTo((ulong) other._pointerValue);
+ return ((ulong) _pointerValue).CompareTo((ulong) other._pointerValue);
#else
return _pointerValue.CompareTo(other._pointerValue);
#endif
diff --git a/SharpGen.Runtime/PointerSize.cs b/SharpGen.Runtime/PointerSize.cs
index 6f341390..5fd0b9ae 100644
--- a/SharpGen.Runtime/PointerSize.cs
+++ b/SharpGen.Runtime/PointerSize.cs
@@ -19,63 +19,80 @@
// THE SOFTWARE.
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Globalization;
+#nullable enable
+
namespace SharpGen.Runtime;
///
/// The maximum number of bytes to which a pointer can point. Use for a count that must span the full range of a pointer.
/// Equivalent to the native type size_t.
///
-public readonly struct PointerSize : IEquatable, IFormattable
+public readonly struct PointerSize : IEquatable,
+#if NET8_0_OR_GREATER
+ IComparable,
+#endif
+ IFormattable
{
- private readonly IntPtr _size;
+ public readonly IntPtr Value;
///
/// An empty pointer size initialized to zero.
///
public static readonly PointerSize Zero = new(0);
- public PointerSize(IntPtr size) => _size = size;
- private unsafe PointerSize(void* size) => _size = new IntPtr(size);
- public PointerSize(int size) => _size = new IntPtr(size);
- public PointerSize(long size) => _size = new IntPtr(size);
+ public PointerSize(IntPtr value) => Value = value;
+ private unsafe PointerSize(void* value) => Value = new IntPtr(value);
+ public PointerSize(int value) => Value = new IntPtr(value);
+ public PointerSize(long value) => Value = new IntPtr(value);
public override string ToString() => ToString(null, null);
- public string ToString(string format, IFormatProvider formatProvider) => string.Format(
+ public string ToString(string? format, IFormatProvider? formatProvider) => string.Format(
formatProvider ?? CultureInfo.CurrentCulture,
string.IsNullOrEmpty(format) ? "{0}" : "{0:" + format + "}",
- _size
+ Value
);
- public string ToString(string format) => ToString(format, null);
+ public string ToString(string? format) => ToString(format, null);
+
+ public override int GetHashCode() => Value.GetHashCode();
- public override int GetHashCode() => _size.GetHashCode();
+ public bool Equals(PointerSize other) => Value.Equals(other.Value);
- public bool Equals(PointerSize other) => _size.Equals(other._size);
+ public override bool Equals([NotNullWhen(true)] object? obj) => obj is PointerSize value && Equals(value);
- public override bool Equals(object value)
+#if NET8_0_OR_GREATER
+ public int CompareTo(object? obj)
{
- if (ReferenceEquals(null, value)) return false;
- return value is PointerSize size && Equals(size);
+ if (obj is PointerSize other)
+ {
+ return CompareTo(other);
+ }
+
+ return (obj is null) ? 1 : throw new ArgumentException("obj is not an instance of PointerSize.");
}
- public static PointerSize operator +(PointerSize left, PointerSize right) => new(left._size.ToInt64() + right._size.ToInt64());
+ public int CompareTo(PointerSize other) => Value.CompareTo(other.Value);
+#endif
+
+ public static PointerSize operator +(PointerSize left, PointerSize right) => new(left.Value.ToInt64() + right.Value.ToInt64());
public static PointerSize operator +(PointerSize value) => value;
- public static PointerSize operator -(PointerSize left, PointerSize right) => new(left._size.ToInt64() - right._size.ToInt64());
- public static PointerSize operator -(PointerSize value) => new(-value._size.ToInt64());
- public static PointerSize operator *(int scale, PointerSize value) => new(scale*value._size.ToInt64());
- public static PointerSize operator *(PointerSize value, int scale) => new(scale*value._size.ToInt64());
- public static PointerSize operator /(PointerSize value, int scale) => new(value._size.ToInt64()/scale);
+ public static PointerSize operator -(PointerSize left, PointerSize right) => new(left.Value.ToInt64() - right.Value.ToInt64());
+ public static PointerSize operator -(PointerSize value) => new(-value.Value.ToInt64());
+ public static PointerSize operator *(int scale, PointerSize value) => new(scale * value.Value.ToInt64());
+ public static PointerSize operator *(PointerSize value, int scale) => new(scale * value.Value.ToInt64());
+ public static PointerSize operator /(PointerSize value, int scale) => new(value.Value.ToInt64() / scale);
public static bool operator ==(PointerSize left, PointerSize right) => left.Equals(right);
public static bool operator !=(PointerSize left, PointerSize right) => !left.Equals(right);
- public static implicit operator int(PointerSize value) => value._size.ToInt32();
- public static implicit operator long(PointerSize value) => value._size.ToInt64();
+ public static implicit operator int(PointerSize value) => value.Value.ToInt32();
+ public static implicit operator long(PointerSize value) => value.Value.ToInt64();
public static implicit operator PointerSize(int value) => new(value);
public static implicit operator PointerSize(long value) => new(value);
public static implicit operator PointerSize(IntPtr value) => new(value);
- public static implicit operator IntPtr(PointerSize value) => value._size;
+ public static implicit operator IntPtr(PointerSize value) => value.Value;
public static unsafe implicit operator PointerSize(void* value) => new(value);
- public static unsafe implicit operator void*(PointerSize value) => (void*) value._size;
+ public static unsafe implicit operator void*(PointerSize value) => (void*) value.Value;
}
\ No newline at end of file
diff --git a/SharpGen.Runtime/PointerUSize.cs b/SharpGen.Runtime/PointerUSize.cs
new file mode 100644
index 00000000..12036e88
--- /dev/null
+++ b/SharpGen.Runtime/PointerUSize.cs
@@ -0,0 +1,97 @@
+// Copyright (c) 2010-2014 SharpDX - Alexandre Mutel
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+
+#nullable enable
+
+namespace SharpGen.Runtime;
+
+///
+/// The maximum number of bytes to which a pointer can point. Use for a count that must span the full range of a pointer.
+/// Equivalent to the native type size_t.
+///
+public readonly struct PointerUSize : IEquatable,
+#if NET8_0_OR_GREATER
+ IComparable,
+#endif
+ IFormattable
+{
+ public readonly UIntPtr Value;
+
+ ///
+ /// An empty pointer size initialized to zero.
+ ///
+ public static readonly PointerUSize Zero = new(0);
+
+ public PointerUSize(UIntPtr value) => Value = value;
+ private unsafe PointerUSize(void* value) => Value = new UIntPtr(value);
+ public PointerUSize(uint value) => Value = new UIntPtr(value);
+ public PointerUSize(ulong value) => Value = new UIntPtr(value);
+
+ public override string ToString() => ToString(null, null);
+
+ public string ToString(string? format, IFormatProvider? formatProvider) => string.Format(
+ formatProvider ?? CultureInfo.CurrentCulture,
+ string.IsNullOrEmpty(format) ? "{0}" : "{0:" + format + "}",
+ Value
+ );
+
+ public string ToString(string? format) => ToString(format, null);
+
+ public override int GetHashCode() => Value.GetHashCode();
+
+ public bool Equals(PointerUSize other) => Value.Equals(other.Value);
+
+ public override bool Equals([NotNullWhen(true)] object? obj) => obj is PointerUSize value && Equals(value);
+
+
+#if NET8_0_OR_GREATER
+ public int CompareTo(object? obj)
+ {
+ if (obj is PointerUSize other)
+ {
+ return CompareTo(other);
+ }
+
+ return (obj is null) ? 1 : throw new ArgumentException("obj is not an instance of PointerUSize.");
+ }
+
+ public int CompareTo(PointerUSize other) => Value.CompareTo(other.Value);
+#endif
+
+ public static PointerUSize operator +(PointerUSize left, PointerUSize right) => new(left.Value.ToUInt64() + right.Value.ToUInt64());
+ public static PointerUSize operator -(PointerUSize left, PointerUSize right) => new(left.Value.ToUInt64() - right.Value.ToUInt64());
+ public static PointerUSize operator *(uint scale, PointerUSize value) => new(scale*value.Value.ToUInt64());
+ public static PointerUSize operator *(PointerUSize value, uint scale) => new(scale*value.Value.ToUInt64());
+ public static PointerUSize operator /(PointerUSize value, uint scale) => new(value.Value.ToUInt64()/scale);
+ public static bool operator ==(PointerUSize left, PointerUSize right) => left.Equals(right);
+ public static bool operator !=(PointerUSize left, PointerUSize right) => !left.Equals(right);
+ public static implicit operator uint(PointerUSize value) => value.Value.ToUInt32();
+ public static implicit operator ulong(PointerUSize value) => value.Value.ToUInt64();
+ public static implicit operator PointerUSize(uint value) => new(value);
+ public static implicit operator PointerUSize(ulong value) => new(value);
+ public static implicit operator PointerUSize(UIntPtr value) => new(value);
+ public static implicit operator UIntPtr(PointerUSize value) => value.Value;
+ public static unsafe implicit operator PointerUSize(void* value) => new(value);
+ public static unsafe implicit operator void*(PointerUSize value) => (void*) value.Value;
+}
\ No newline at end of file
diff --git a/SharpGen.Runtime/Result.cs b/SharpGen.Runtime/Result.cs
index edb628b4..5c51c740 100644
--- a/SharpGen.Runtime/Result.cs
+++ b/SharpGen.Runtime/Result.cs
@@ -102,7 +102,8 @@ namespace SharpGen.Runtime;
public override bool Equals(object? obj) => obj is Result res && Equals(res);
public override int GetHashCode() => Code;
public override string ToString() => Code.ToString("X8");
- public string ToString(string format, IFormatProvider formatProvider) => Code.ToString(format, formatProvider);
+ ///
+ public string ToString(string? format, IFormatProvider? formatProvider) => Code.ToString(format, formatProvider);
public int CompareTo(Result other) => Code.CompareTo(other.Code);
diff --git a/SharpGen.Runtime/ShadowAttribute.cs b/SharpGen.Runtime/ShadowAttribute.cs
index 8e2f6e08..6e4fe2e7 100644
--- a/SharpGen.Runtime/ShadowAttribute.cs
+++ b/SharpGen.Runtime/ShadowAttribute.cs
@@ -20,6 +20,7 @@
using System;
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
@@ -34,13 +35,20 @@ public sealed class ShadowAttribute : Attribute
///
/// Type of the associated shadow
///
+#if NET6_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+#endif
public Type Type { get; }
///
/// Initializes a new instance of class.
///
/// Type of the associated shadow
- public ShadowAttribute(Type holder)
+ public ShadowAttribute(
+#if NET6_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+#endif
+ Type holder)
{
Type = holder ?? throw new ArgumentNullException(nameof(holder));
diff --git a/SharpGen.Runtime/SharpGen.Runtime.csproj b/SharpGen.Runtime/SharpGen.Runtime.csproj
index 8909b46c..8c174e33 100644
--- a/SharpGen.Runtime/SharpGen.Runtime.csproj
+++ b/SharpGen.Runtime/SharpGen.Runtime.csproj
@@ -1,43 +1,44 @@
-
-
+
+
+ netstandard2.0;netstandard2.1;net8.0;net9.0
+ Support classes for code generated by SharpGen.
+ true
+ false
+ true
+ true
+ true
+ preview
+ true
+
-
-
- net5.0;netcoreapp3.0;netstandard2.1;netcoreapp2.1;netstandard2.0;net471;net46;net45;netstandard1.3
- Support classes for code generated by SharpGen.
- true
- false
- true
- true
- preview
-
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+ <_Parameter1>SharpGen.Runtime.COM, PublicKey=$(SharpGenPublicKey)
+
+
-
-
-
diff --git a/SharpGen.Runtime/SharpGen.Runtime.props b/SharpGen.Runtime/SharpGen.Runtime.props
index 88e4c793..9a54ae7a 100644
--- a/SharpGen.Runtime/SharpGen.Runtime.props
+++ b/SharpGen.Runtime/SharpGen.Runtime.props
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/SharpGen.Runtime/Shim/FormattableString.cs b/SharpGen.Runtime/Shim/FormattableString.cs
deleted file mode 100644
index 8a91e031..00000000
--- a/SharpGen.Runtime/Shim/FormattableString.cs
+++ /dev/null
@@ -1,104 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#nullable enable
-
-/*============================================================
-**
-**
-**
-** Purpose: implementation of the FormattableString
-** class.
-**
-===========================================================*/
-
-namespace System;
-
-///
-/// A composite format string along with the arguments to be formatted. An instance of this
-/// type may result from the use of the C# or VB language primitive "interpolated string".
-///
-internal abstract class FormattableString : IFormattable
-{
- ///
- /// The composite format string.
- ///
- public abstract string Format { get; }
-
- ///
- /// Returns an object array that contains zero or more objects to format. Clients should not
- /// mutate the contents of the array.
- ///
- public abstract object?[] GetArguments();
-
- ///
- /// The number of arguments to be formatted.
- ///
- public abstract int ArgumentCount { get; }
-
- ///
- /// Returns one argument to be formatted from argument position .
- ///
- public abstract object? GetArgument(int index);
-
- ///
- /// Format to a string using the given culture.
- ///
- public abstract string ToString(IFormatProvider? formatProvider);
-
- string IFormattable.ToString(string? ignored, IFormatProvider? formatProvider)
- {
- return ToString(formatProvider);
- }
-
- ///
- /// Format the given object in the invariant culture. This static method may be
- /// imported in C# by
- ///
- /// using static System.FormattableString;
- /// .
- /// Within the scope
- /// of that import directive an interpolated string may be formatted in the
- /// invariant culture by writing, for example,
- ///
- /// Invariant($"{{ lat = {latitude}; lon = {longitude} }}")
- ///
- ///
- public static string Invariant(FormattableString formattable)
- {
- if (formattable == null)
- {
- throw new ArgumentNullException(nameof(formattable));
- }
-
- return formattable.ToString(Globalization.CultureInfo.InvariantCulture);
- }
-
- ///
- /// Format the given object in the current culture. This static method may be
- /// imported in C# by
- ///
- /// using static System.FormattableString;
- /// .
- /// Within the scope
- /// of that import directive an interpolated string may be formatted in the
- /// current culture by writing, for example,
- ///
- /// CurrentCulture($"{{ lat = {latitude}; lon = {longitude} }}")
- ///
- ///
- public static string CurrentCulture(FormattableString formattable)
- {
- if (formattable == null)
- {
- throw new ArgumentNullException(nameof(formattable));
- }
-
- return formattable.ToString(Globalization.CultureInfo.CurrentCulture);
- }
-
- public override string ToString()
- {
- return ToString(Globalization.CultureInfo.CurrentCulture);
- }
-}
\ No newline at end of file
diff --git a/SharpGen.Runtime/Shim/FormattableStringFactory.cs b/SharpGen.Runtime/Shim/FormattableStringFactory.cs
deleted file mode 100644
index 974fb978..00000000
--- a/SharpGen.Runtime/Shim/FormattableStringFactory.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#nullable enable
-
-/*============================================================
-**
-**
-**
-** Purpose: implementation of the FormattableStringFactory
-** class.
-**
-===========================================================*/
-
-namespace System.Runtime.CompilerServices;
-
-///
-/// A factory type used by compilers to create instances of the type .
-///
-internal static class FormattableStringFactory
-{
- ///
- /// Create a from a composite format string and object
- /// array containing zero or more objects to format.
- ///
- public static FormattableString Create(string format, params object?[] arguments)
- {
- if (format == null)
- {
- throw new ArgumentNullException(nameof(format));
- }
-
- if (arguments == null)
- {
- throw new ArgumentNullException(nameof(arguments));
- }
-
- return new ConcreteFormattableString(format, arguments);
- }
-
- private sealed class ConcreteFormattableString : FormattableString
- {
- private readonly string _format;
- private readonly object?[] _arguments;
-
- internal ConcreteFormattableString(string format, object?[] arguments)
- {
- _format = format;
- _arguments = arguments;
- }
-
- public override string Format => _format;
- public override object?[] GetArguments() { return _arguments; }
- public override int ArgumentCount => _arguments.Length;
- public override object? GetArgument(int index) { return _arguments[index]; }
- public override string ToString(IFormatProvider? formatProvider) { return string.Format(formatProvider, _format, _arguments); }
- }
-}
\ No newline at end of file
diff --git a/SharpGen.Runtime/Shim/ReferenceEqualityComparer.cs b/SharpGen.Runtime/Shim/ReferenceEqualityComparer.cs
new file mode 100644
index 00000000..7215373a
--- /dev/null
+++ b/SharpGen.Runtime/Shim/ReferenceEqualityComparer.cs
@@ -0,0 +1,59 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+using System.Runtime.CompilerServices;
+
+namespace System.Collections.Generic;
+
+///
+/// An that uses reference equality ()
+/// instead of value equality () when comparing two object instances.
+///
+///
+/// The type cannot be instantiated. Instead, use the property
+/// to access the singleton instance of this type.
+///
+internal sealed class ReferenceEqualityComparer : IEqualityComparer