From 00c57cf6b399bdc068115c370dd621c51c6cbd84 Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Mon, 25 Apr 2022 09:51:39 +0200 Subject: [PATCH 01/55] Update azure-pipelines.yml to operate on main branch instead of master. --- azure-pipelines.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f8dae52c..18d189f7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,12 +1,12 @@ trigger: branches: include: - - master + - main tags: include: - v*.* pr: -- master +- main variables: buildNumber: $[counter(variables['build.reason'], 1000)] @@ -81,7 +81,7 @@ stages: pathToPublish: '$(Build.ArtifactStagingDirectory)' artifactName: NuGet Packages - stage: Deploy - condition: or(eq(variables['Build.SourceBranchName'], 'master'), startsWith(variables['Build.SourceBranch'], 'refs/tags/v')) + condition: or(eq(variables['Build.SourceBranchName'], 'main'), startsWith(variables['Build.SourceBranch'], 'refs/tags/v')) dependsOn: Build jobs: - job: MyGet_Deploy From dc9e43d002946d708373ebf5b8a86a0fdd8059af Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Mon, 25 Apr 2022 09:56:10 +0200 Subject: [PATCH 02/55] Update README.md to show CI-build status from main branch. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 40f42d62..9033fa6d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SharpGenTools -[![Build Status](https://dev.azure.com/SharpGenTools/SharpGenTools/_apis/build/status/SharpGenTools?branchName=master)](https://dev.azure.com/SharpGenTools/SharpGenTools/_build/latest?definitionId=1&branchName=master) [![MyGet Pre Release](https://img.shields.io/myget/sharpgentools/vpre/SharpGenTools.Sdk.svg)](https://www.myget.org/feed/Packages/sharpgentools) [![NuGet](https://img.shields.io/nuget/v/SharpGenTools.Sdk.svg)](https://www.nuget.org/packages/SharpGenTools.Sdk) [![Docs](https://readthedocs.org/projects/sharpgentools/badge/?version=latest)](https://sharpgentools.readthedocs.io/en/latest/) [![codecov](https://codecov.io/gh/SharpGenTools/SharpGenTools/branch/master/graph/badge.svg)](https://codecov.io/gh/SharpGenTools/SharpGenTools) [![CodeFactor](https://www.codefactor.io/repository/github/sharpgentools/sharpgentools/badge)](https://www.codefactor.io/repository/github/sharpgentools/sharpgentools) +[![Build Status](https://dev.azure.com/SharpGenTools/SharpGenTools/_apis/build/status/SharpGenTools?branchName=main)](https://dev.azure.com/SharpGenTools/SharpGenTools/_build/latest?definitionId=1&branchName=main) [![MyGet Pre Release](https://img.shields.io/myget/sharpgentools/vpre/SharpGenTools.Sdk.svg)](https://www.myget.org/feed/Packages/sharpgentools) [![NuGet](https://img.shields.io/nuget/v/SharpGenTools.Sdk.svg)](https://www.nuget.org/packages/SharpGenTools.Sdk) [![Docs](https://readthedocs.org/projects/sharpgentools/badge/?version=latest)](https://sharpgentools.readthedocs.io/en/latest/) [![codecov](https://codecov.io/gh/SharpGenTools/SharpGenTools/branch/main/graph/badge.svg)](https://codecov.io/gh/SharpGenTools/SharpGenTools) [![CodeFactor](https://www.codefactor.io/repository/github/sharpgentools/sharpgentools/badge)](https://www.codefactor.io/repository/github/sharpgentools/sharpgentools) Accurate and high performance C++ interop code generator for C#. From 7dfac6e0af80431d6bd7917c60527ff41b6b4e48 Mon Sep 17 00:00:00 2001 From: Andrew Boyarshin Date: Sat, 23 Apr 2022 13:54:10 +0700 Subject: [PATCH 03/55] Fix file-scoped namespaces --- SharpGen.UnitTests/Runtime/ICallback.cs | 85 ++++++++++++------------- SharpGen.UnitTests/Runtime/VtblTests.cs | 25 ++++---- SharpGen/Config/SdkRule.cs | 81 ++++++++++++----------- 3 files changed, 94 insertions(+), 97 deletions(-) diff --git a/SharpGen.UnitTests/Runtime/ICallback.cs b/SharpGen.UnitTests/Runtime/ICallback.cs index ff597331..cee80546 100644 --- a/SharpGen.UnitTests/Runtime/ICallback.cs +++ b/SharpGen.UnitTests/Runtime/ICallback.cs @@ -2,59 +2,58 @@ using System.Runtime.InteropServices; using SharpGen.Runtime; -namespace SharpGen.UnitTests.Runtime +namespace SharpGen.UnitTests.Runtime; + +[Vtbl(typeof(CallbackVtbl))] +interface ICallback : ICallbackable { - [Vtbl(typeof(CallbackVtbl))] - interface ICallback : ICallbackable - { - int Increment(int param); - } + int Increment(int param); +} - class CallbackImpl : CallbackBase, ICallback - { - public int Increment(int param) => param + 1; - } +class CallbackImpl : CallbackBase, ICallback +{ + public int Increment(int param) => param + 1; +} + +public static class CallbackVtbl +{ + private static readonly IncrementDelegate Increment = IncrementImpl; - public static class CallbackVtbl + public static readonly IntPtr[] Vtbl = { - private static readonly IncrementDelegate Increment = IncrementImpl; + Marshal.GetFunctionPointerForDelegate(Increment) + }; - public static readonly IntPtr[] Vtbl = - { - Marshal.GetFunctionPointerForDelegate(Increment) - }; + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int IncrementDelegate(IntPtr thisObj, int param); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int IncrementDelegate(IntPtr thisObj, int param); + private static int IncrementImpl(IntPtr thisObj, int param) => + CppObjectShadow.ToCallback(thisObj).Increment(param); +} - private static int IncrementImpl(IntPtr thisObj, int param) => - CppObjectShadow.ToCallback(thisObj).Increment(param); - } +[Vtbl(typeof(Callback2Vtbl))] +interface ICallback2: ICallback +{ + int Decrement(int param); +} - [Vtbl(typeof(Callback2Vtbl))] - interface ICallback2: ICallback - { - int Decrement(int param); - } +public static class Callback2Vtbl +{ + private static readonly DecrementDelegate Decrement = DecrementImpl; - public static class Callback2Vtbl + public static IntPtr[] Fill() => new[] { - private static readonly DecrementDelegate Decrement = DecrementImpl; - - public static IntPtr[] Fill() => new[] - { - Marshal.GetFunctionPointerForDelegate(Decrement) - }; + Marshal.GetFunctionPointerForDelegate(Decrement) + }; - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int DecrementDelegate(IntPtr thisObj, int param); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int DecrementDelegate(IntPtr thisObj, int param); - private static int DecrementImpl(IntPtr thisObj, int param) => - CppObjectShadow.ToCallback(thisObj).Increment(param); - } - - class Callback2Impl : CallbackImpl, ICallback, ICallback2 - { - public int Decrement(int param) => param - 1; - } + private static int DecrementImpl(IntPtr thisObj, int param) => + CppObjectShadow.ToCallback(thisObj).Increment(param); } + +class Callback2Impl : CallbackImpl, ICallback, ICallback2 +{ + public int Decrement(int param) => param - 1; +} \ No newline at end of file diff --git a/SharpGen.UnitTests/Runtime/VtblTests.cs b/SharpGen.UnitTests/Runtime/VtblTests.cs index 4bca413e..7d13839c 100644 --- a/SharpGen.UnitTests/Runtime/VtblTests.cs +++ b/SharpGen.UnitTests/Runtime/VtblTests.cs @@ -3,21 +3,20 @@ using SharpGen.Runtime; using Xunit; -namespace SharpGen.UnitTests.Runtime +namespace SharpGen.UnitTests.Runtime; + +public class VtblTests { - public class VtblTests + [Fact] + public void CanRoundTripCallThroughNativeVtblToManagedObject() { - [Fact] - public void CanRoundTripCallThroughNativeVtblToManagedObject() - { - using var callback = new CallbackImpl(); + using var callback = new CallbackImpl(); - var callbackPtr = MarshallingHelpers.ToCallbackPtr(callback); - Assert.NotEqual(IntPtr.Zero, callbackPtr); + var callbackPtr = MarshallingHelpers.ToCallbackPtr(callback); + Assert.NotEqual(IntPtr.Zero, callbackPtr); - var methodPtr = Marshal.ReadIntPtr(Marshal.ReadIntPtr(callbackPtr)); - var delegateObject = Marshal.GetDelegateForFunctionPointer(methodPtr); - Assert.Equal(3, delegateObject(callbackPtr, 2)); - } + var methodPtr = Marshal.ReadIntPtr(Marshal.ReadIntPtr(callbackPtr)); + var delegateObject = Marshal.GetDelegateForFunctionPointer(methodPtr); + Assert.Equal(3, delegateObject(callbackPtr, 2)); } -} +} \ No newline at end of file diff --git a/SharpGen/Config/SdkRule.cs b/SharpGen/Config/SdkRule.cs index b8f252f1..b2232683 100644 --- a/SharpGen/Config/SdkRule.cs +++ b/SharpGen/Config/SdkRule.cs @@ -1,58 +1,57 @@ using System; using System.Xml.Serialization; -namespace SharpGen.Config +namespace SharpGen.Config; + +public enum SdkLib { - public enum SdkLib + StdLib = 1, + WindowsSdk +} + +public static class SdkLibExtensions +{ + public static string Name(this SdkLib lib) => lib switch { - StdLib = 1, - WindowsSdk - } + SdkLib.StdLib => "standard library", + SdkLib.WindowsSdk => "Windows SDK", + _ => lib.ToString() + }; +} - public static class SdkLibExtensions +public class SdkRule +{ + public SdkRule() { - public static string Name(this SdkLib lib) => lib switch - { - SdkLib.StdLib => "standard library", - SdkLib.WindowsSdk => "Windows SDK", - _ => lib.ToString() - }; } - public class SdkRule + public SdkRule(SdkLib name, string version) { - public SdkRule() - { - } - - public SdkRule(SdkLib name, string version) - { - Name = name; - Version = version; - } + Name = name; + Version = version; + } - [XmlIgnore] - public SdkLib? Name { get; private set; } + [XmlIgnore] + public SdkLib? Name { get; private set; } - [XmlAttribute("name")] - public string _Name_ + [XmlAttribute("name")] + public string _Name_ + { + get => Name.ToString(); + set { - get => Name.ToString(); - set - { - if (Enum.TryParse(value, out SdkLib name)) - Name = name; - else - Name = null; - } + if (Enum.TryParse(value, out SdkLib name)) + Name = name; + else + Name = null; } + } - public bool ShouldSerialize_Name_() => Name is { } name && Enum.IsDefined(typeof(SdkLib), name); + public bool ShouldSerialize_Name_() => Name is { } name && Enum.IsDefined(typeof(SdkLib), name); - [XmlAttribute("version")] - public string Version { get; set; } + [XmlAttribute("version")] + public string Version { get; set; } - [XmlAttribute("components")] - public string Components { get; set; } - } -} + [XmlAttribute("components")] + public string Components { get; set; } +} \ No newline at end of file From 6afbf46a6393f3098bddbe32c24e614346be2ca6 Mon Sep 17 00:00:00 2001 From: Andrew Boyarshin Date: Sat, 23 Apr 2022 22:10:27 +0700 Subject: [PATCH 04/55] Fix shadows in the new system --- SharpGen.Runtime/CallbackBase.Reflection.cs | 222 +++++++++++++----- .../CallbackBase.ReflectionCache.cs | 29 +++ .../CallbackBase.ReflectionImpl.cs | 74 ++++++ SharpGen.Runtime/CallbackBase.cs | 122 +++++----- SharpGen.Runtime/CppObjectCallableWrapper.cs | 46 ++++ SharpGen.Runtime/CppObjectMultiShadow.cs | 81 +++++++ SharpGen.Runtime/CppObjectShadow.cs | 72 ++---- SharpGen.Runtime/SharpGen.Runtime.csproj | 4 + .../Shim/ReferenceEqualityComparer.cs | 59 +++++ .../TypeDataRegistrationHelper.cs | 17 +- SharpGen.Runtime/TypeDataStorage.cs | 127 +++++++++- SharpGen.UnitTests/Runtime/ICallback.cs | 2 +- .../Runtime/TestTypeRegistry.cs | 2 +- 13 files changed, 672 insertions(+), 185 deletions(-) create mode 100644 SharpGen.Runtime/CallbackBase.ReflectionCache.cs create mode 100644 SharpGen.Runtime/CallbackBase.ReflectionImpl.cs create mode 100644 SharpGen.Runtime/CppObjectCallableWrapper.cs create mode 100644 SharpGen.Runtime/CppObjectMultiShadow.cs create mode 100644 SharpGen.Runtime/Shim/ReferenceEqualityComparer.cs diff --git a/SharpGen.Runtime/CallbackBase.Reflection.cs b/SharpGen.Runtime/CallbackBase.Reflection.cs index 8e8f8158..9bf2493b 100644 --- a/SharpGen.Runtime/CallbackBase.Reflection.cs +++ b/SharpGen.Runtime/CallbackBase.Reflection.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.Collections.Generic; using System.Linq; @@ -7,105 +9,195 @@ 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(); + public readonly TypeInfo Type; + public readonly List ImplementedInterfaces; - // Associate all shadows with their interfaces. - foreach (var item in GetUninheritedShadowedInterfaces(type)) + public ImmediateShadowInterfaceInfo(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(); -#endif + // Cache reflection on interface inheritance + private class CallbackTypeInfo + { + private readonly TypeInfo type; + private ImmediateShadowInterfaceInfo[]? _vtbls; + private TypeInfo[]? _shadows; + private Guid[]? _guids; - return guids.Where(guid => guidSet.Add(guid)).ToArray(); - } + public CallbackTypeInfo(Type type) : this(type.GetTypeInfo()) + { + } + + private CallbackTypeInfo(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.GetTypeInfo(); - public ImmediateShadowInterfaceInfo(TypeInfo type) + // 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(); + } + + 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..5cbeecb4 --- /dev/null +++ b/SharpGen.Runtime/CallbackBase.ReflectionCache.cs @@ -0,0 +1,29 @@ +#nullable enable + +using System; +using System.Collections.Generic; + +namespace SharpGen.Runtime; + +public abstract partial class CallbackBase +{ + private static readonly Dictionary TypeReflectionCache = new(); + + private CallbackTypeInfo GetTypeInfo() + { + CallbackTypeInfo info; + var type = GetType(); + 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..bfe9b6b1 --- /dev/null +++ b/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs @@ -0,0 +1,74 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics; +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(TypeInfo type) + { + var shadow = (CppObjectShadow) Activator.CreateInstance(type.AsType()); + + // Initialize the shadow with the callback + shadow.Initialize(ThisHandle); + + return GCHandle.Alloc(shadow, GCHandleType.Normal); + } + + 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..e362ca5a 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,9 +37,9 @@ namespace SharpGen.Runtime; /// public abstract unsafe partial class CallbackBase : DisposeBase, ICallbackable { - private Dictionary _ccw; + private Dictionary? _ccw; private IntPtr _guidPtr; - private IntPtr[] _guids; + private IntPtr[]? _guids; #if NET5_0_OR_GREATER private uint _refCount = 1; #else @@ -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..00d40992 100644 --- a/SharpGen.Runtime/CppObjectShadow.cs +++ b/SharpGen.Runtime/CppObjectShadow.cs @@ -18,8 +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.Linq; using System.Runtime.InteropServices; namespace SharpGen.Runtime; @@ -36,8 +40,8 @@ public abstract unsafe class CppObjectShadow static CppObjectShadow() { - Debug.Assert(Marshal.SizeOf(typeof(CppObjectNative)) == CppObjectNative.Size); - Debug.Assert(sizeof(CppObjectNative) == CppObjectNative.Size); + Debug.Assert(Marshal.SizeOf(typeof(CppObjectCallableWrapper)) == CppObjectCallableWrapper.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/SharpGen.Runtime.csproj b/SharpGen.Runtime/SharpGen.Runtime.csproj index 8909b46c..530e8e9a 100644 --- a/SharpGen.Runtime/SharpGen.Runtime.csproj +++ b/SharpGen.Runtime/SharpGen.Runtime.csproj @@ -40,4 +40,8 @@ + + + + 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, IEqualityComparer +{ + private ReferenceEqualityComparer() { } + + /// + /// Gets the singleton instance. + /// + public static ReferenceEqualityComparer Instance { get; } = new(); + + /// + /// Determines whether two object references refer to the same object instance. + /// + /// The first object to compare. + /// The second object to compare. + /// + /// if both and refer to the same object instance + /// or if both are ; otherwise, . + /// + /// + /// This API is a wrapper around . + /// It is not necessarily equivalent to calling . + /// + public new bool Equals(object? x, object? y) => ReferenceEquals(x, y); + + /// + /// Returns a hash code for the specified object. The returned hash code is based on the object + /// identity, not on the contents of the object. + /// + /// The object for which to retrieve the hash code. + /// A hash code for the identity of . + /// + /// This API is a wrapper around . + /// It is not necessarily equivalent to calling . + /// + public int GetHashCode(object? obj) + { + // Depending on target framework, RuntimeHelpers.GetHashCode might not be annotated + // with the proper nullability attribute. We'll suppress any warning that might + // result. + return RuntimeHelpers.GetHashCode(obj!); + } +} \ No newline at end of file diff --git a/SharpGen.Runtime/TypeDataRegistrationHelper.cs b/SharpGen.Runtime/TypeDataRegistrationHelper.cs index 30b62176..8f56332c 100644 --- a/SharpGen.Runtime/TypeDataRegistrationHelper.cs +++ b/SharpGen.Runtime/TypeDataRegistrationHelper.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Reflection; namespace SharpGen.Runtime; @@ -19,7 +20,7 @@ private void InitializeIfNeeded() _size = 0; } - public void Add() where T : ICallbackable => Add(TypeDataStorage.Storage.SourceVtbl); + public void Add() where T : ICallbackable => Add(TypeDataStorage.GetSourceVtbl()); public void Add(IntPtr[] vtbl) { @@ -31,7 +32,16 @@ public void Add(IntPtr[] vtbl) _size += (uint) vtbl.Length; } - public void Register() where T : ICallbackable + public void Register() where T : ICallbackable => TypeDataStorage.Register(RegisterImpl()); + + internal IntPtr Register(TypeInfo type) + { + var vtbl = RegisterImpl(); + TypeDataStorage.Register(type.GUID, vtbl); + return new IntPtr(vtbl); + } + + private void** RegisterImpl() { InitializeIfNeeded(); @@ -46,8 +56,9 @@ public void Register() where T : ICallbackable offset += length; } - TypeDataStorage.Register(nativePointer); pointers.Clear(); _size = 0; + + return nativePointer; } } \ No newline at end of file diff --git a/SharpGen.Runtime/TypeDataStorage.cs b/SharpGen.Runtime/TypeDataStorage.cs index 836afc0b..7860d5e6 100644 --- a/SharpGen.Runtime/TypeDataStorage.cs +++ b/SharpGen.Runtime/TypeDataStorage.cs @@ -1,7 +1,13 @@ +// #define FORCE_REFLECTION_ONLY + +#nullable enable + using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; @@ -32,6 +38,7 @@ static TypeDataStorage() [MethodImpl(Utilities.MethodAggressiveOptimization)] internal static Guid GetGuid() where T : ICallbackable { +#if !FORCE_REFLECTION_ONLY ref var guid = ref Storage.Guid; if (guid != default) @@ -41,29 +48,133 @@ internal static Guid GetGuid() where T : ICallbackable return guid = typeof(T).GetTypeInfo().GUID; #else return guid = typeof(T).GUID; +#endif +#else +#if NETSTANDARD1_3 + return typeof(T).GetTypeInfo().GUID; +#else + return typeof(T).GUID; +#endif #endif } internal static void Register(void* vtbl) where T : ICallbackable { - Debug.Assert(Storage.TargetVtbl is null); - Storage.TargetVtbl = vtbl; - vtblByGuid[GetGuid()] = new IntPtr(vtbl); +#if !FORCE_REFLECTION_ONLY + Register(GetGuid(), vtbl); +#endif + } + + internal static void Register(Guid guid, void* vtbl) => vtblByGuid[guid] = new IntPtr(vtbl); + + internal static IntPtr[] GetSourceVtbl() where T : ICallbackable + { +#if !FORCE_REFLECTION_ONLY + if (Storage.SourceVtbl is { } storedVtbl) + return storedVtbl; +#endif + + return GetSourceVtblFromReflection(typeof(T)); + } + + private static IntPtr[] GetSourceVtblFromReflection(Type type) + { + const string vtbl = "Vtbl"; + + var vtblAttribute = VtblAttribute.Get(type); + Debug.Assert(vtblAttribute is not null, $"Type {type.FullName} has no Vtbl attribute"); + + var vtblType = vtblAttribute.Type; + +#if NETSTANDARD1_3 + static bool Predicate(MemberInfo x) => x.Name == vtbl; + + if (vtblType.GetRuntimeFields().FirstOrDefault(Predicate)?.GetValue(null) is IntPtr[] vtblFieldValue) + { + return vtblFieldValue; + } + + if (vtblType.GetRuntimeProperties().FirstOrDefault(Predicate)?.GetValue(null) is IntPtr[] vtblPropertyValue) + { + return vtblPropertyValue; + } +#else + const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Static; + + if (vtblType.GetField(vtbl, flags)?.GetValue(null) is IntPtr[] vtblFieldValue) + { + return vtblFieldValue; + } + + if (vtblType.GetProperty(vtbl, flags)?.GetValue(null) is IntPtr[] vtblPropertyValue) + { + return vtblPropertyValue; + } +#endif + + Debug.Fail($"Type {type.FullName} has no Vtbl field or property"); + + return null; + } + + internal static bool GetTargetVtbl(TypeInfo type, out void* pointer) + { +#if !FORCE_REFLECTION_ONLY + if (vtblByGuid.TryGetValue(type.GUID, out var ptr)) + { + pointer = ptr.ToPointer(); + return true; + } +#endif + + if (GetSourceVtblFromReflection(type.AsType()) is { } sourceVtbl) + { + pointer = RegisterFromReflection(type, sourceVtbl).ToPointer(); + return true; + } + + pointer = default; + return false; } - internal static bool GetVtbl(Guid guid, out void* pointer) + private static IntPtr RegisterFromReflection(TypeInfo type, IntPtr[] sourceVtbl) { - var result = vtblByGuid.TryGetValue(guid, out var ptr); - pointer = ptr.ToPointer(); - return result; + var callbackable = typeof(ICallbackable).GetTypeInfo(); + + TypeDataRegistrationHelper helper = new(); + List items = new(); + + foreach (var iface in type.ImplementedInterfaces) + { + var typeInfo = iface.GetTypeInfo(); + if (callbackable == typeInfo || !callbackable.IsAssignableFrom(typeInfo)) + continue; + + var iSourceVtbl = GetSourceVtblFromReflection(iface); + if (iSourceVtbl is null) + throw new Exception($"Failed to reflect Vtbl out of an {nameof(ICallbackable)} interface '{iface.FullName}'."); + + items.Add(new RegisterInheritanceItem(typeInfo, typeInfo.ImplementedInterfaces.Count(), iSourceVtbl)); + } + + // TODO: properly sort items by inheritance + // TODO: verify single inheritance + items.Sort(static (x, y) => Comparer.Default.Compare(x.InterfaceCount, y.InterfaceCount)); + + foreach (var (_, _, iSourceVtbl) in items) + helper.Add(iSourceVtbl); + + helper.Add(sourceVtbl); + return helper.Register(type); } + private record struct RegisterInheritanceItem(TypeInfo Type, int InterfaceCount, IntPtr[] SourceVtbl); + [SuppressMessage("ReSharper", "StaticMemberInGenericType")] [SuppressMessage("Usage", "CA2211:Non-constant fields should not be visible")] public static class Storage where T : ICallbackable { public static Guid Guid; public static IntPtr[] SourceVtbl; - public static void* TargetVtbl; } } \ No newline at end of file diff --git a/SharpGen.UnitTests/Runtime/ICallback.cs b/SharpGen.UnitTests/Runtime/ICallback.cs index cee80546..b69a9b59 100644 --- a/SharpGen.UnitTests/Runtime/ICallback.cs +++ b/SharpGen.UnitTests/Runtime/ICallback.cs @@ -41,7 +41,7 @@ public static class Callback2Vtbl { private static readonly DecrementDelegate Decrement = DecrementImpl; - public static IntPtr[] Fill() => new[] + public static IntPtr[] Vtbl { get; } = { Marshal.GetFunctionPointerForDelegate(Decrement) }; diff --git a/SharpGen.UnitTests/Runtime/TestTypeRegistry.cs b/SharpGen.UnitTests/Runtime/TestTypeRegistry.cs index e0038e78..b797020c 100644 --- a/SharpGen.UnitTests/Runtime/TestTypeRegistry.cs +++ b/SharpGen.UnitTests/Runtime/TestTypeRegistry.cs @@ -9,7 +9,7 @@ internal static class TestTypeRegistry internal static void Initialize() { TypeDataStorage.Storage.SourceVtbl = CallbackVtbl.Vtbl; - TypeDataStorage.Storage.SourceVtbl = Callback2Vtbl.Fill(); + TypeDataStorage.Storage.SourceVtbl = Callback2Vtbl.Vtbl; TypeDataRegistrationHelper helper = new(); { helper.Add(); From 913aed5b1e5eb6138652d3bcc26d0f014155571b Mon Sep 17 00:00:00 2001 From: Andrew Boyarshin Date: Tue, 26 Apr 2022 22:59:08 +0700 Subject: [PATCH 05/55] Fix minor misspelling --- SharpGen/Config/ConfigFile.cs | 4 ++-- SharpGen/Logging/LoggingCodes.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SharpGen/Config/ConfigFile.cs b/SharpGen/Config/ConfigFile.cs index 6c34f476..cc6a5f8d 100644 --- a/SharpGen/Config/ConfigFile.cs +++ b/SharpGen/Config/ConfigFile.cs @@ -297,7 +297,7 @@ public string ExpandString(string str, bool expandDynamicVariable, Logger logger var localResult = GetVariable(name, logger) ?? Environment.GetEnvironmentVariable(name); if (localResult is not null) return localResult; - logger.Error(LoggingCodes.UnkownVariable, "Unable to substitute config/environment variable $({0}). Variable is not defined", name); + logger.Error(LoggingCodes.UnknownVariable, "Unable to substitute config/environment variable $({0}). Variable is not defined", name); return string.Empty; } ); @@ -314,7 +314,7 @@ public string ExpandString(string str, bool expandDynamicVariable, Logger logger if (GetRoot().DynamicVariables.TryGetValue(name, out var localResult)) return localResult.Trim('"'); - logger.Error(LoggingCodes.UnkownDynamicVariable, + logger.Error(LoggingCodes.UnknownDynamicVariable, "Unable to substitute dynamic variable #({0}). Variable is not defined", name); return string.Empty; } diff --git a/SharpGen/Logging/LoggingCodes.cs b/SharpGen/Logging/LoggingCodes.cs index 06ff6166..c1f5f766 100644 --- a/SharpGen/Logging/LoggingCodes.cs +++ b/SharpGen/Logging/LoggingCodes.cs @@ -18,9 +18,9 @@ public static class LoggingCodes public const string RegistryKeyNotFound = "SG0007"; - public const string UnkownVariable = "SG0008"; + public const string UnknownVariable = "SG0008"; - public const string UnkownDynamicVariable = "SG0009"; + public const string UnknownDynamicVariable = "SG0009"; public const string InvalidMethodReturnType = "SG0010"; From e2d7b6694c9155079b7e34f6968bb7756d3d0197 Mon Sep 17 00:00:00 2001 From: Andrew Boyarshin Date: Tue, 26 Apr 2022 23:01:05 +0700 Subject: [PATCH 06/55] Simplify caching by using "profiles" stored in project 'obj' directory --- .../SharpGenModuleGenerator.GenerateModule.cs | 2 +- SharpGen.Runtime/SharpGen.Runtime.props | 2 +- SharpGen/Generator/RoslynGenerator.cs | 100 ++---- .../CallerArgumentExpressionAttribute.cs | 15 + SharpGenTools.Sdk/Internal/Utilities.cs | 26 ++ SharpGenTools.Sdk/Sdk.props | 12 +- SharpGenTools.Sdk/Sdk.targets | 238 +++++--------- SharpGenTools.Sdk/SharpGenTools.Sdk.csproj | 4 + .../Tasks/SharpGenTask.PropertyCache.cs | 267 ++++++++++++++++ SharpGenTools.Sdk/Tasks/SharpGenTask.cs | 291 ++++++++++++++---- SharpGenTools.Sdk/Tasks/SharpGenTaskBase.cs | 25 -- .../Tasks/SharpPropertyCacheTask.cs | 143 --------- SharpGenTools.Sdk/Tasks/SharpTaskBase.cs | 51 --- 13 files changed, 651 insertions(+), 525 deletions(-) create mode 100644 SharpGenTools.Sdk/CallerArgumentExpressionAttribute.cs create mode 100644 SharpGenTools.Sdk/Tasks/SharpGenTask.PropertyCache.cs delete mode 100644 SharpGenTools.Sdk/Tasks/SharpGenTaskBase.cs delete mode 100644 SharpGenTools.Sdk/Tasks/SharpPropertyCacheTask.cs delete mode 100644 SharpGenTools.Sdk/Tasks/SharpTaskBase.cs diff --git a/SharpGen.Generator/SharpGenModuleGenerator.GenerateModule.cs b/SharpGen.Generator/SharpGenModuleGenerator.GenerateModule.cs index dcc0bcc2..be4e62c9 100644 --- a/SharpGen.Generator/SharpGenModuleGenerator.GenerateModule.cs +++ b/SharpGen.Generator/SharpGenModuleGenerator.GenerateModule.cs @@ -193,7 +193,7 @@ localVtbl is not null ); context.AddSource( - "SharpGen.g.cs", + "SharpGen.Module.g.cs", SourceText.From(GenerateCompilationUnit(clazz).ToString(), Encoding.UTF8) ); } 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/Generator/RoslynGenerator.cs b/SharpGen/Generator/RoslynGenerator.cs index b51d8af8..ebb2d9f6 100644 --- a/SharpGen/Generator/RoslynGenerator.cs +++ b/SharpGen/Generator/RoslynGenerator.cs @@ -18,93 +18,43 @@ public sealed class RoslynGenerator SingletonSeparatedList(Attribute(ParseName("System.Runtime.CompilerServices.ModuleInitializerAttribute"))) ); - public void Run(CsAssembly csAssembly, string generatedCodeFolder, Ioc ioc) + public string Run(CsAssembly csAssembly, Ioc ioc) { - if (string.IsNullOrEmpty(generatedCodeFolder)) - throw new ArgumentException("Value cannot be null or empty.", nameof(generatedCodeFolder)); - var logger = ioc.Logger; var generators = ioc.Generators; - var directoryToCreate = new HashSet(StringComparer.CurrentCulture); - - // Remove the generated directory before creating it - if (!directoryToCreate.Contains(generatedCodeFolder)) - { - directoryToCreate.Add(generatedCodeFolder); - if (Directory.Exists(generatedCodeFolder)) - { - foreach (var oldGeneratedFile in Directory.EnumerateFiles(generatedCodeFolder, "*.cs", SearchOption.AllDirectories)) - { - try - { - File.Delete(oldGeneratedFile); - } - catch - { - // ignored - } - } - } - } - - if (!Directory.Exists(generatedCodeFolder)) - Directory.CreateDirectory(generatedCodeFolder); - - logger.Message("Process Assembly => {0}", generatedCodeFolder); - - List trees = new() - { - CreateTree("Enumerations", ns => ns.Enums, generators.Enum), - CreateTree("Structures", ns => ns.Structs, generators.Struct), - CreateTree("Functions", ns => ns.Classes, generators.Group), - CreateTree("Interfaces", ns => ns.Interfaces, generators.Interface) - }; + logger.Message("Generating Roslyn syntax tree..."); var resultConstants = csAssembly.Namespaces .SelectMany(x => x.EnumerateDescendants(withAdditionalItems: false)) .ToArray(); - if (resultConstants.Length > 0) - trees.Add( - CSharpSyntaxTree.Create( - CompilationUnit( - default, - default, - default, - SingletonList( - ClassDeclaration("ModuleDataInitializer") - .WithModifiers(ModuleInitModifiers) - .AddMembers(GenerateResultDescriptor(resultConstants, ioc)) - ) - ).NormalizeWhitespace(elasticTrivia: true), - path: FormatFilePath("ModuleData") - ) - ); - SyntaxTree CreateTree(string fileName, Func> membersFunc, - IMemberCodeGenerator generator) where T : CsBase - { - MemberDeclarationSyntax NamespaceSelector(CsNamespace ns) - { - MemberSyntaxList list = new(ioc); - list.AddRange(membersFunc(ns).OrderBy(element => element.Name), generator); - return NamespaceDeclaration(ParseName(ns.Name), default, default, List(list)) - .WithLeadingTrivia(Comment(AutoGeneratedCommentText)); - } + var moduleInitializer = resultConstants.Length > 0 + ? new[] + { + ClassDeclaration("ModuleDataInitializer") + .WithModifiers(ModuleInitModifiers) + .AddMembers(GenerateResultDescriptor(resultConstants, ioc)) + } + : Enumerable.Empty(); - return CSharpSyntaxTree.Create( - CompilationUnit( - default, default, default, - List(csAssembly.Namespaces.Select(NamespaceSelector)) - ).NormalizeWhitespace(elasticTrivia: true), - path: FormatFilePath(fileName) - ); + MemberDeclarationSyntax NamespaceSelector(CsNamespace ns) + { + MemberSyntaxList list = new(ioc); + list.AddRange(ns.Enums.OrderBy(element => element.Name), generators.Enum); + list.AddRange(ns.Structs.OrderBy(element => element.Name), generators.Struct); + list.AddRange(ns.Classes.OrderBy(element => element.Name), generators.Group); + list.AddRange(ns.Interfaces.OrderBy(element => element.Name), generators.Interface); + return NamespaceDeclaration(ParseName(ns.Name), default, default, List(list)) + .WithLeadingTrivia(Comment(AutoGeneratedCommentText)); } - string FormatFilePath(string fileName) => Path.Combine(generatedCodeFolder, $"{fileName}.cs"); - - foreach (var tree in trees) - File.WriteAllText(tree.FilePath, tree.GetCompilationUnitRoot().ToFullString()); + return CSharpSyntaxTree.Create( + CompilationUnit( + default, default, default, + List(csAssembly.Namespaces.Select(NamespaceSelector)).AddRange(moduleInitializer) + ).NormalizeWhitespace(elasticTrivia: true) + ).GetCompilationUnitRoot().ToFullString(); } private MethodDeclarationSyntax GenerateResultDescriptor(CsResultConstant[] descriptors, Ioc ioc) diff --git a/SharpGenTools.Sdk/CallerArgumentExpressionAttribute.cs b/SharpGenTools.Sdk/CallerArgumentExpressionAttribute.cs new file mode 100644 index 00000000..a0f26543 --- /dev/null +++ b/SharpGenTools.Sdk/CallerArgumentExpressionAttribute.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.CompilerServices; + +[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] +public sealed class CallerArgumentExpressionAttribute : Attribute +{ + public CallerArgumentExpressionAttribute(string parameterName) + { + ParameterName = parameterName; + } + + public string ParameterName { get; } +} \ No newline at end of file diff --git a/SharpGenTools.Sdk/Internal/Utilities.cs b/SharpGenTools.Sdk/Internal/Utilities.cs index cbccb5e2..6c7592f5 100644 --- a/SharpGenTools.Sdk/Internal/Utilities.cs +++ b/SharpGenTools.Sdk/Internal/Utilities.cs @@ -91,4 +91,30 @@ internal static Stream OpenRead(string fullPath) throw new IOException(e.Message, e); } } + + private static string FixFilePath(string path) + { + return string.IsNullOrEmpty(path) || Path.DirectorySeparatorChar == '\\' ? path : path.Replace('\\', '/'); + } + + internal static string EnsureTrailingSlash(string fileSpec) + { + fileSpec = FixFilePath(fileSpec); + if (fileSpec.Length > 0 && !IsSlash(fileSpec[fileSpec.Length - 1])) + { + fileSpec += Path.DirectorySeparatorChar; + } + + return fileSpec; + } + + /// + /// Indicates if the given character is a slash. + /// + /// + /// true, if slash + private static bool IsSlash(char c) + { + return (c == Path.DirectorySeparatorChar) || (c == Path.AltDirectorySeparatorChar); + } } \ No newline at end of file diff --git a/SharpGenTools.Sdk/Sdk.props b/SharpGenTools.Sdk/Sdk.props index 3ca2414d..861b8526 100644 --- a/SharpGenTools.Sdk/Sdk.props +++ b/SharpGenTools.Sdk/Sdk.props @@ -1,7 +1,8 @@  - true + $(BeforePack);SharpGenGenerateConsumerBindMappingFile;SharpGenGenerateConsumerProps + SharpGenGenerateConsumerBindMappingFile;SharpGenGenerateConsumerProps @@ -18,12 +19,11 @@ - - - - + + + + - \ No newline at end of file diff --git a/SharpGenTools.Sdk/Sdk.targets b/SharpGenTools.Sdk/Sdk.targets index 3993bf44..10fc433f 100644 --- a/SharpGenTools.Sdk/Sdk.targets +++ b/SharpGenTools.Sdk/Sdk.targets @@ -13,34 +13,33 @@ - + - + - $([MSBuild]::NormalizeDirectory('$(IntermediateOutputPath)', 'SharpGen')) + $([MSBuild]::NormalizeDirectory('$(BaseIntermediateOutputPath)')) - $([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)', '$(IntermediateOutputPath)', 'SharpGen')) + $([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)', '$(BaseIntermediateOutputPath)')) - + - $([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)', '$(SharpGenIntermediateDir)')) + $([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)', '$(SharpGenIntermediateOutputDirectory)')) - + - $([MSBuild]::EnsureTrailingSlash('$(SharpGenIntermediateDir)')) + $([MSBuild]::EnsureTrailingSlash('$(SharpGenIntermediateOutputDirectory)')) - $([MSBuild]::NormalizeDirectory('$(SharpGenIntermediateDir)', 'Generated')) $(AssemblyName) $([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)', '..', 'tools')) @@ -85,17 +84,6 @@ - - - - - - - - - - - @@ -108,11 +96,13 @@ - - + - - $(TargetsForTfmSpecificContentInPackage);GenerateConsumerBindMappingFile;GenerateTfmSpecificConsumerProps - - - - - build/$(TargetFramework);buildMultiTargeting/$(TargetFramework) - true - - - build/$(TargetFramework);buildMultiTargeting/$(TargetFramework) - true - - - + + + + + - + Condition="'$(SharpGenGenerateConsumerBindMapping)' != 'false'"> - - - - - + + + build/$(PackageId).props;buildMultiTargeting/$(PackageId).props + true + - - - - - - - - - - - - - - - - - - - + - - - - <_SharpGenMappingNonexistent Include="@(SharpGenMapping)" Condition="!Exists('%(Identity)')" /> <_SharpGenConsumerMappingNonexistent Include="@(SharpGenConsumerMapping)" Condition="!Exists('%(Identity)')" /> @@ -296,33 +188,51 @@ <_SharpGenConsumerMappingNonexistent Remove="@(_SharpGenConsumerMappingNonexistent)" /> - + + <_SharpGenConsumerMapping Remove="@(_SharpGenConsumerMapping)" /> + <_SharpGenConsumerMapping Include="@(SharpGenConsumerMapping->Distinct())" /> + + + <_SharpGenConsumerMapping Remove="@(_SharpGenConsumerMapping)" /> + + + + + + + + + + + build;buildMultiTargeting + true + + - + DependsOnTargets="GenerateSharpGenBindings" + Condition="'$(SharpGenGenerateConsumerBindMapping)' != 'false' and '@(SharpGenMapping)' != ''" /> - + - + diff --git a/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj b/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj index 6da3093a..20fbb5a3 100644 --- a/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj +++ b/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj @@ -31,6 +31,10 @@ + + + + diff --git a/SharpGenTools.Sdk/Tasks/SharpGenTask.PropertyCache.cs b/SharpGenTools.Sdk/Tasks/SharpGenTask.PropertyCache.cs new file mode 100644 index 00000000..95acf1d6 --- /dev/null +++ b/SharpGenTools.Sdk/Tasks/SharpGenTask.PropertyCache.cs @@ -0,0 +1,267 @@ +#nullable enable + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using Microsoft.Build.Framework; +using SharpGenTools.Sdk.Internal; + +namespace SharpGenTools.Sdk.Tasks; + +public sealed partial class SharpGenTask +{ + private const int CacheFormatSignature = ('S' << 0) | ('G' << 8) | ('P' << 16) | ('C' << 24); + private const int CacheFormatVersion = 1; + private static readonly Encoding TextEncoding = Encoding.UTF8; + + private static HashAlgorithm CreateSettingsHash() => SHA256.Create(); + + private bool GeneratePropertyCache() + { + List parts = new(4) + { + "SharpGen" + }; + + if (!string.Equals(PlatformName, "AnyCPU", StringComparison.InvariantCultureIgnoreCase)) + parts.Add(PlatformName); + + if (!string.IsNullOrWhiteSpace(RuntimeIdentifier)) + parts.Add(RuntimeIdentifier); + + using MemoryStream stream = new(); + var currentHash = HashSettings(stream); + + parts.Add(currentHash); + + var profileName = string.Join("-", parts); + + workerLock = new Mutex(false, @"Global\" + profileName); + var writeNeeded = false; + + try + { + while (!(workerLockAcquired = workerLock.WaitOne(TimeSpan.FromSeconds(5))) && !AbortExecution) + { + SharpGenLogger.Message($"Waiting for the worker to finish job {profileName}…"); + } + } + catch (AbandonedMutexException) + { + SharpGenLogger.Message($"The worker failed to complete job {profileName}."); + workerLockAcquired = true; + writeNeeded = true; + } + + ProfilePath = Path.Combine(IntermediateOutputDirectory, profileName); + + if (!workerLockAcquired) + { + SharpGenLogger.Message($"Aborting the wait for job {profileName} to complete."); + Debug.Assert(AbortExecution); + return true; + } + + var cacheItemSpec = PropertyCache; + + Utilities.RequireAbsolutePath(cacheItemSpec, nameof(PropertyCache)); + + if (File.Exists(DirtyMarkerFile)) + { + SharpGenLogger.Message("Dirty marker exists, requesting full regeneration."); + writeNeeded = true; + } + else if (File.Exists(cacheItemSpec)) + { + ReadOnlySpan cachedHash = File.ReadAllBytes(cacheItemSpec); + + stream.Position = 0; + var success = stream.TryGetBuffer(out var buffer); + Debug.Assert(success); + + if (buffer.AsSpan().SequenceEqual(cachedHash)) + { + SharpGenLogger.Message("Properties match cached value."); + } + else + { + SharpGenLogger.Message("Properties mismatch, writing a new property cache file."); + writeNeeded = true; + } + } + else + { + SharpGenLogger.Message("Properties cache doesn't exist."); + writeNeeded = true; + } + + if (writeNeeded) + { + stream.Position = 0; + Directory.CreateDirectory(ProfilePath); + File.WriteAllBytes(DirtyMarkerFile, Array.Empty()); + using var file = File.Open(cacheItemSpec, FileMode.Create, FileAccess.Write); + stream.CopyTo(file); + } + + return writeNeeded; + } + + private string HashSettings(Stream stream) + { + { + using var writer = new StreamWriter(stream, TextEncoding, -1, true); + + writer.Write(CacheFormatSignature.ToString("X8")); + writer.Write('.'); + writer.Write(CacheFormatVersion); + writer.WriteLine(); + + WriteStringArray(CastXmlArguments); + WriteString(CastXmlExecutable); + WriteStringArray(ConfigFiles); + WriteString(ConsumerBindMappingConfigId); + WriteBool(DocumentationFailuresAsErrors); + WriteStringArray(ExtensionAssemblies); + WriteStringArray(ExternalDocumentation); + WriteTaskItems(GlobalNamespaceOverrides); + WriteStringArray(Macros); + WriteString(IntermediateOutputDirectory); + WriteString(PlatformName); + WriteString(RuntimeIdentifier); + WriteStringArray(Platforms); + WriteStringArray(SilenceMissingDocumentationErrorIdentifierPatterns); + + void WriteString(string? s, [CallerArgumentExpression("s")] string? name = null) + { + writer.Write(name); + writer.Write(": "); + writer.WriteLine(s ?? ""); + } + + void WriteBool(bool v, [CallerArgumentExpression("v")] string? name = null) + { + writer.Write(name); + writer.Write(": "); + writer.WriteLine(v); + } + + void WriteStringArray(IReadOnlyList? items, [CallerArgumentExpression("items")] string? name = null) + { + writer.Write(name); + writer.Write(":"); + + if (items is null) + { + writer.WriteLine(" "); + return; + } + + writer.WriteLine(); + + for (int i = 0, length = items.Count; i < length; i++) + { + writer.Write("* "); + writer.WriteLine(items[i]); + } + } + + void WriteTaskItem(ITaskItem? item, [CallerArgumentExpression("item")] string? name = null) + { + writer.Write(name); + writer.Write(": "); + + if (item is null) + { + writer.WriteLine(""); + return; + } + + writer.WriteLine(item.ItemSpec); + + foreach (DictionaryEntry entry in item.CloneCustomMetadata()) + { + writer.Write(name); + writer.Write("["); + writer.Write(entry.Key.ToString() ?? ""); + writer.Write("]: "); + writer.WriteLine(entry.Value?.ToString() ?? ""); + } + } + + void WriteTaskItems(IReadOnlyList? items, [CallerArgumentExpression("items")] string? name = null) + { + writer.Write(name); + writer.Write(":"); + + if (items is null) + { + writer.WriteLine(" "); + return; + } + + writer.WriteLine(); + + for (int i = 0, length = items.Count; i < length; i++) + { + WriteTaskItem(items[i], $"{name}[{i}]"); + } + } + } + + stream.Position = 0; + + using var hash = CreateSettingsHash(); + return Base64UrlEncode(hash.ComputeHash(stream)); + } + + private static string Base64UrlEncode(byte[] input) + { + if (input == null) + throw new ArgumentNullException(nameof(input)); + + // Special-case empty input + var count = input.Length; + if (count == 0) + return string.Empty; + + var numWholeOrPartialInputBlocks = checked(count + 2) / 3; + var buffer = new char[checked(numWholeOrPartialInputBlocks * 4)]; + var numBase64Chars = Base64UrlEncode(input, buffer, count); + + return new string(buffer, 0, numBase64Chars); + } + + private static int Base64UrlEncode(byte[] input, char[] output, int count) + { + // Use base64url encoding with no padding characters. See RFC 4648, Sec. 5. + + // Start with default Base64 encoding. + var numBase64Chars = Convert.ToBase64CharArray(input, 0, count, output, 0); + + // Fix up '+' -> '-' and '/' -> '_'. Drop padding characters. + for (var i = 0; i < numBase64Chars; i++) + { + switch (output[i]) + { + case '+': + output[i] = '-'; + break; + case '/': + output[i] = '_'; + break; + case '=': + // We've reached a padding character; truncate the remainder. + return i; + } + } + + return numBase64Chars; + } +} \ No newline at end of file diff --git a/SharpGenTools.Sdk/Tasks/SharpGenTask.cs b/SharpGenTools.Sdk/Tasks/SharpGenTask.cs index 9dbe76ce..93146ab1 100644 --- a/SharpGenTools.Sdk/Tasks/SharpGenTask.cs +++ b/SharpGenTools.Sdk/Tasks/SharpGenTask.cs @@ -1,10 +1,16 @@ +#nullable enable + using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; +using System.Threading; using System.Xml; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; using SharpGen; using SharpGen.Config; using SharpGen.CppModel; @@ -18,49 +24,153 @@ using SharpGenTools.Sdk.Documentation; using SharpGenTools.Sdk.Extensibility; using SharpGenTools.Sdk.Internal; +using Logger = SharpGen.Logging.Logger; +using SdkResolver = SharpGen.Parser.SdkResolver; namespace SharpGenTools.Sdk.Tasks; -public sealed class SharpGenTask : SharpGenTaskBase +public sealed partial class SharpGenTask : Task, ICancelableTask { // Default encoding used by MSBuild ReadLinesFromFile task private static readonly Encoding DefaultEncoding = new UTF8Encoding(false, true); private readonly IocServiceContainer serviceContainer = new(); private readonly Ioc ioc = new(); + private string? profilePath; + private volatile bool isCancellationRequested; + private Mutex? workerLock; + private bool workerLockAcquired; + + // ReSharper disable MemberCanBePrivate.Global, UnusedAutoPropertyAccessor.Global + [Required] public string[]? CastXmlArguments { get; set; } + [Required] public string? CastXmlExecutable { get; set; } + [Required] public string[]? ConfigFiles { get; set; } + [Required] public string? ConsumerBindMappingConfigId { get; set; } + [Required] public bool DebugWaitForDebuggerAttach { get; set; } + [Required] public bool DocumentationFailuresAsErrors { get; set; } + [Required] public string[]? ExtensionAssemblies { get; set; } + [Required] public string[]? ExternalDocumentation { get; set; } + [Required] public ITaskItem[]? GlobalNamespaceOverrides { get; set; } + [Required] public string[]? Macros { get; set; } + [Required] public string? IntermediateOutputDirectory { get; set; } + public string? PlatformName { get; set; } + [Required] public string[]? Platforms { get; set; } + + [Output] + public string ProfilePath + { + get => profilePath ?? throw new InvalidOperationException("Profile not set"); + set + { + if (profilePath is not null) + throw new InvalidOperationException("Profile cannot be set twice"); + profilePath = Utilities.EnsureTrailingSlash(value ?? throw new InvalidOperationException("Profile cannot be null")); + } + } + + public string? RuntimeIdentifier { get; set; } + [Required] public string[]? SilenceMissingDocumentationErrorIdentifierPatterns { get; set; } + // ReSharper restore UnusedAutoPropertyAccessor.Global, MemberCanBePrivate.Global + + private string GeneratedCodeFile => GetProfileChild("SharpGen.Bindings.g.cs"); + private string InputsCache => GetProfileChild("InputsCache.txt"); + private string PropertyCache => GetProfileChild("PropertyCache.txt"); + private string DocumentationCache => GetProfileChild("DocumentationCache.json"); + private string DirtyMarkerFile => GetProfileChild("dirty"); + private string PackagePropsFile => GetProfileChild("Package.props"); + + private string ConsumerBindMappingConfig => GetProfileChild( + (ConsumerBindMappingConfigId ?? throw new InvalidOperationException(nameof(ConsumerBindMappingConfigId))) + + ".BindMapping.xml" + ); + + private string GetProfileChild(string childName) => Path.Combine(ProfilePath, childName); + + private Logger SharpGenLogger { get; set; } + + [Conditional("DEBUG")] + private void WaitForDebuggerAttach() + { + if (!Debugger.IsAttached) + { + SharpGenLogger.Warning(null, $"{GetType().Name} is waiting for attach: {Process.GetCurrentProcess().Id}"); + Thread.Yield(); + } + + while (!Debugger.IsAttached && !isCancellationRequested) + Thread.Sleep(TimeSpan.FromSeconds(1)); + } + + public void Cancel() => isCancellationRequested = true; public override bool Execute() { - PrepareExecute(); + BindingRedirectResolution.Enable(); - serviceContainer.AddService(SharpGenLogger); - serviceContainer.AddService(); - serviceContainer.AddService(); - serviceContainer.AddService(new TypeRegistry(ioc)); - ioc.ConfigureServices(serviceContainer); + SharpGenLogger = new Logger(new MSBuildSharpGenLogger(Log)); - ExtensibilityDriver.Instance.LoadExtensions(SharpGenLogger, - ExtensionAssemblies.Select(x => x.ItemSpec).ToArray()); +#if DEBUG + if (DebugWaitForDebuggerAttach) + WaitForDebuggerAttach(); +#endif - var config = new ConfigFile - { - Files = ConfigFiles.Select(file => file.ItemSpec).ToList(), - Id = "SharpGen-MSBuild" - }; + var success = false; try { - config = LoadConfig(config); + if (!GeneratePropertyCache()) + return success = true; + + if (AbortExecution) + return success = false; + + serviceContainer.AddService(SharpGenLogger); + serviceContainer.AddService(); + serviceContainer.AddService(); + serviceContainer.AddService(new TypeRegistry(ioc)); + ioc.ConfigureServices(serviceContainer); - return !SharpGenLogger.HasErrors && Execute(config); + ExtensibilityDriver.Instance.LoadExtensions(SharpGenLogger, ExtensionAssemblies.ToArray()); + + ConfigFile config = new() + { + Files = ConfigFiles.ToList(), + Id = "SharpGen-MSBuild" + }; + + LoadConfig(config); + + return success = !AbortExecution && Execute(config); } catch (CodeGenFailedException ex) { SharpGenLogger.Fatal("Internal SharpGen exception", ex); - return false; + return success = false; + } + finally + { + Debug.Assert(workerLock != null, nameof(workerLock) + " != null"); + + try + { + GeneratePackagesProps(); + } + catch (Exception e) + { + SharpGenLogger.Fatal("Internal SharpGen exception while generating NuGet package properties file", e); + } + + if (success && !AbortExecution && File.Exists(DirtyMarkerFile)) + File.Delete(DirtyMarkerFile); + + if (workerLockAcquired) + workerLock.ReleaseMutex(); + + workerLock.Dispose(); + workerLock = null; } } - private bool AbortExecution => SharpGenLogger.HasErrors || IsCancellationRequested; + private bool AbortExecution => SharpGenLogger.HasErrors || isCancellationRequested; private bool Execute(ConfigFile config) { @@ -69,7 +179,7 @@ private bool Execute(ConfigFile config) out var configsWithExtensionHeaders ); - CppHeaderGenerator cppHeaderGenerator = new(OutputPath, ioc); + CppHeaderGenerator cppHeaderGenerator = new(ProfilePath, ioc); var cppHeaderGenerationResult = cppHeaderGenerator.GenerateCppHeaders(config, configsWithHeaders, configsWithExtensionHeaders); @@ -79,9 +189,9 @@ out var configsWithExtensionHeaders IncludeDirectoryResolver resolver = new(ioc); resolver.Configure(config); - CastXmlRunner castXml = new(resolver, CastXmlExecutable.ItemSpec, CastXmlArguments, ioc) + CastXmlRunner castXml = new(resolver, CastXmlExecutable, CastXmlArguments, ioc) { - OutputPath = OutputPath + OutputPath = ProfilePath }; var macroManager = new MacroManager(castXml); @@ -90,16 +200,16 @@ out var configsWithExtensionHeaders var module = config.CreateSkeletonModule(); - macroManager.Parse(Path.Combine(OutputPath, config.HeaderFileName), module); + macroManager.Parse(Path.Combine(ProfilePath, config.HeaderFileName), module); cppExtensionGenerator.GenerateExtensionHeaders( - config, OutputPath, module, configsWithExtensionHeaders, cppHeaderGenerationResult.UpdatedConfigs + config, ProfilePath, module, configsWithExtensionHeaders, cppHeaderGenerationResult.UpdatedConfigs ); GenerateInputsCache( macroManager.IncludedFiles .Concat(config.ConfigFilesLoaded.Select(x => x.AbsoluteFilePath)) - .Concat(configsWithExtensionHeaders.Select(x => Path.Combine(OutputPath, x.ExtensionFileName))) + .Concat(configsWithExtensionHeaders.Select(x => Path.Combine(ProfilePath, x.ExtensionFileName))) .Select(s => Utilities.FixFilePath(s, Utilities.EmptyFilePathBehavior.Ignore)) .Where(x => x != null) .Distinct() @@ -111,7 +221,7 @@ out var configsWithExtensionHeaders // Run the parser var parser = new CppParser(config, ioc) { - OutputPath = OutputPath + OutputPath = ProfilePath }; if (AbortExecution) @@ -199,7 +309,7 @@ out var configsWithExtensionHeaders if (AbortExecution) return false; - var documentationCacheItemSpec = DocumentationCache.ItemSpec; + var documentationCacheItemSpec = DocumentationCache; Utilities.RequireAbsolutePath(documentationCacheItemSpec, nameof(DocumentationCache)); @@ -223,7 +333,7 @@ out var configsWithExtensionHeaders silencePatterns = new Regex[SilenceMissingDocumentationErrorIdentifierPatterns.Length]; for (var i = 0; i < silencePatterns.Length; i++) silencePatterns[i] = new Regex( - SilenceMissingDocumentationErrorIdentifierPatterns[i].ItemSpec, + SilenceMissingDocumentationErrorIdentifierPatterns[i], RegexOptions.CultureInvariant ); } @@ -244,35 +354,49 @@ out var configsWithExtensionHeaders ? LogLevel.Warning : docLogLevelDefault; - if (queryFailure.Exceptions == null || queryFailure.Exceptions.Count <= 1) + switch (queryFailure.Exceptions) { - SharpGenLogger.LogRawMessage( - docLogLevel, - LoggingCodes.DocumentationProviderInternalError, - "Documentation provider [{0}] query for \"{1}\" failed.", - queryFailure.Exceptions?.FirstOrDefault(), - providerName, - queryFailure.Query - ); - } - else - { - var exceptionsCount = queryFailure.Exceptions.Count; - for (var index = 0; index < exceptionsCount; index++) + case { Count: > 1 } exceptions: { - var exception = queryFailure.Exceptions[index]; - + var exceptionsCount = exceptions.Count; + for (var index = 0; index < exceptionsCount; index++) + { + var exception = exceptions[index]; + + SharpGenLogger.LogRawMessage( + docLogLevel, + LoggingCodes.DocumentationProviderInternalError, + "Documentation provider [{0}] query for \"{1}\" failed ({2}/{3}).", + exception, + providerName, + queryFailure.Query, + index + 1, + exceptionsCount + ); + } + + break; + } + case { Count: 1 } exceptions: SharpGenLogger.LogRawMessage( docLogLevel, LoggingCodes.DocumentationProviderInternalError, - "Documentation provider [{0}] query for \"{1}\" failed ({2}/{3}).", - exception, + "Documentation provider [{0}] query for \"{1}\" failed.", + exceptions[0], providerName, - queryFailure.Query, - index + 1, - exceptionsCount + queryFailure.Query ); - } + break; + default: + SharpGenLogger.LogRawMessage( + docLogLevel, + LoggingCodes.DocumentationProviderInternalError, + "Documentation provider [{0}] query for \"{1}\" failed.", + null, + providerName, + queryFailure.Query + ); + break; } } } @@ -286,11 +410,11 @@ out var configsWithExtensionHeaders foreach (var file in ExternalDocumentation) { - using var stream = File.OpenRead(file.ItemSpec); + using var stream = File.OpenRead(file); var xml = new XmlDocument(); xml.Load(stream); - documentationFiles.Add(file.ItemSpec, xml); + documentationFiles.Add(file, xml); } if (AbortExecution) @@ -300,7 +424,9 @@ out var configsWithExtensionHeaders serviceContainer.AddService(new DefaultGenerators(ioc)); RoslynGenerator generator = new(); - generator.Run(solution, GeneratedCodeFolder, ioc); + + var generatedCode = generator.Run(solution, ioc); + File.WriteAllText(GeneratedCodeFile, generatedCode); return !SharpGenLogger.HasErrors; } @@ -313,7 +439,7 @@ private PlatformDetectionType ConfigPlatforms foreach (var platform in Platforms) { - if (!Enum.TryParse(platform.ItemSpec, out var parsedPlatform)) + if (!Enum.TryParse(platform, out var parsedPlatform)) { SharpGenLogger.Warning( LoggingCodes.InvalidPlatformDetectionType, @@ -334,21 +460,19 @@ private PlatformDetectionType ConfigPlatforms private void GenerateConfigForConsumers(ConfigFile consumerConfig) { - if (ConsumerBindMappingConfig == null) return; - - using var consumerBindMapping = File.Create(ConsumerBindMappingConfig.ItemSpec); + using var consumerBindMapping = File.Create(ConsumerBindMappingConfig); consumerConfig.Write(consumerBindMapping); } private void GenerateInputsCache(IEnumerable paths) { - using var inputCacheStream = new StreamWriter(InputsCache.ItemSpec, false, DefaultEncoding); + using var inputCacheStream = new StreamWriter(InputsCache, false, DefaultEncoding); foreach (var path in paths) inputCacheStream.WriteLine(path); } - private ConfigFile LoadConfig(ConfigFile config) + private void LoadConfig(ConfigFile config) { config.Load(null, Macros, SharpGenLogger); @@ -367,7 +491,56 @@ private ConfigFile LoadConfig(ConfigFile config) } } } + } + + private void GeneratePackagesProps() + { + using MemoryStream stream = new(); + { + using var writer = new StreamWriter(stream, DefaultEncoding, -1, true); + + + writer.WriteLine(@""); + writer.WriteLine(@" "); + writer.WriteLine( + $@" " + ); + writer.WriteLine(@" "); + writer.WriteLine(@""); + } + + bool writeNeeded; + + if (File.Exists(PackagePropsFile)) + { + ReadOnlySpan cachedHash = File.ReadAllBytes(PackagePropsFile); + + stream.Position = 0; + var success = stream.TryGetBuffer(out var buffer); + Debug.Assert(success); + + if (buffer.AsSpan().SequenceEqual(cachedHash)) + { + SharpGenLogger.Message("NuGet package properties file is already up-to-date."); + writeNeeded = false; + } + else + { + SharpGenLogger.Message("NuGet package properties file is out-of-date."); + writeNeeded = true; + } + } + else + { + SharpGenLogger.Message("NuGet package properties file doesn't exist."); + writeNeeded = true; + } - return config; + if (writeNeeded) + { + stream.Position = 0; + using var file = File.Open(PackagePropsFile, FileMode.Create, FileAccess.Write); + stream.CopyTo(file); + } } } \ No newline at end of file diff --git a/SharpGenTools.Sdk/Tasks/SharpGenTaskBase.cs b/SharpGenTools.Sdk/Tasks/SharpGenTaskBase.cs deleted file mode 100644 index b2944eb7..00000000 --- a/SharpGenTools.Sdk/Tasks/SharpGenTaskBase.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.Build.Framework; - -namespace SharpGenTools.Sdk.Tasks; - -public abstract class SharpGenTaskBase : SharpTaskBase -{ - // ReSharper disable MemberCanBeProtected.Global, UnusedAutoPropertyAccessor.Global - [Required] public string[] CastXmlArguments { get; set; } - [Required] public ITaskItem CastXmlExecutable { get; set; } - [Required] public ITaskItem[] ConfigFiles { get; set; } - [Required] public string ConsumerBindMappingConfigId { get; set; } - [Required] public ITaskItem DocumentationCache { get; set; } - [Required] public bool DocumentationFailuresAsErrors { get; set; } - [Required] public ITaskItem[] ExtensionAssemblies { get; set; } - [Required] public ITaskItem[] ExternalDocumentation { get; set; } - [Required] public string GeneratedCodeFolder { get; set; } - [Required] public ITaskItem[] GlobalNamespaceOverrides { get; set; } - [Required] public ITaskItem InputsCache { get; set; } - [Required] public string[] Macros { get; set; } - [Required] public string OutputPath { get; set; } - [Required] public ITaskItem[] Platforms { get; set; } - [Required] public ITaskItem[] SilenceMissingDocumentationErrorIdentifierPatterns { get; set; } - public ITaskItem ConsumerBindMappingConfig { get; set; } - // ReSharper restore UnusedAutoPropertyAccessor.Global, MemberCanBeProtected.Global -} \ No newline at end of file diff --git a/SharpGenTools.Sdk/Tasks/SharpPropertyCacheTask.cs b/SharpGenTools.Sdk/Tasks/SharpPropertyCacheTask.cs deleted file mode 100644 index 5f29229a..00000000 --- a/SharpGenTools.Sdk/Tasks/SharpPropertyCacheTask.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Security.Cryptography; -using System.Text; -using Microsoft.Build.Framework; -using SharpGenTools.Sdk.Internal; - -namespace SharpGenTools.Sdk.Tasks; - -public sealed class SharpPropertyCacheTask : SharpGenTaskBase -{ - [Required] public ITaskItem PropertyCache { get; set; } - - private const int CacheFormatSignature = ('S' << 0) | ('G' << 8) | ('P' << 16) | ('C' << 24); - private const int CacheFormatVersion = 1; - private static readonly Encoding TextEncoding = Encoding.UTF8; - - private static HashAlgorithm CreateSettingsHash() => SHA512.Create(); - - public override bool Execute() - { - PrepareExecute(); - - var cacheItemSpec = PropertyCache.ItemSpec; - Utilities.RequireAbsolutePath(cacheItemSpec, nameof(PropertyCache)); - - var currentHash = HashSettings(); - - bool writeNeeded; - - if (File.Exists(cacheItemSpec)) - { - ReadOnlySpan currentHashSpan = currentHash; - ReadOnlySpan cachedHash = File.ReadAllBytes(cacheItemSpec); - - if (currentHashSpan.SequenceEqual(cachedHash)) - { - SharpGenLogger.Message("Properties hash matches cached value."); - writeNeeded = false; - } - else - { - SharpGenLogger.Message("Properties hash mismatch, writing a new property cache file."); - writeNeeded = true; - } - } - else - { - SharpGenLogger.Message("Properties cache doesn't exist."); - writeNeeded = true; - } - - if (writeNeeded) - { - File.WriteAllBytes(cacheItemSpec, currentHash); - } - - return true; - } - - private byte[] HashSettings() - { - using var stream = new MemoryStream(); - WriteProperties(stream); - - stream.Position = 0; - - using var hash = CreateSettingsHash(); - return hash.ComputeHash(stream); - } - - private void WriteProperties(Stream stream) - { - using var writer = new BinaryWriter(stream, TextEncoding, true); - - writer.Write(CacheFormatSignature); - writer.Write(CacheFormatVersion); - WriteStringArray(CastXmlArguments); - WriteTaskItem(CastXmlExecutable); - WriteTaskItems(ConfigFiles); - WriteString(ConsumerBindMappingConfigId); - WriteTaskItem(DocumentationCache); - WriteBool(DocumentationFailuresAsErrors); - WriteTaskItems(ExtensionAssemblies); - WriteTaskItems(ExternalDocumentation); - WriteString(GeneratedCodeFolder); - WriteTaskItems(GlobalNamespaceOverrides); - WriteTaskItem(InputsCache); - WriteStringArray(Macros); - WriteString(OutputPath); - WriteTaskItems(Platforms); - WriteTaskItems(SilenceMissingDocumentationErrorIdentifierPatterns); - WriteTaskItem(ConsumerBindMappingConfig); - - void WriteString(string s) - { - if (s == null) - { - writer.Write(0); - return; - } - - writer.Write(s); - } - - void WriteStringArray(IReadOnlyList strings) - { - var length = strings.Count; - writer.Write(length); - for (var i = 0; i < length; i++) - { - WriteString(strings[i]); - } - } - - void WriteTaskItem(ITaskItem item) - { - if (item == null) - { - writer.Write(0); - return; - } - - writer.Write(item.ItemSpec); - var metadata = item.CloneCustomMetadata(); - var length = metadata.Count; - writer.Write(length); - } - - void WriteTaskItems(IReadOnlyList items) - { - var length = items.Count; - writer.Write(length); - for (var i = 0; i < length; i++) - { - WriteTaskItem(items[i]); - } - } - - void WriteBool(bool v) => writer.Write(v); - } -} \ No newline at end of file diff --git a/SharpGenTools.Sdk/Tasks/SharpTaskBase.cs b/SharpGenTools.Sdk/Tasks/SharpTaskBase.cs deleted file mode 100644 index f5bc2b3f..00000000 --- a/SharpGenTools.Sdk/Tasks/SharpTaskBase.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using Logger = SharpGen.Logging.Logger; - -namespace SharpGenTools.Sdk.Tasks; - -public abstract class SharpTaskBase : Task, ICancelableTask -{ - private volatile bool isCancellationRequested; - - // ReSharper disable MemberCanBePrivate.Global, UnusedAutoPropertyAccessor.Global - [Required] public bool DebugWaitForDebuggerAttach { get; set; } - // ReSharper restore UnusedAutoPropertyAccessor.Global, MemberCanBePrivate.Global - - protected Logger SharpGenLogger { get; private set; } - - protected bool IsCancellationRequested => isCancellationRequested; - - protected void PrepareExecute() - { - BindingRedirectResolution.Enable(); - - SharpGenLogger = new Logger(new MSBuildSharpGenLogger(Log)); - -#if DEBUG - if (DebugWaitForDebuggerAttach) - WaitForDebuggerAttach(); -#endif - } - - [Conditional("DEBUG")] - private void WaitForDebuggerAttach() - { - if (!Debugger.IsAttached) - { - SharpGenLogger.Warning(null, $"{GetType().Name} is waiting for attach: {Process.GetCurrentProcess().Id}"); - Thread.Yield(); - } - - while (!Debugger.IsAttached && !IsCancellationRequested) - Thread.Sleep(TimeSpan.FromSeconds(1)); - } - - public void Cancel() - { - isCancellationRequested = true; - } -} \ No newline at end of file From f5c7118ee3ccf3462fd76182bddc6bd0eda91019 Mon Sep 17 00:00:00 2001 From: Aaron Sun Date: Wed, 27 Apr 2022 00:36:02 -0700 Subject: [PATCH 07/55] Correct documentation generation typo --- SharpGen/Transform/TransformServiceExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharpGen/Transform/TransformServiceExtensions.cs b/SharpGen/Transform/TransformServiceExtensions.cs index 857d5717..7cd37ccc 100644 --- a/SharpGen/Transform/TransformServiceExtensions.cs +++ b/SharpGen/Transform/TransformServiceExtensions.cs @@ -50,7 +50,7 @@ public static IEnumerable GetDocItems(this IDocumentationLinker aggregat } else { - docItems.Add($""); + docItems.Add($""); } if (element.CppElementName != null) From c0ed0ad7a1f8a61431b7cf3abec02461a54fd6fe Mon Sep 17 00:00:00 2001 From: Aaron Sun Date: Wed, 27 Apr 2022 00:57:26 -0700 Subject: [PATCH 08/55] Utilize the full cpp name for documentation matching. --- SharpGen/Model/CsBase.cs | 9 +++++++++ SharpGen/Transform/ExternalDocCommentsReader.cs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/SharpGen/Model/CsBase.cs b/SharpGen/Model/CsBase.cs index 137560b5..5e802141 100644 --- a/SharpGen/Model/CsBase.cs +++ b/SharpGen/Model/CsBase.cs @@ -205,6 +205,15 @@ public string CppElementName set => _cppElementName = value; } + /// + /// Gets the full name of the C++ element. + /// + /// The name of the C++ element. + public string CppElementFullName + { + get => CppElement?.FullName ?? CppElementName; + } + /// /// Gets or sets the doc id. /// diff --git a/SharpGen/Transform/ExternalDocCommentsReader.cs b/SharpGen/Transform/ExternalDocCommentsReader.cs index 0d1464e2..ae835048 100644 --- a/SharpGen/Transform/ExternalDocCommentsReader.cs +++ b/SharpGen/Transform/ExternalDocCommentsReader.cs @@ -42,6 +42,6 @@ public string GetDocumentWithExternalComments(CsBase element) private static string GetExternalDocCommentId(CsBase element) { - return element.CppElementName ?? element.QualifiedName; + return element.CppElementFullName ?? element.QualifiedName; } } \ No newline at end of file From 276c3246763d0bba8f4d73b3ee23a11dee4c1aa6 Mon Sep 17 00:00:00 2001 From: Aaron Sun Date: Wed, 27 Apr 2022 18:33:18 -0700 Subject: [PATCH 09/55] Improve documentation for doc generation. --- docs/documentation.rst | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/documentation.rst b/docs/documentation.rst index 3368fa2f..86b97a5d 100644 --- a/docs/documentation.rst +++ b/docs/documentation.rst @@ -12,12 +12,31 @@ You can supply external documentation though XML files structured as below: .. code-block:: xml - + Summary here + + An example summary for a C++ struct named DML_BUFFER_BINDING. + + + An example summary for a C++ field named Buffer within a struct named DML_BUFFER_BINDING. + -SharpGenTools will automatically include an ```` documentation tag that points to a matching element in an external documentation file. You can specify an external comments file to SharpGen by adding it as a ``SharpGenExternalDocs`` item in your project file. +SharpGenTools will automatically add an ```` documentation tag to matching elements in the generated code. This tag will point to the matching element in an external documentation file. You can specify an external comments file to SharpGen by adding it as a ``SharpGenExternalDocs`` item in your project file. + +An example project file that supports documentation: + +.. code-block:: xml + + + ... + + + + ... + + Doc Providers ================== From 677efb0b01505d847883b1221efd2c22dd1b836c Mon Sep 17 00:00:00 2001 From: Andrew Boyarshin Date: Thu, 28 Apr 2022 16:30:22 +0700 Subject: [PATCH 10/55] Add partial method generation, comprehensive fixes --- SdkTests/Interface/Mapping.xml | 6 +- SharpGen.Generator/SharpGen.Generator.csproj | 8 +- .../SharpGenModuleGenerator.GenerateModule.cs | 31 +- ...arpGenModuleGenerator.GenerateUtilities.cs | 5 +- SharpGen/Config/BindRule.cs | 56 ++-- SharpGen/Config/DefineExtensionRule.cs | 77 +++-- SharpGen/Config/MappingRule.cs | 52 ++-- SharpGen/Generator/CallableCodeGenerator.cs | 7 + SharpGen/Generator/InterfaceCodeGenerator.cs | 31 +- SharpGen/Generator/ShadowCallbackGenerator.cs | 70 +++-- SharpGen/Model/CppElement.Extensions.cs | 4 +- SharpGen/Model/CsInterface.cs | 8 +- SharpGen/Model/CsMethod.cs | 4 + SharpGen/Parser/SdkResolver.cs | 69 ++++- SharpGen/Transform/InterfaceTransform.cs | 8 +- SharpGen/Transform/TransformManager.cs | 48 +--- SharpGen/Transform/TypeRegistry.cs | 88 +++++- SharpGenTools.Sdk/Internal/CacheFile.cs | 133 +++++++++ SharpGenTools.Sdk/Sdk.targets | 12 +- SharpGenTools.Sdk/SharpGenTask.InputsCache.cs | 182 ++++++++++++ .../SharpGenTask.PropertyCache.cs | 253 +++++++++++++++++ SharpGenTools.Sdk/{Tasks => }/SharpGenTask.cs | 168 ++++++----- SharpGenTools.Sdk/SharpGenTools.Sdk.csproj | 9 + .../Tasks/SharpGenTask.PropertyCache.cs | 267 ------------------ build.ps1 | 2 +- 25 files changed, 1041 insertions(+), 557 deletions(-) create mode 100644 SharpGenTools.Sdk/Internal/CacheFile.cs create mode 100644 SharpGenTools.Sdk/SharpGenTask.InputsCache.cs create mode 100644 SharpGenTools.Sdk/SharpGenTask.PropertyCache.cs rename SharpGenTools.Sdk/{Tasks => }/SharpGenTask.cs (81%) delete mode 100644 SharpGenTools.Sdk/Tasks/SharpGenTask.PropertyCache.cs 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/SharpGen.Generator/SharpGen.Generator.csproj b/SharpGen.Generator/SharpGen.Generator.csproj index 01238138..278150d3 100644 --- a/SharpGen.Generator/SharpGen.Generator.csproj +++ b/SharpGen.Generator/SharpGen.Generator.csproj @@ -3,7 +3,7 @@ - net472;net5.0 + netstandard2.0 true latest false @@ -14,11 +14,11 @@ - - + + - + diff --git a/SharpGen.Generator/SharpGenModuleGenerator.GenerateModule.cs b/SharpGen.Generator/SharpGenModuleGenerator.GenerateModule.cs index be4e62c9..8a4f127c 100644 --- a/SharpGen.Generator/SharpGenModuleGenerator.GenerateModule.cs +++ b/SharpGen.Generator/SharpGenModuleGenerator.GenerateModule.cs @@ -21,7 +21,7 @@ private static void GenerateModule(GeneratorExecutionContext context) List vtblJobs = 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) { @@ -58,16 +58,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; @@ -198,10 +205,10 @@ localVtbl is not null ); } - 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 class VtblJob diff --git a/SharpGen.Generator/SharpGenModuleGenerator.GenerateUtilities.cs b/SharpGen.Generator/SharpGenModuleGenerator.GenerateUtilities.cs index d51371ff..d0b9e2c3 100644 --- a/SharpGen.Generator/SharpGenModuleGenerator.GenerateUtilities.cs +++ b/SharpGen.Generator/SharpGenModuleGenerator.GenerateUtilities.cs @@ -50,8 +50,9 @@ private static void GenerateUtilities(GeneratorExecutionContext context) { List attributes = new(1); - if (context.Compilation.GetTypeByMetadataName(ModuleInitializerAttributeName) is not - { IsReferenceType: true, IsGenericType: false }) + var moduleInitType = context.Compilation.GetTypeByMetadataName(ModuleInitializerAttributeName); + if (moduleInitType is not { IsReferenceType: true, IsGenericType: false } || + !context.Compilation.IsSymbolAccessibleWithin(moduleInitType, context.Compilation.Assembly)) attributes.Add(ModuleInitializerAttribute); if (attributes.Count == 0) diff --git a/SharpGen/Config/BindRule.cs b/SharpGen/Config/BindRule.cs index 45185256..60c8faa3 100644 --- a/SharpGen/Config/BindRule.cs +++ b/SharpGen/Config/BindRule.cs @@ -18,6 +18,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +#nullable enable + +using System.ComponentModel; using System.Globalization; using System.Xml.Serialization; @@ -30,35 +33,20 @@ namespace SharpGen.Config; [XmlType("bind")] public class BindRule : ConfigBaseRule { - /// - /// Initializes a new instance of the class. - /// public BindRule() { } - /// - /// Initializes a new instance of the class. - /// - /// The cpp type - /// The C# type - public BindRule(string @from, string to) - { - From = from; - To = to; - } - - /// - /// Initializes a new instance of the class. - /// - /// The c++ type + /// The C++ type /// The C# public type /// The C# marshal type - public BindRule(string @from, string to, string marshal) + /// The source of the definition + public BindRule(string from, string to, string? marshal = null, string? source = null) { From = from; To = to; Marshal = marshal; + Source = source; } /// @@ -79,12 +67,32 @@ public BindRule(string @from, string to, string marshal) /// Gets or sets the C# the marshal type /// [XmlAttribute("marshal")] - public string Marshal { get; set; } + public string? Marshal { get; set; } - /// - [ExcludeFromCodeCoverage] - public override string ToString() + /// The source of the definition + [XmlAttribute("source")] + [DefaultValue(null)] + public string? Source { get; set; } + + /// + /// Provides an ability to override an existing mapping + /// + [XmlIgnore] + public bool? Override { get; set; } + + [XmlAttribute("override")] + public bool _Override_ { - return string.Format(CultureInfo.InvariantCulture, "{0} from:{1} to:{2} marshal:{3}", base.ToString(), From, To, Marshal); + get => Override.Value; + set => Override = value; } + + public bool ShouldSerialize_Override_() => Override != null; + + /// + [ExcludeFromCodeCoverage] + public override string ToString() => string.Format( + CultureInfo.InvariantCulture, + "{0} from:{1} to:{2} marshal:{3} source:{4} override:{5}", base.ToString(), From, To, Marshal, Source, Override + ); } \ No newline at end of file diff --git a/SharpGen/Config/DefineExtensionRule.cs b/SharpGen/Config/DefineExtensionRule.cs index ed8cd520..9cbadb91 100644 --- a/SharpGen/Config/DefineExtensionRule.cs +++ b/SharpGen/Config/DefineExtensionRule.cs @@ -18,6 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +using System.ComponentModel; using System.Globalization; using System.Xml.Serialization; @@ -39,41 +40,77 @@ public class DefineExtensionRule : ExtensionBaseRule [XmlAttribute("underlying")] public string UnderlyingType { get; set; } - [XmlAttribute("shadow")] + [XmlAttribute("shadow"), DefaultValue(null)] public string ShadowName { get; set; } [XmlAttribute("vtbl")] public string VtblName { get; set; } - [XmlIgnore] - public int? SizeOf { get; set; } + [XmlIgnore] public int? SizeOf { get; set; } + [XmlAttribute("sizeof")] - public int _SizeOf_ { get { return SizeOf.Value; } set { SizeOf = value; } } public bool ShouldSerialize_SizeOf_() { return SizeOf != null; } - - [XmlIgnore] - public int? Align { get; set; } + public int _SizeOf_ + { + get => SizeOf.Value; + set => SizeOf = value; + } + + public bool ShouldSerialize_SizeOf_() => SizeOf != null; + + [XmlIgnore] public int? Align { get; set; } + [XmlAttribute("align")] - public int _Align_ { get { return Align.Value; } set { Align = value; } } public bool ShouldSerialize_Align_() { return Align != null; } + public int _Align_ + { + get => Align.Value; + set => Align = value; + } + + public bool ShouldSerialize_Align_() => Align != null; + + [XmlIgnore] public bool? HasCustomMarshal { get; set; } - [XmlIgnore] - public bool? HasCustomMarshal { get; set; } [XmlAttribute("marshal")] - public bool _HasCustomMarshal_ { get { return HasCustomMarshal.Value; } set { HasCustomMarshal = value; } } public bool ShouldSerialize_HasCustomMarshal_() { return HasCustomMarshal != null; } + public bool _HasCustomMarshal_ + { + get => HasCustomMarshal.Value; + set => HasCustomMarshal = value; + } + + public bool ShouldSerialize_HasCustomMarshal_() => HasCustomMarshal != null; + + [XmlIgnore] public bool? IsStaticMarshal { get; set; } - [XmlIgnore] - public bool? IsStaticMarshal { get; set; } [XmlAttribute("static-marshal")] - public bool _IsStaticMarshal_ { get { return IsStaticMarshal.Value; } set { IsStaticMarshal = value; } } public bool ShouldSerialize_IsStaticMarshal_() { return IsStaticMarshal != null; } + public bool _IsStaticMarshal_ + { + get => IsStaticMarshal.Value; + set => IsStaticMarshal = value; + } + + public bool ShouldSerialize_IsStaticMarshal_() => IsStaticMarshal != null; + + [XmlIgnore] public bool? HasCustomNew { get; set; } - [XmlIgnore] - public bool? HasCustomNew { get; set; } [XmlAttribute("custom-new")] - public bool _HasCustomNew_ { get { return HasCustomNew.Value; } set { HasCustomNew = value; } } public bool ShouldSerialize_HasCustomNew_() { return HasCustomNew != null; } + public bool _HasCustomNew_ + { + get => HasCustomNew.Value; + set => HasCustomNew = value; + } + + public bool ShouldSerialize_HasCustomNew_() => HasCustomNew != null; + + [XmlIgnore] public bool? IsNativePrimitive { get; set; } - [XmlIgnore] - public bool? IsNativePrimitive { get; set; } [XmlAttribute("primitive")] - public bool _IsNativePrimitive_ { get => IsNativePrimitive.Value; set => IsNativePrimitive = value; } public bool ShouldSerialize_IsNativePrimitive_() => IsNativePrimitive != null; + public bool _IsNativePrimitive_ + { + get => IsNativePrimitive.Value; + set => IsNativePrimitive = value; + } + + public bool ShouldSerialize_IsNativePrimitive_() => IsNativePrimitive != null; [XmlIgnore] public bool? IsCallbackInterface { get; set; } diff --git a/SharpGen/Config/MappingRule.cs b/SharpGen/Config/MappingRule.cs index 70a54e89..f3522a92 100644 --- a/SharpGen/Config/MappingRule.cs +++ b/SharpGen/Config/MappingRule.cs @@ -171,21 +171,7 @@ public int _StructPack_ /// Mapping name /// [XmlAttribute("name")] - public string MappingNameFinal - { - get => MappingName; - set - { - MappingName = value; - IsFinalMappingName = true; - } - } - - /// - /// True if the MappingName doesn't need any further rename processing - /// - [XmlIgnore] - public bool? IsFinalMappingName { get; set; } = false; + public string MappingNameFinal { get; set; } /// /// True if a struct should used a native value type marshalling @@ -468,6 +454,32 @@ public bool _Hidden_ set => Hidden = value; } + /// + /// Provides an ability to prevent default vtbl method implementation generation in an interface + /// + [XmlIgnore] + public bool? ManagedPartial { get; set; } + + [XmlAttribute("managed-partial")] + public bool _ManagedPartial_ + { + get => ManagedPartial.Value; + set => ManagedPartial = value; + } + + /// + /// Provides an ability to prevent default CppObject method implementation generation + /// + [XmlIgnore] + public bool? NativePartial { get; set; } + + [XmlAttribute("native-partial")] + public bool _NativePartial_ + { + get => NativePartial.Value; + set => NativePartial = value; + } + /// /// Best-effort flag for retaining pointers in generated bindings. /// @@ -516,9 +528,9 @@ public StringMarshalType _StringMarshal_ public bool ShouldSerialize_StructPack_() => StructPack != null; - public bool ShouldSerializeMappingName() => IsFinalMappingName.HasValue && !IsFinalMappingName.Value; + public bool ShouldSerializeMappingName() => MappingName != null; - public bool ShouldSerializeMappingNameFinal() => !IsFinalMappingName.HasValue || IsFinalMappingName.Value; + public bool ShouldSerializeMappingNameFinal() => MappingNameFinal != null; public bool ShouldSerialize_StructHasNativeValueType_() => StructHasNativeValueType != null; @@ -540,7 +552,7 @@ public StringMarshalType _StringMarshal_ public bool ShouldSerialize_IsDualCallbackInterface_() => IsDualCallbackInterface != null; - public bool ShouldSerialize_AutoGenerateShadow_() => AutoGenerateShadow != null; + public bool ShouldSerialize_AutoGenerateShadow_() => false; public bool ShouldSerialize_AutoGenerateVtbl_() => AutoGenerateVtbl != null; @@ -558,6 +570,10 @@ public StringMarshalType _StringMarshal_ public bool ShouldSerialize_Hidden_() => Hidden != null; + public bool ShouldSerialize_ManagedPartial_() => ManagedPartial != null; + + public bool ShouldSerialize_NativePartial_() => NativePartial != null; + public bool ShouldSerialize_KeepPointers_() => KeepPointers != null; public bool ShouldSerialize_StringMarshal_() => StringMarshal != null; diff --git a/SharpGen/Generator/CallableCodeGenerator.cs b/SharpGen/Generator/CallableCodeGenerator.cs index de2fdf4a..5cdfae42 100644 --- a/SharpGen/Generator/CallableCodeGenerator.cs +++ b/SharpGen/Generator/CallableCodeGenerator.cs @@ -39,6 +39,13 @@ public override MemberDeclarationSyntax GenerateCode(CsCallable csElement) .WithModifiers(TokenList()); } + if (csElement is CsMethod { NativePartial: true }) + { + return methodDeclaration + .AddModifiers(Token(SyntaxKind.PartialKeyword)) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); + } + var statements = NewStatementList; foreach (var param in csElement.Parameters) diff --git a/SharpGen/Generator/InterfaceCodeGenerator.cs b/SharpGen/Generator/InterfaceCodeGenerator.cs index b74457d4..ed388c98 100644 --- a/SharpGen/Generator/InterfaceCodeGenerator.cs +++ b/SharpGen/Generator/InterfaceCodeGenerator.cs @@ -310,16 +310,29 @@ StatementSyntax GetDisposeInvocation(CsProperty x, params ArgumentSyntax[] args) var resultList = NewMemberList; - if (csElement.IsCallback && csElement.AutoGenerateVtbl) + if (csElement.IsCallback) { - resultList.Add(csElement, Generators.Vtbl); - var shadowAttribute = Attribute( - GlobalNamespace.GetTypeNameSyntax(WellKnownName.VtblAttribute), - AttributeArgumentList( - SingletonSeparatedList(AttributeArgument(TypeOfExpression(ParseTypeName(csElement.VtblName)))) - ) - ); - attributes = attributes?.AddAttributes(shadowAttribute) ?? AttributeList(SingletonSeparatedList(shadowAttribute)); + if (csElement.AutoGenerateVtbl) + { + resultList.Add(csElement, Generators.Vtbl); + var attribute = Attribute( + GlobalNamespace.GetTypeNameSyntax(WellKnownName.VtblAttribute), + AttributeArgumentList( + SingletonSeparatedList(AttributeArgument(TypeOfExpression(ParseTypeName(csElement.VtblName)))) + ) + ); + attributes = attributes?.AddAttributes(attribute) ?? AttributeList(SingletonSeparatedList(attribute)); + } + if (csElement.AutoGenerateShadow) + { + var attribute = Attribute( + GlobalNamespace.GetTypeNameSyntax(WellKnownName.ShadowAttribute), + AttributeArgumentList( + SingletonSeparatedList(AttributeArgument(TypeOfExpression(ParseTypeName(csElement.ShadowName)))) + ) + ); + attributes = attributes?.AddAttributes(attribute) ?? AttributeList(SingletonSeparatedList(attribute)); + } } var attributeList = attributes != null ? SingletonList(attributes) : default; diff --git a/SharpGen/Generator/ShadowCallbackGenerator.cs b/SharpGen/Generator/ShadowCallbackGenerator.cs index cd3073a2..eee1ae37 100644 --- a/SharpGen/Generator/ShadowCallbackGenerator.cs +++ b/SharpGen/Generator/ShadowCallbackGenerator.cs @@ -119,6 +119,44 @@ private MethodDeclarationSyntax GenerateShadowCallback(CsCallable csElement, Pla { var interopReturnType = sig.ReturnTypeSyntax; + var methodDeclaration = MethodDeclaration( + interopReturnType, + VtblGenerator.GetMethodImplName(csElement, platform) + ) + .WithModifiers( + TokenList( + Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.StaticKeyword), + Token(SyntaxKind.UnsafeKeyword) + ) + ) + .WithParameterList(GetNativeParameterList(csElement, sig)) + .WithAttributeLists( + csElement is CsMethod { IsFunctionPointerInVtbl: true } + ? SingletonList( + AttributeList( + SingletonSeparatedList( + Attribute( + UnmanagedCallersOnlyAttributeName, + AttributeArgumentList( + SingletonSeparatedList( + AttributeArgument(FnPtrCallConvs(sig.CallingConvention)) + .WithNameEquals(NameEquals("CallConvs")) + ) + ) + ) + ) + ).WithTrailingEndIfDirective() + ) + : default + ); + + if (csElement is CsMethod { ManagedPartial: true }) + { + return methodDeclaration + .AddModifiers(Token(SyntaxKind.PartialKeyword)) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); + } + var statements = NewStatementList; statements.Add(csElement, platform, Generators.ReverseCallableProlog); @@ -265,37 +303,7 @@ private MethodDeclarationSyntax GenerateShadowCallback(CsCallable csElement, Pla ) ); - return MethodDeclaration( - interopReturnType, - VtblGenerator.GetMethodImplName(csElement, platform) - ) - .WithModifiers( - TokenList( - Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.StaticKeyword), - Token(SyntaxKind.UnsafeKeyword) - ) - ) - .WithParameterList(GetNativeParameterList(csElement, sig)) - .WithBody(fullBody.ToBlock()) - .WithAttributeLists( - csElement is CsMethod { IsFunctionPointerInVtbl: true } - ? SingletonList( - AttributeList( - SingletonSeparatedList( - Attribute( - UnmanagedCallersOnlyAttributeName, - AttributeArgumentList( - SingletonSeparatedList( - AttributeArgument(FnPtrCallConvs(sig.CallingConvention)) - .WithNameEquals(NameEquals("CallConvs")) - ) - ) - ) - ) - ).WithTrailingEndIfDirective() - ) - : default - ); + return methodDeclaration.WithBody(fullBody.ToBlock()); static ExpressionSyntax FnPtrCallConvs(CallingConvention callingConvention) => ImplicitArrayCreationExpression( diff --git a/SharpGen/Model/CppElement.Extensions.cs b/SharpGen/Model/CppElement.Extensions.cs index ca88c439..64fcd564 100644 --- a/SharpGen/Model/CppElement.Extensions.cs +++ b/SharpGen/Model/CppElement.Extensions.cs @@ -151,7 +151,9 @@ private static void ProcessRule(CppElement element, MappingRule newRule, Regex p if (newRule.ParameterUsedAsReturnType != null) tag.ParameterUsedAsReturnType = newRule.ParameterUsedAsReturnType; if (newRule.Relation != null) tag.Relation = newRule.Relation; - if (newRule.Hidden != null) tag.Hidden = newRule.Hidden; + if (newRule.Hidden is { } hidden) tag.Hidden = hidden; + if (newRule.ManagedPartial is { } managedPartial) tag.ManagedPartial = managedPartial; + if (newRule.NativePartial is { } nativePartial) tag.NativePartial = nativePartial; if (newRule.KeepPointers != null) tag.KeepPointers = newRule.KeepPointers; if (newRule.StringMarshal is { } stringMarshal) tag.StringMarshal = stringMarshal; } diff --git a/SharpGen/Model/CsInterface.cs b/SharpGen/Model/CsInterface.cs index 34c6219b..66acf7e4 100644 --- a/SharpGen/Model/CsInterface.cs +++ b/SharpGen/Model/CsInterface.cs @@ -123,7 +123,11 @@ private static string FindGuid(CppInterface cppInterface) public string ShadowName { get => shadowName ?? DefaultShadowFullName; - set => shadowName = value; + set + { + shadowName = value; + AutoGenerateShadow = true; + } } public string VtblName @@ -135,7 +139,7 @@ public string VtblName private string DefaultShadowFullName => $"{QualifiedName}Shadow"; private string DefaultVtblFullName => $"{QualifiedName}Vtbl"; - public bool AutoGenerateShadow { get; } = true; + public bool AutoGenerateShadow { get; private set; } public bool AutoGenerateVtbl { get; } = true; public bool StaticShadowVtbl { get; } = true; public bool AutoDisposePersistentProperties { get; } = true; diff --git a/SharpGen/Model/CsMethod.cs b/SharpGen/Model/CsMethod.cs index 2a2a1b0d..eabb25e0 100644 --- a/SharpGen/Model/CsMethod.cs +++ b/SharpGen/Model/CsMethod.cs @@ -42,6 +42,8 @@ public CsMethod(Ioc ioc, CppMethod cppMethod, string name) : base(ioc, cppMethod AllowProperty = tag.Property ?? AllowProperty; IsPersistent = tag.Persist ?? IsPersistent; Hidden = tag.Hidden ?? Hidden; + ManagedPartial = tag.ManagedPartial ?? ManagedPartial; + NativePartial = tag.NativePartial ?? NativePartial; CustomVtbl = tag.CustomVtbl ?? CustomVtbl; IsKeepImplementPublic = tag.IsKeepImplementPublic ?? IsKeepImplementPublic; @@ -53,6 +55,8 @@ public CsMethod(Ioc ioc, CppMethod cppMethod, string name) : base(ioc, cppMethod } public bool Hidden { get; set; } + public bool ManagedPartial { get; } + public bool NativePartial { get; } private bool IsKeepImplementPublic { get; } public bool? AllowProperty { get; } public bool CustomVtbl { get; } diff --git a/SharpGen/Parser/SdkResolver.cs b/SharpGen/Parser/SdkResolver.cs index 189c3dd3..1e1ea113 100644 --- a/SharpGen/Parser/SdkResolver.cs +++ b/SharpGen/Parser/SdkResolver.cs @@ -44,28 +44,68 @@ private IEnumerable ResolveStdLib(string? version) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - var vsInstallDir = GetVSInstallPath(); - - version ??= File.ReadAllText( - Path.Combine(vsInstallDir, "VC", "Auxiliary", "Build", "Microsoft.VCToolsVersion.default.txt") - ); + foreach (var vsInstallDir in GetVSInstallPath()) + { + string actualVersion; + if (version is not null) + { + actualVersion = version; + } + else + { + var defaultVersion = Path.Combine( + vsInstallDir, "VC", "Auxiliary", "Build", "Microsoft.VCToolsVersion.default.txt" + ); + + if (!File.Exists(defaultVersion)) + continue; + + actualVersion = File.ReadAllText(defaultVersion); + } + + var path = Path.Combine( + vsInstallDir, "VC", "Tools", "MSVC", actualVersion.Trim(), "include" + ); + + if (!Directory.Exists(path)) + continue; - yield return new IncludeDirRule( - Path.Combine(vsInstallDir, "VC", "Tools", "MSVC", version.Trim(), "include") - ); + yield return new IncludeDirRule(path); + yield break; + } } else { - yield return new IncludeDirRule(Path.Combine("/usr", "include", "c++", version)); + if (version is not null) + { + var path = Path.Combine("/usr", "include", "c++", version); + if (Directory.Exists(path)) + yield return new IncludeDirRule(path); + } + else + { + DirectoryInfo cpp = new(Path.Combine("/usr", "include", "c++")); + + if (!cpp.Exists) + yield break; + + // TODO: pick latest if not specified + var path = cpp.EnumerateDirectories().FirstOrDefault()?.FullName; + if (path is not null) + yield return new IncludeDirRule(path); + } } } - private string? GetVSInstallPath() + private IReadOnlyCollection GetVSInstallPath() { var vsPathOverride = Environment.GetEnvironmentVariable("SHARPGEN_VS_OVERRIDE"); if (!string.IsNullOrEmpty(vsPathOverride)) - return vsPathOverride; + { + return new[] {vsPathOverride}; + } + List paths = new(); try { var query = new SetupConfiguration(); @@ -85,7 +125,7 @@ private IEnumerable ResolveStdLib(string? version) continue; if (instance2.GetPackages().Any(Predicate)) - return instance2.GetInstallationPath(); + paths.Add(instance2.GetInstallationPath()); } while (fetched > 0); static bool Predicate(ISetupPackageReference pkg) => @@ -99,9 +139,10 @@ static bool Predicate(ISetupPackageReference pkg) => ); } - Logger.Fatal("Unable to find a Visual Studio installation that has the Visual C++ Toolchain installed."); + if (paths.Count == 0) + Logger.Fatal("Unable to find a Visual Studio installation that has the Visual C++ Toolchain installed."); - return null; + return paths; } private IEnumerable ResolveWindowsSdk(string version, string? components) diff --git a/SharpGen/Transform/InterfaceTransform.cs b/SharpGen/Transform/InterfaceTransform.cs index 7da6a9e4..d67bd5a6 100644 --- a/SharpGen/Transform/InterfaceTransform.cs +++ b/SharpGen/Transform/InterfaceTransform.cs @@ -61,7 +61,6 @@ public InterfaceTransform(NamingRulesManager namingRules, CppObjectType = new CsInterface(null, globalNamespace.GetTypeName(WellKnownName.CppObject)); DefaultCallbackable = new CsInterface(null, globalNamespace.GetTypeName(WellKnownName.ICallbackable)) { - ShadowName = globalNamespace.GetTypeName(WellKnownName.CppObjectShadow), VtblName = globalNamespace.GetTypeName(WellKnownName.CppObjectVtbl) }; } @@ -107,7 +106,7 @@ public override CsInterface Prepare(CppInterface cppInterface) TypeRegistry.BindType(cppInterface.Name, cSharpInterface, source: cppInterface.ParentInclude?.Name); - if (cppInterface.Rule.AutoGenerateShadow == true) + if (cppInterface.Rule.AutoGenerateShadow is { }) Logger.Message("Interface [{0}] has redundant shadow generation flag", cppInterface.FullName); if (cppInterface.Rule.AutoGenerateVtbl == true) @@ -288,10 +287,7 @@ private CsInterface CreateNativeCallbackType(CsInterface interfaceType) MethodTransform.CreateNativeInteropSignatures(interopSignatureTransform, newCsMethod); - var keepImplementPublic = interfaceType.AutoGenerateShadow || - method.IsPublicVisibilityForced(interfaceType); - - if (!keepImplementPublic) + if (!method.IsPublicVisibilityForced(interfaceType)) { newCsMethod.Visibility = Visibility.Internal; newCsMethod.SuffixName("_"); diff --git a/SharpGen/Transform/TransformManager.cs b/SharpGen/Transform/TransformManager.cs index ef5bc60f..1f5c9746 100644 --- a/SharpGen/Transform/TransformManager.cs +++ b/SharpGen/Transform/TransformManager.cs @@ -179,7 +179,8 @@ private void RegisterBindings(ConfigFile file) string.IsNullOrEmpty(bindingRule.Marshal) ? null : TypeRegistry.ImportType(bindingRule.Marshal), - file.Id + bindingRule.Source ?? file.Id, + bindingRule.Override ?? false ); } } @@ -631,49 +632,6 @@ private void HandleConstantRule(CppElementFinder elementFinder, ConstantRule con public (IEnumerable bindings, IEnumerable defines) GenerateTypeBindingsForConsumers() { - return (from record in TypeRegistry.GetTypeBindings() - select new BindRule(record.CppType, record.CSharpType.QualifiedName, record.MarshalType?.QualifiedName), - GenerateDefinesForMappedTypes()); - } - - private IEnumerable GenerateDefinesForMappedTypes() - { - foreach (var (_, CSharpType, _) in TypeRegistry.GetTypeBindings()) - { - switch (CSharpType) - { - case CsEnum csEnum: - CsFundamentalType tempQualifier = csEnum.UnderlyingType; - yield return new DefineExtensionRule - { - Enum = csEnum.QualifiedName, - SizeOf = checked((int) csEnum.Size), - UnderlyingType = tempQualifier?.Name - }; - break; - case CsStruct csStruct: - yield return new DefineExtensionRule - { - Struct = csStruct.QualifiedName, - SizeOf = checked((int) csStruct.Size), - Align = csStruct.Align, - HasCustomMarshal = csStruct.HasCustomMarshal, - HasCustomNew = csStruct.HasCustomNew, - IsStaticMarshal = csStruct.IsStaticMarshal, - IsNativePrimitive = csStruct.IsNativePrimitive - }; - break; - case CsInterface csInterface: - yield return new DefineExtensionRule - { - Interface = csInterface.QualifiedName, - NativeImplementation = csInterface.NativeImplementation?.QualifiedName, - ShadowName = csInterface.ShadowName, - VtblName = csInterface.VtblName, - IsCallbackInterface = csInterface.IsCallback - }; - break; - } - } + return TypeRegistry.GetTypeBindings(); } } \ No newline at end of file diff --git a/SharpGen/Transform/TypeRegistry.cs b/SharpGen/Transform/TypeRegistry.cs index 7b4ea342..eceac4a5 100644 --- a/SharpGen/Transform/TypeRegistry.cs +++ b/SharpGen/Transform/TypeRegistry.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using SharpGen.Config; using SharpGen.Logging; using SharpGen.Model; @@ -187,7 +188,7 @@ private static CsFundamentalType FindPrimitiveTypeImpl(PrimitiveTypeIdentity ide /// Name of the CPP. /// The C# type. /// The C# marshal type - public void BindType(string cppName, CsTypeBase type, CsTypeBase marshalType = null, string source = null) + public void BindType(string cppName, CsTypeBase type, CsTypeBase marshalType = null, string source = null, bool @override = false) { if (cppName == null) throw new ArgumentNullException(nameof(cppName)); @@ -197,24 +198,42 @@ public void BindType(string cppName, CsTypeBase type, CsTypeBase marshalType = n if (_mapCppNameToCSharpType.TryGetValue(cppName, out var old)) { - var logLevel = type == old.CSharpType && marshalType == old.MarshalType + var match = type == old.CSharpType && marshalType == old.MarshalType; + LogLevel logLevel; + string message; + + if (@override) + { + message = "Remapping C++ element [{0}]{5} to C# type [{1}/{2}] previously mapped to [{3}/{4}]{6}."; + logLevel = LogLevel.Info; + } + else + { + message = "Mapping C++ element [{0}]{5} to C# type [{1}/{2}] when already mapped to [{3}/{4}]{6}. First binding takes priority."; + logLevel = match ? LogLevel.Info : LogLevel.Warning; + } Logger.LogRawMessage( - logLevel, - LoggingCodes.DuplicateBinding, - "Mapping C++ element [{0}]{5} to C# type [{1}/{2}] when already mapped to [{3}/{4}]{6}. First binding takes priority.", - null, + logLevel, LoggingCodes.DuplicateBinding, message, null, cppName, type.CppElementName, type.QualifiedName, old.CSharpType.CppElementName, old.CSharpType.QualifiedName, AtLocation(source), AtLocation(old.Source) ); + if (@override) + BindImpl(); + static string AtLocation(string location) => location != null ? $" at [{location}]" : string.Empty; } else { - _mapCppNameToCSharpType.Add(cppName, new BoundType(type, marshalType, source)); + BindImpl(); + } + + void BindImpl() + { + _mapCppNameToCSharpType[cppName] = new BoundType(type, marshalType, source); DocLinker.AddOrUpdateDocLink(cppName, type.QualifiedName); } } @@ -237,10 +256,59 @@ public bool FindBoundType(string cppName, out BoundType boundType) return false; } - public IEnumerable<(string CppType, CsTypeBase CSharpType, CsTypeBase MarshalType)> GetTypeBindings() + public (IEnumerable Bindings, IEnumerable Defines) GetTypeBindings() { - return from record in _mapCppNameToCSharpType - select (record.Key, record.Value.CSharpType, record.Value.MarshalType); + List bindRules = new(); + List defineRules = new(); + foreach (var entry in _mapCppNameToCSharpType) + { + var boundType = entry.Value; + var csType = boundType.CSharpType; + var marshalType = boundType.MarshalType; + bindRules.Add(new BindRule(entry.Key, csType.QualifiedName, marshalType?.QualifiedName, boundType.Source)); + + switch (csType) + { + case CsEnum { UnderlyingType: var tempQualifier } csEnum: + defineRules.Add( + new DefineExtensionRule + { + Enum = csEnum.QualifiedName, + SizeOf = checked((int) csEnum.Size), + UnderlyingType = tempQualifier?.Name + } + ); + break; + case CsStruct csStruct: + defineRules.Add( + new DefineExtensionRule + { + Struct = csStruct.QualifiedName, + SizeOf = checked((int) csStruct.Size), + Align = csStruct.Align, + HasCustomMarshal = csStruct.HasCustomMarshal, + HasCustomNew = csStruct.HasCustomNew, + IsStaticMarshal = csStruct.IsStaticMarshal, + IsNativePrimitive = csStruct.IsNativePrimitive + } + ); + break; + case CsInterface csInterface: + defineRules.Add( + new DefineExtensionRule + { + Interface = csInterface.QualifiedName, + NativeImplementation = csInterface.NativeImplementation?.QualifiedName, + ShadowName = csInterface.AutoGenerateShadow ? csInterface.ShadowName : null, + VtblName = csInterface.VtblName, + IsCallbackInterface = csInterface.IsCallback + } + ); + break; + } + } + + return (bindRules, defineRules); } #nullable enable diff --git a/SharpGenTools.Sdk/Internal/CacheFile.cs b/SharpGenTools.Sdk/Internal/CacheFile.cs new file mode 100644 index 00000000..44c9f427 --- /dev/null +++ b/SharpGenTools.Sdk/Internal/CacheFile.cs @@ -0,0 +1,133 @@ +#nullable enable + +using System; +using System.Diagnostics; +using System.IO; +using System.Security.Cryptography; + +namespace SharpGenTools.Sdk.Internal; + +public ref struct CacheFile +{ + public enum CacheState + { + Hit, + Miss, + Absent + } + + private bool? _isWriteNeeded = null; + private CacheState _state = CacheState.Absent; + private bool _hasWritten = false; + private StreamWriter? _writer = null; + private FileInfo? _file = null; + + public CacheFile() + { + Stream = new MemoryStream(); + } + + public CacheFile(FileInfo file) : this() + { + File = file; + } + + public FileInfo File + { + get => _file ?? throw new InvalidOperationException(); + set + { + _file = value ?? throw new ArgumentNullException(nameof(value)); + Utilities.RequireAbsolutePath(value.FullName, nameof(value)); + } + } + + private MemoryStream Stream { get; } + + public bool IsWriteNeeded + { + get + { + return _isWriteNeeded ??= ComputeIsWriteNeeded(); + } + } + + public CacheState State + { + get + { + _isWriteNeeded ??= ComputeIsWriteNeeded(); + return _state; + } + } + + private bool ComputeIsWriteNeeded() + { + Debug.Assert(_isWriteNeeded is null); + Debug.Assert(_writer is not null); + Debug.Assert(!_hasWritten); + + _writer.Dispose(); + + File.Refresh(); + + if (!File.Exists) + { + _state = CacheState.Absent; + return true; + } + + ReadOnlySpan cachedHash = System.IO.File.ReadAllBytes(File.FullName); + + Stream.Position = 0; + var success = Stream.TryGetBuffer(out var buffer); + Debug.Assert(success); + + if (buffer.AsSpan().SequenceEqual(cachedHash)) + { + _state = CacheState.Hit; + return false; + } + + _state = CacheState.Miss; + return true; + } + + public StreamWriter StreamWriter => + _writer is null + ? _writer = new StreamWriter(Stream, SharpGenTask.DefaultEncoding, 1024, true) + : throw new InvalidOperationException(); + + public void Write() + { + Debug.Assert(_isWriteNeeded == true); + Debug.Assert(!_hasWritten); + + Stream.Position = 0; + + if (File.DirectoryName is { } directory) + Directory.CreateDirectory(directory); + + using var file = File.Open(FileMode.Create, FileAccess.Write); + Stream.CopyTo(file); + _hasWritten = true; + } + + public byte[] ComputeHash(HashAlgorithm hash) + { + Debug.Assert(_writer is not null); + + _writer.Dispose(); + + Stream.Position = 0; + + return hash.ComputeHash(Stream); + } + + public void Dispose() + { + if (_writer is { } writer) + writer.Dispose(); + Stream.Dispose(); + } +} \ No newline at end of file diff --git a/SharpGenTools.Sdk/Sdk.targets b/SharpGenTools.Sdk/Sdk.targets index 10fc433f..5a3c3654 100644 --- a/SharpGenTools.Sdk/Sdk.targets +++ b/SharpGenTools.Sdk/Sdk.targets @@ -188,14 +188,6 @@ <_SharpGenConsumerMappingNonexistent Remove="@(_SharpGenConsumerMappingNonexistent)" /> - - <_SharpGenConsumerMapping Remove="@(_SharpGenConsumerMapping)" /> - <_SharpGenConsumerMapping Include="@(SharpGenConsumerMapping->Distinct())" /> - - - <_SharpGenConsumerMapping Remove="@(_SharpGenConsumerMapping)" /> - - - build;buildMultiTargeting + build true diff --git a/SharpGenTools.Sdk/SharpGenTask.InputsCache.cs b/SharpGenTools.Sdk/SharpGenTask.InputsCache.cs new file mode 100644 index 00000000..5856f6ad --- /dev/null +++ b/SharpGenTools.Sdk/SharpGenTask.InputsCache.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using SharpGenTools.Sdk.Internal; + +namespace SharpGenTools.Sdk; + +public sealed partial class SharpGenTask +{ + private const string InputsCacheMarkerFiles = "# Files"; + private const string InputsCacheMarkerEnvironmentVariables = "# Environment variables"; + private readonly List inputsCacheListFiles = new(); + private readonly List inputsCacheListEnvironmentVariables = new(); + private readonly HashSet inputsCacheSetFiles = new(); + private readonly HashSet inputsCacheSetEnvironmentVariables = new(); + private readonly List inputsCacheMetadataFiles = new(); + private readonly List inputsCacheMetadataEnvironmentVariables = new(); + private bool inputsCacheImmutable; + private static readonly Func ComputeInputsCacheFileMetadataFunc = ComputeInputsCacheFileMetadata; + private static readonly Func ComputeInputsCacheEnvironmentVariableMetadataFunc = ComputeInputsCacheEnvironmentVariableMetadata; + + private void GenerateInputsCache() + { + inputsCacheImmutable = true; + + using CacheFile cacheFile = new(new FileInfo(InputsCache)); + + { + using var writer = cacheFile.StreamWriter; + + writer.WriteLine(InputsCacheMarkerFiles); + for (int i = 0, count = inputsCacheListFiles.Count; i < count; i++) + { + writer.Write(inputsCacheMetadataFiles[i]); + writer.Write(' '); + writer.WriteLine(inputsCacheListFiles[i]); + } + + writer.WriteLine(InputsCacheMarkerEnvironmentVariables); + for (int i = 0, count = inputsCacheListEnvironmentVariables.Count; i < count; i++) + { + writer.Write(inputsCacheListEnvironmentVariables[i]); + writer.Write(' '); + writer.WriteLine(inputsCacheMetadataEnvironmentVariables[i]); + } + } + + SharpGenLogger.Message( + cacheFile.State switch + { + CacheFile.CacheState.Hit => "Input file cache is already up-to-date.", + CacheFile.CacheState.Miss => "Input file cache is out-of-date.", + CacheFile.CacheState.Absent => "Input file cache doesn't exist.", + _ => throw new ArgumentOutOfRangeException() + } + ); + + if (cacheFile.IsWriteNeeded) + cacheFile.Write(); + } + + private static string ComputeInputsCacheFileMetadata(string path) + { + FileInfo file = new(path); + Debug.Assert(file.Exists); + + return $"{file.CreationTimeUtc.Ticks} {file.LastWriteTimeUtc.Ticks} {file.Length}"; + } + + private static string ComputeInputsCacheEnvironmentVariableMetadata(string name) => + Environment.GetEnvironmentVariable(name) is { } value ? $"true {value}" : "false"; + + private void AddInputsCacheItem(string item, List itemList, HashSet itemSet, + List metadataList, Func metadataFunc) + { + Debug.Assert(!inputsCacheImmutable); + + if (string.IsNullOrWhiteSpace(item)) + return; + + var metadata = metadataFunc(item); + if (itemSet.Add(item)) + { + itemList.Add(item); + metadataList.Add(metadata); + } + else + { + bool Predicate(string x) => string.Equals(x, item); + + var index = itemList.FindIndex(Predicate); + Debug.Assert(index != -1); + Debug.Assert(metadataList[index] == metadata); + } + } + + private void AddInputsCacheFile(string path) => AddInputsCacheItem( + Utilities.FixFilePath(path, Utilities.EmptyFilePathBehavior.Ignore), + inputsCacheListFiles, inputsCacheSetFiles, inputsCacheMetadataFiles, + ComputeInputsCacheFileMetadataFunc + ); + + private void AddInputsCacheEnvironmentVariable(string name) => AddInputsCacheItem( + name, inputsCacheListEnvironmentVariables, inputsCacheSetEnvironmentVariables, + inputsCacheMetadataEnvironmentVariables, ComputeInputsCacheEnvironmentVariableMetadataFunc + ); + + private void AddInputsCacheFiles(IEnumerable paths) + { + foreach (var path in paths) + AddInputsCacheFile(path); + } + + private bool IsInputsCacheValid() + { + if (!File.Exists(InputsCache)) + return false; + + bool files = false, env = false; + + foreach (var line in File.ReadLines(InputsCache, DefaultEncoding)) + { + if (string.IsNullOrWhiteSpace(line)) + continue; + + if (string.Equals(line, InputsCacheMarkerFiles)) + { + files = true; + env = false; + } + else if (string.Equals(line, InputsCacheMarkerEnvironmentVariables)) + { + files = false; + env = true; + } + else if (files) + { + var items = line.Split(SpaceSeparator, 4); + long creation = long.Parse(items[0]), + modified = long.Parse(items[1]), + size = long.Parse(items[2]); + FileInfo file = new(items[3]); + + if (!file.Exists) + return false; + + if (file.Length != size) + return false; + + if (file.CreationTimeUtc.Ticks != creation) + return false; + + if (file.LastWriteTimeUtc.Ticks != modified) + return false; + } + else if (env) + { + var items = line.Split(SpaceSeparator, 3); + + if (!bool.TryParse(items[1], out var nonNull)) + return false; + + var value = Environment.GetEnvironmentVariable(items[0]); + + if (value is not null != nonNull) + return false; + + if (nonNull && value != items[2]) + return false; + } + else + { + // Unsupported block, either some future SharpGen version wrote this file, + // or the file data is simply corrupt. Either way, invalidate the cache. + return false; + } + } + + return true; + } +} \ No newline at end of file diff --git a/SharpGenTools.Sdk/SharpGenTask.PropertyCache.cs b/SharpGenTools.Sdk/SharpGenTask.PropertyCache.cs new file mode 100644 index 00000000..792662c3 --- /dev/null +++ b/SharpGenTools.Sdk/SharpGenTask.PropertyCache.cs @@ -0,0 +1,253 @@ +#nullable enable + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using Microsoft.Build.Framework; +using SharpGenTools.Sdk.Internal; + +namespace SharpGenTools.Sdk; + +public sealed partial class SharpGenTask +{ + private const int CacheFormatSignature = ('S' << 0) | ('G' << 8) | ('P' << 16) | ('C' << 24); + private const int CacheFormatVersion = 1; + private static readonly Encoding TextEncoding = Encoding.UTF8; + + private static HashAlgorithm CreateSettingsHash() => SHA256.Create(); + + private bool GeneratePropertyCache() + { + List parts = new(4) + { + "SharpGen" + }; + + if (!string.Equals(PlatformName, "AnyCPU", StringComparison.InvariantCultureIgnoreCase)) + parts.Add(PlatformName); + + if (!string.IsNullOrWhiteSpace(RuntimeIdentifier)) + parts.Add(RuntimeIdentifier); + + CacheFile cacheFile = new(); + try + { + { + using var writer = cacheFile.StreamWriter; + HashSettings(writer); + } + + var currentHash = Base64UrlEncode(cacheFile.ComputeHash(CreateSettingsHash())); + + parts.Add(currentHash); + + var profileName = string.Join("-", parts); + + workerLock = new Mutex(false, @"Global\" + profileName); + var cacheInvalid = false; + + try + { + while (!(workerLockAcquired = workerLock.WaitOne(TimeSpan.FromSeconds(5))) && !AbortExecution) + { + SharpGenLogger.Message($"Waiting for the worker to finish job {profileName}…"); + } + } + catch (AbandonedMutexException) + { + SharpGenLogger.Message($"The worker failed to complete job {profileName}."); + workerLockAcquired = true; + cacheInvalid = true; + } + + ProfilePath = Path.Combine(IntermediateOutputDirectory, profileName); + + if (!workerLockAcquired) + { + SharpGenLogger.Message($"Aborting the wait for job {profileName} to complete."); + Debug.Assert(AbortExecution); + return true; + } + + cacheFile.File = new FileInfo(PropertyCache); + Utilities.RequireAbsolutePath(PropertyCache, nameof(PropertyCache)); + + if (File.Exists(DirtyMarkerFile)) + { + SharpGenLogger.Message("Dirty marker exists, requesting full regeneration."); + cacheInvalid = true; + } + else if (!cacheInvalid) + { + cacheInvalid = cacheFile.IsWriteNeeded; + SharpGenLogger.Message( + cacheFile.State switch + { + CacheFile.CacheState.Hit => "Properties match cached value.", + CacheFile.CacheState.Miss => "Properties mismatch, writing a new property cache file.", + CacheFile.CacheState.Absent => "Properties cache doesn't exist.", + _ => throw new ArgumentOutOfRangeException() + } + ); + } + + if (cacheFile.IsWriteNeeded) + cacheFile.Write(); + + return cacheInvalid; + } + finally + { + cacheFile.Dispose(); + } + } + + private void HashSettings(StreamWriter writer) + { + writer.Write(CacheFormatSignature.ToString("X8")); + writer.Write('.'); + writer.Write(CacheFormatVersion); + writer.WriteLine(); + + WriteStringArray(CastXmlArguments); + WriteString(CastXmlExecutable); + WriteStringArray(ConfigFiles); + WriteString(ConsumerBindMappingConfigId); + WriteBool(DocumentationFailuresAsErrors); + WriteStringArray(ExtensionAssemblies); + WriteStringArray(ExternalDocumentation); + WriteTaskItems(GlobalNamespaceOverrides); + WriteStringArray(Macros); + WriteString(IntermediateOutputDirectory); + WriteString(PlatformName); + WriteString(RuntimeIdentifier); + WriteStringArray(Platforms); + WriteStringArray(SilenceMissingDocumentationErrorIdentifierPatterns); + + void WriteString(string? s, [CallerArgumentExpression("s")] string? name = null) + { + writer.Write(name); + writer.Write(": "); + writer.WriteLine(s ?? ""); + } + + void WriteBool(bool v, [CallerArgumentExpression("v")] string? name = null) + { + writer.Write(name); + writer.Write(": "); + writer.WriteLine(v); + } + + void WriteStringArray(IReadOnlyList? items, [CallerArgumentExpression("items")] string? name = null) + { + writer.Write(name); + writer.Write(":"); + + if (items is null) + { + writer.WriteLine(" "); + return; + } + + writer.WriteLine(); + + for (int i = 0, length = items.Count; i < length; i++) + { + writer.Write("* "); + writer.WriteLine(items[i]); + } + } + + void WriteTaskItem(ITaskItem? item, [CallerArgumentExpression("item")] string? name = null) + { + writer.Write(name); + writer.Write(": "); + + if (item is null) + { + writer.WriteLine(""); + return; + } + + writer.WriteLine(item.ItemSpec); + + foreach (DictionaryEntry entry in item.CloneCustomMetadata()) + { + writer.Write(name); + writer.Write("["); + writer.Write(entry.Key.ToString() ?? ""); + writer.Write("]: "); + writer.WriteLine(entry.Value?.ToString() ?? ""); + } + } + + void WriteTaskItems(IReadOnlyList? items, [CallerArgumentExpression("items")] string? name = null) + { + writer.Write(name); + writer.Write(":"); + + if (items is null) + { + writer.WriteLine(" "); + return; + } + + writer.WriteLine(); + + for (int i = 0, length = items.Count; i < length; i++) + { + WriteTaskItem(items[i], $"{name}[{i}]"); + } + } + } + + private static string Base64UrlEncode(byte[] input) + { + if (input == null) + throw new ArgumentNullException(nameof(input)); + + // Special-case empty input + var count = input.Length; + if (count == 0) + return string.Empty; + + var numWholeOrPartialInputBlocks = checked(count + 2) / 3; + var buffer = new char[checked(numWholeOrPartialInputBlocks * 4)]; + var numBase64Chars = Base64UrlEncode(input, buffer, count); + + return new string(buffer, 0, numBase64Chars); + } + + private static int Base64UrlEncode(byte[] input, char[] output, int count) + { + // Use base64url encoding with no padding characters. See RFC 4648, Sec. 5. + + // Start with default Base64 encoding. + var numBase64Chars = Convert.ToBase64CharArray(input, 0, count, output, 0); + + // Fix up '+' -> '-' and '/' -> '_'. Drop padding characters. + for (var i = 0; i < numBase64Chars; i++) + { + switch (output[i]) + { + case '+': + output[i] = '-'; + break; + case '/': + output[i] = '_'; + break; + case '=': + // We've reached a padding character; truncate the remainder. + return i; + } + } + + return numBase64Chars; + } +} \ No newline at end of file diff --git a/SharpGenTools.Sdk/Tasks/SharpGenTask.cs b/SharpGenTools.Sdk/SharpGenTask.cs similarity index 81% rename from SharpGenTools.Sdk/Tasks/SharpGenTask.cs rename to SharpGenTools.Sdk/SharpGenTask.cs index 93146ab1..9f1411b2 100644 --- a/SharpGenTools.Sdk/Tasks/SharpGenTask.cs +++ b/SharpGenTools.Sdk/SharpGenTask.cs @@ -27,18 +27,19 @@ using Logger = SharpGen.Logging.Logger; using SdkResolver = SharpGen.Parser.SdkResolver; -namespace SharpGenTools.Sdk.Tasks; +namespace SharpGenTools.Sdk; public sealed partial class SharpGenTask : Task, ICancelableTask { // Default encoding used by MSBuild ReadLinesFromFile task - private static readonly Encoding DefaultEncoding = new UTF8Encoding(false, true); + internal static readonly Encoding DefaultEncoding = new UTF8Encoding(false, true); + private static readonly char[] SpaceSeparator = { ' ' }; private readonly IocServiceContainer serviceContainer = new(); private readonly Ioc ioc = new(); private string? profilePath; private volatile bool isCancellationRequested; private Mutex? workerLock; - private bool workerLockAcquired; + private bool workerLockAcquired, regenerationStarted; // ReSharper disable MemberCanBePrivate.Global, UnusedAutoPropertyAccessor.Global [Required] public string[]? CastXmlArguments { get; set; } @@ -102,6 +103,11 @@ private void WaitForDebuggerAttach() public void Cancel() => isCancellationRequested = true; + private static string[] PreprocessPathListProperty(string[] items) => + items.Distinct(StringComparer.OrdinalIgnoreCase) + .OrderBy(static x => x, StringComparer.OrdinalIgnoreCase) + .ToArray(); + public override bool Execute() { BindingRedirectResolution.Enable(); @@ -117,29 +123,24 @@ public override bool Execute() try { - if (!GeneratePropertyCache()) - return success = true; - - if (AbortExecution) - return success = false; - - serviceContainer.AddService(SharpGenLogger); - serviceContainer.AddService(); - serviceContainer.AddService(); - serviceContainer.AddService(new TypeRegistry(ioc)); - ioc.ConfigureServices(serviceContainer); - - ExtensibilityDriver.Instance.LoadExtensions(SharpGenLogger, ExtensionAssemblies.ToArray()); + ConfigFiles = PreprocessPathListProperty(ConfigFiles); + ExtensionAssemblies = PreprocessPathListProperty(ExtensionAssemblies); + ExternalDocumentation = PreprocessPathListProperty(ExternalDocumentation); - ConfigFile config = new() + if (GeneratePropertyCache()) + { + return success = ExecuteImpl(); + } + else { - Files = ConfigFiles.ToList(), - Id = "SharpGen-MSBuild" - }; + if (AbortExecution) + return success = false; - LoadConfig(config); + if (IsInputsCacheValid()) + return success = true; - return success = !AbortExecution && Execute(config); + return success = ExecuteImpl(); + } } catch (CodeGenFailedException ex) { @@ -156,7 +157,25 @@ public override bool Execute() } catch (Exception e) { - SharpGenLogger.Fatal("Internal SharpGen exception while generating NuGet package properties file", e); + SharpGenLogger.LogRawMessage( + LogLevel.Warning, null, + "Internal SharpGen exception while generating NuGet package properties file", e + ); + } + + if (regenerationStarted) + { + try + { + GenerateInputsCache(); + } + catch (Exception e) + { + SharpGenLogger.LogRawMessage( + LogLevel.Warning, null, + "Internal SharpGen exception while generating input file cache", e + ); + } } if (success && !AbortExecution && File.Exists(DirtyMarkerFile)) @@ -172,13 +191,38 @@ public override bool Execute() private bool AbortExecution => SharpGenLogger.HasErrors || isCancellationRequested; - private bool Execute(ConfigFile config) + private bool ExecuteImpl() { + File.WriteAllBytes(DirtyMarkerFile, Array.Empty()); + regenerationStarted = true; + + if (AbortExecution) + return false; + + serviceContainer.AddService(SharpGenLogger); + serviceContainer.AddService(); + serviceContainer.AddService(); + serviceContainer.AddService(new TypeRegistry(ioc)); + ioc.ConfigureServices(serviceContainer); + + ExtensibilityDriver.Instance.LoadExtensions(SharpGenLogger, ExtensionAssemblies); + AddInputsCacheFiles(ExtensionAssemblies); + + ConfigFile config = new() + { + Files = ConfigFiles.ToList(), + Id = "SharpGen-MSBuild" + }; + + LoadConfig(config); + config.GetFilesWithIncludesAndExtensionHeaders( out var configsWithHeaders, out var configsWithExtensionHeaders ); + AddInputsCacheFiles(config.ConfigFilesLoaded.Select(x => x.AbsoluteFilePath)); + CppHeaderGenerator cppHeaderGenerator = new(ProfilePath, ioc); var cppHeaderGenerationResult = cppHeaderGenerator.GenerateCppHeaders(config, configsWithHeaders, configsWithExtensionHeaders); @@ -189,31 +233,23 @@ out var configsWithExtensionHeaders IncludeDirectoryResolver resolver = new(ioc); resolver.Configure(config); + AddInputsCacheFile(CastXmlExecutable); CastXmlRunner castXml = new(resolver, CastXmlExecutable, CastXmlArguments, ioc) { OutputPath = ProfilePath }; - var macroManager = new MacroManager(castXml); - - var cppExtensionGenerator = new CppExtensionHeaderGenerator(); - var module = config.CreateSkeletonModule(); + MacroManager macroManager = new(castXml); macroManager.Parse(Path.Combine(ProfilePath, config.HeaderFileName), module); + AddInputsCacheFiles(macroManager.IncludedFiles); - cppExtensionGenerator.GenerateExtensionHeaders( + new CppExtensionHeaderGenerator().GenerateExtensionHeaders( config, ProfilePath, module, configsWithExtensionHeaders, cppHeaderGenerationResult.UpdatedConfigs ); - GenerateInputsCache( - macroManager.IncludedFiles - .Concat(config.ConfigFilesLoaded.Select(x => x.AbsoluteFilePath)) - .Concat(configsWithExtensionHeaders.Select(x => Path.Combine(ProfilePath, x.ExtensionFileName))) - .Select(s => Utilities.FixFilePath(s, Utilities.EmptyFilePathBehavior.Ignore)) - .Where(x => x != null) - .Distinct() - ); + AddInputsCacheFiles(configsWithExtensionHeaders.Select(x => Path.Combine(ProfilePath, x.ExtensionFileName))); if (AbortExecution) return false; @@ -408,6 +444,7 @@ out var configsWithExtensionHeaders var documentationFiles = new Dictionary(); + AddInputsCacheFiles(ExternalDocumentation); foreach (var file in ExternalDocumentation) { using var stream = File.OpenRead(file); @@ -465,18 +502,14 @@ private void GenerateConfigForConsumers(ConfigFile consumerConfig) consumerConfig.Write(consumerBindMapping); } - private void GenerateInputsCache(IEnumerable paths) - { - using var inputCacheStream = new StreamWriter(InputsCache, false, DefaultEncoding); - - foreach (var path in paths) inputCacheStream.WriteLine(path); - } - private void LoadConfig(ConfigFile config) { config.Load(null, Macros, SharpGenLogger); - var sdkResolver = new SdkResolver(SharpGenLogger); + AddInputsCacheEnvironmentVariable("SHARPGEN_VS_OVERRIDE"); + AddInputsCacheEnvironmentVariable("SHARPGEN_SDK_OVERRIDE"); + + SdkResolver sdkResolver = new(SharpGenLogger); SharpGenLogger.Message("Resolving SDKs..."); foreach (var cfg in config.ConfigFilesLoaded) { @@ -495,10 +528,10 @@ private void LoadConfig(ConfigFile config) private void GeneratePackagesProps() { - using MemoryStream stream = new(); - { - using var writer = new StreamWriter(stream, DefaultEncoding, -1, true); + using CacheFile cacheFile = new(new FileInfo(PackagePropsFile)); + { + using var writer = cacheFile.StreamWriter; writer.WriteLine(@""); writer.WriteLine(@" "); @@ -509,38 +542,17 @@ private void GeneratePackagesProps() writer.WriteLine(@""); } - bool writeNeeded; - - if (File.Exists(PackagePropsFile)) - { - ReadOnlySpan cachedHash = File.ReadAllBytes(PackagePropsFile); - - stream.Position = 0; - var success = stream.TryGetBuffer(out var buffer); - Debug.Assert(success); - - if (buffer.AsSpan().SequenceEqual(cachedHash)) - { - SharpGenLogger.Message("NuGet package properties file is already up-to-date."); - writeNeeded = false; - } - else + SharpGenLogger.Message( + cacheFile.State switch { - SharpGenLogger.Message("NuGet package properties file is out-of-date."); - writeNeeded = true; + CacheFile.CacheState.Hit => "NuGet package properties file is already up-to-date.", + CacheFile.CacheState.Miss => "NuGet package properties file is out-of-date.", + CacheFile.CacheState.Absent => "NuGet package properties file doesn't exist.", + _ => throw new ArgumentOutOfRangeException() } - } - else - { - SharpGenLogger.Message("NuGet package properties file doesn't exist."); - writeNeeded = true; - } + ); - if (writeNeeded) - { - stream.Position = 0; - using var file = File.Open(PackagePropsFile, FileMode.Create, FileAccess.Write); - stream.CopyTo(file); - } + if (cacheFile.IsWriteNeeded) + cacheFile.Write(); } } \ No newline at end of file diff --git a/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj b/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj index 20fbb5a3..5258983b 100644 --- a/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj +++ b/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj @@ -33,6 +33,15 @@ + + Code + + + Code + + + Code + diff --git a/SharpGenTools.Sdk/Tasks/SharpGenTask.PropertyCache.cs b/SharpGenTools.Sdk/Tasks/SharpGenTask.PropertyCache.cs deleted file mode 100644 index 95acf1d6..00000000 --- a/SharpGenTools.Sdk/Tasks/SharpGenTask.PropertyCache.cs +++ /dev/null @@ -1,267 +0,0 @@ -#nullable enable - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Runtime.CompilerServices; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using Microsoft.Build.Framework; -using SharpGenTools.Sdk.Internal; - -namespace SharpGenTools.Sdk.Tasks; - -public sealed partial class SharpGenTask -{ - private const int CacheFormatSignature = ('S' << 0) | ('G' << 8) | ('P' << 16) | ('C' << 24); - private const int CacheFormatVersion = 1; - private static readonly Encoding TextEncoding = Encoding.UTF8; - - private static HashAlgorithm CreateSettingsHash() => SHA256.Create(); - - private bool GeneratePropertyCache() - { - List parts = new(4) - { - "SharpGen" - }; - - if (!string.Equals(PlatformName, "AnyCPU", StringComparison.InvariantCultureIgnoreCase)) - parts.Add(PlatformName); - - if (!string.IsNullOrWhiteSpace(RuntimeIdentifier)) - parts.Add(RuntimeIdentifier); - - using MemoryStream stream = new(); - var currentHash = HashSettings(stream); - - parts.Add(currentHash); - - var profileName = string.Join("-", parts); - - workerLock = new Mutex(false, @"Global\" + profileName); - var writeNeeded = false; - - try - { - while (!(workerLockAcquired = workerLock.WaitOne(TimeSpan.FromSeconds(5))) && !AbortExecution) - { - SharpGenLogger.Message($"Waiting for the worker to finish job {profileName}…"); - } - } - catch (AbandonedMutexException) - { - SharpGenLogger.Message($"The worker failed to complete job {profileName}."); - workerLockAcquired = true; - writeNeeded = true; - } - - ProfilePath = Path.Combine(IntermediateOutputDirectory, profileName); - - if (!workerLockAcquired) - { - SharpGenLogger.Message($"Aborting the wait for job {profileName} to complete."); - Debug.Assert(AbortExecution); - return true; - } - - var cacheItemSpec = PropertyCache; - - Utilities.RequireAbsolutePath(cacheItemSpec, nameof(PropertyCache)); - - if (File.Exists(DirtyMarkerFile)) - { - SharpGenLogger.Message("Dirty marker exists, requesting full regeneration."); - writeNeeded = true; - } - else if (File.Exists(cacheItemSpec)) - { - ReadOnlySpan cachedHash = File.ReadAllBytes(cacheItemSpec); - - stream.Position = 0; - var success = stream.TryGetBuffer(out var buffer); - Debug.Assert(success); - - if (buffer.AsSpan().SequenceEqual(cachedHash)) - { - SharpGenLogger.Message("Properties match cached value."); - } - else - { - SharpGenLogger.Message("Properties mismatch, writing a new property cache file."); - writeNeeded = true; - } - } - else - { - SharpGenLogger.Message("Properties cache doesn't exist."); - writeNeeded = true; - } - - if (writeNeeded) - { - stream.Position = 0; - Directory.CreateDirectory(ProfilePath); - File.WriteAllBytes(DirtyMarkerFile, Array.Empty()); - using var file = File.Open(cacheItemSpec, FileMode.Create, FileAccess.Write); - stream.CopyTo(file); - } - - return writeNeeded; - } - - private string HashSettings(Stream stream) - { - { - using var writer = new StreamWriter(stream, TextEncoding, -1, true); - - writer.Write(CacheFormatSignature.ToString("X8")); - writer.Write('.'); - writer.Write(CacheFormatVersion); - writer.WriteLine(); - - WriteStringArray(CastXmlArguments); - WriteString(CastXmlExecutable); - WriteStringArray(ConfigFiles); - WriteString(ConsumerBindMappingConfigId); - WriteBool(DocumentationFailuresAsErrors); - WriteStringArray(ExtensionAssemblies); - WriteStringArray(ExternalDocumentation); - WriteTaskItems(GlobalNamespaceOverrides); - WriteStringArray(Macros); - WriteString(IntermediateOutputDirectory); - WriteString(PlatformName); - WriteString(RuntimeIdentifier); - WriteStringArray(Platforms); - WriteStringArray(SilenceMissingDocumentationErrorIdentifierPatterns); - - void WriteString(string? s, [CallerArgumentExpression("s")] string? name = null) - { - writer.Write(name); - writer.Write(": "); - writer.WriteLine(s ?? ""); - } - - void WriteBool(bool v, [CallerArgumentExpression("v")] string? name = null) - { - writer.Write(name); - writer.Write(": "); - writer.WriteLine(v); - } - - void WriteStringArray(IReadOnlyList? items, [CallerArgumentExpression("items")] string? name = null) - { - writer.Write(name); - writer.Write(":"); - - if (items is null) - { - writer.WriteLine(" "); - return; - } - - writer.WriteLine(); - - for (int i = 0, length = items.Count; i < length; i++) - { - writer.Write("* "); - writer.WriteLine(items[i]); - } - } - - void WriteTaskItem(ITaskItem? item, [CallerArgumentExpression("item")] string? name = null) - { - writer.Write(name); - writer.Write(": "); - - if (item is null) - { - writer.WriteLine(""); - return; - } - - writer.WriteLine(item.ItemSpec); - - foreach (DictionaryEntry entry in item.CloneCustomMetadata()) - { - writer.Write(name); - writer.Write("["); - writer.Write(entry.Key.ToString() ?? ""); - writer.Write("]: "); - writer.WriteLine(entry.Value?.ToString() ?? ""); - } - } - - void WriteTaskItems(IReadOnlyList? items, [CallerArgumentExpression("items")] string? name = null) - { - writer.Write(name); - writer.Write(":"); - - if (items is null) - { - writer.WriteLine(" "); - return; - } - - writer.WriteLine(); - - for (int i = 0, length = items.Count; i < length; i++) - { - WriteTaskItem(items[i], $"{name}[{i}]"); - } - } - } - - stream.Position = 0; - - using var hash = CreateSettingsHash(); - return Base64UrlEncode(hash.ComputeHash(stream)); - } - - private static string Base64UrlEncode(byte[] input) - { - if (input == null) - throw new ArgumentNullException(nameof(input)); - - // Special-case empty input - var count = input.Length; - if (count == 0) - return string.Empty; - - var numWholeOrPartialInputBlocks = checked(count + 2) / 3; - var buffer = new char[checked(numWholeOrPartialInputBlocks * 4)]; - var numBase64Chars = Base64UrlEncode(input, buffer, count); - - return new string(buffer, 0, numBase64Chars); - } - - private static int Base64UrlEncode(byte[] input, char[] output, int count) - { - // Use base64url encoding with no padding characters. See RFC 4648, Sec. 5. - - // Start with default Base64 encoding. - var numBase64Chars = Convert.ToBase64CharArray(input, 0, count, output, 0); - - // Fix up '+' -> '-' and '/' -> '_'. Drop padding characters. - for (var i = 0; i < numBase64Chars; i++) - { - switch (output[i]) - { - case '+': - output[i] = '-'; - break; - case '/': - output[i] = '_'; - break; - case '=': - // We've reached a padding character; truncate the remainder. - return i; - } - } - - return numBase64Chars; - } -} \ No newline at end of file diff --git a/build.ps1 b/build.ps1 index f0c7b323..2c9dadfc 100644 --- a/build.ps1 +++ b/build.ps1 @@ -99,7 +99,7 @@ if (!$SkipOuterloopTests -and !($env:ReleaseTag -and ($Configuration -eq "Releas $managedTests = "Interface", "Struct", "Functions" - $tfms = "net5.0" # "net472", "netcoreapp2.1", "net5.0" + $tfms = "net472", "netcoreapp2.1", "net5.0" $platforms = "x86", "x64" $matrix = CartesianProduct-Lists @($tfms, $platforms) From 5971785813c7bc3030ccd06cbb85fd237362cf92 Mon Sep 17 00:00:00 2001 From: Andrew Boyarshin Date: Thu, 28 Apr 2022 21:00:51 +0700 Subject: [PATCH 11/55] Fix Roslyn bug blocking documentation generation --- SharpGen.UnitTests/RoslynGeneratorTests.cs | 72 + SharpGen.UnitTests/TestBase.cs | 5 + SharpGen/Generator/RoslynGenerator.cs | 17 +- SharpGen/Generator/RoslynSyntaxNormalizer.cs | 1205 +++++++++++++++++ .../SharpGenTask.PropertyCache.cs | 1 - SharpGenTools.Sdk/SharpGenTask.cs | 6 +- 6 files changed, 1297 insertions(+), 9 deletions(-) create mode 100644 SharpGen.UnitTests/RoslynGeneratorTests.cs create mode 100644 SharpGen/Generator/RoslynSyntaxNormalizer.cs diff --git a/SharpGen.UnitTests/RoslynGeneratorTests.cs b/SharpGen.UnitTests/RoslynGeneratorTests.cs new file mode 100644 index 00000000..59924b26 --- /dev/null +++ b/SharpGen.UnitTests/RoslynGeneratorTests.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Xml; +using Microsoft.CodeAnalysis.CSharp; +using SharpGen.Generator; +using SharpGen.Model; +using SharpGen.Transform; +using Xunit; +using Xunit.Abstractions; + +namespace SharpGen.UnitTests; + +public class RoslynGeneratorTests : TestBase +{ + public RoslynGeneratorTests(ITestOutputHelper outputHelper) : base(outputHelper) + { + } + + [Fact] + public void IncludeXmlDocIsNotMangled() + { + CsAssembly assembly = new(); + CsNamespace @namespace = new("Test"); + @namespace.Add( + new CsStruct(null, "PAIR") + { + CppElementName = "PAIR" + } + ); + assembly.Add(@namespace); + + XmlDocument docs = new(); + docs.LoadXml(@"Test"); + + AddIocServices(CreateExternalDocCommentsReader(docs)); + + Assert.Collection( + GenerateLines(assembly), + x => Assert.Equal(@"// ", x), + x => Assert.Equal(@"namespace Test", x), + x => Assert.Equal(@"{", x), + x => Assert.Equal(@"/// ", x), + x => Assert.Equal(@"/// PAIR", x), + x => Assert.Equal(@"/// PAIR", x), + x => Assert.Equal(@"[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 0, CharSet = System.Runtime.InteropServices.CharSet.Unicode)]", x), + x => Assert.Equal(@"public partial struct PAIR", x), + x => Assert.Equal(@"{", x), + x => Assert.Equal(@"}", x), + x => Assert.Equal(@"}", x) + ); + } + + private static Action CreateExternalDocCommentsReader(XmlDocument docs) + { + return container => + container.AddService(new ExternalDocCommentsReader(new Dictionary { ["doc.xml"] = docs })); + } + + private Action CreateDefaultGenerators() + { + return container => container.AddService(new DefaultGenerators(Ioc)); + } + + private string[] GenerateLines(CsAssembly assembly) + { + AddIocServices(CreateDefaultGenerators()); + + var root = new RoslynGenerator().Run(assembly, Ioc).GetCompilationUnitRoot(); + return root.ToFullString() + .Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + } +} \ No newline at end of file diff --git a/SharpGen.UnitTests/TestBase.cs b/SharpGen.UnitTests/TestBase.cs index f6f5dca7..81115d85 100644 --- a/SharpGen.UnitTests/TestBase.cs +++ b/SharpGen.UnitTests/TestBase.cs @@ -24,6 +24,11 @@ protected TestBase(ITestOutputHelper outputHelper) Ioc.ConfigureServices(serviceContainer); } + protected void AddIocServices(Action action) + { + action(serviceContainer); + } + protected IDisposable LoggerEnvironment(LoggerAssertHandler handler) { return new LoggerTestEnvironment(loggerImpl, handler); diff --git a/SharpGen/Generator/RoslynGenerator.cs b/SharpGen/Generator/RoslynGenerator.cs index ebb2d9f6..cf717768 100644 --- a/SharpGen/Generator/RoslynGenerator.cs +++ b/SharpGen/Generator/RoslynGenerator.cs @@ -18,7 +18,7 @@ public sealed class RoslynGenerator SingletonSeparatedList(Attribute(ParseName("System.Runtime.CompilerServices.ModuleInitializerAttribute"))) ); - public string Run(CsAssembly csAssembly, Ioc ioc) + public SyntaxTree Run(CsAssembly csAssembly, Ioc ioc) { var logger = ioc.Logger; var generators = ioc.Generators; @@ -50,11 +50,16 @@ MemberDeclarationSyntax NamespaceSelector(CsNamespace ns) } return CSharpSyntaxTree.Create( - CompilationUnit( - default, default, default, - List(csAssembly.Namespaces.Select(NamespaceSelector)).AddRange(moduleInitializer) - ).NormalizeWhitespace(elasticTrivia: true) - ).GetCompilationUnitRoot().ToFullString(); + RoslynSyntaxNormalizer.Normalize( + CompilationUnit( + default, default, default, + List(csAssembly.Namespaces.Select(NamespaceSelector)).AddRange(moduleInitializer) + ), + " ", + "\r\n", + true + ) + ); } private MethodDeclarationSyntax GenerateResultDescriptor(CsResultConstant[] descriptors, Ioc ioc) diff --git a/SharpGen/Generator/RoslynSyntaxNormalizer.cs b/SharpGen/Generator/RoslynSyntaxNormalizer.cs new file mode 100644 index 00000000..3472f536 --- /dev/null +++ b/SharpGen/Generator/RoslynSyntaxNormalizer.cs @@ -0,0 +1,1205 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace SharpGen.Generator; + +internal sealed class RoslynSyntaxNormalizer : CSharpSyntaxRewriter +{ + private readonly TextSpan _consideredSpan; + private readonly int _initialDepth; + private readonly string _indentWhitespace; + private readonly bool _useElasticTrivia; + private readonly SyntaxTrivia _eolTrivia; + + private bool _isInStructuredTrivia; + + private SyntaxToken _previousToken; + + private bool _afterLineBreak; + private bool _afterIndentation; + private bool _inSingleLineInterpolation; + + // CONSIDER: if we become concerned about space, we shouldn't actually need any + // of the values between indentations[0] and indentations[initialDepth] (exclusive). + private ImmutableArray.Builder? _indentations; + + private RoslynSyntaxNormalizer(TextSpan consideredSpan, int initialDepth, string indentWhitespace, + string eolWhitespace, bool useElasticTrivia) + : base(visitIntoStructuredTrivia: true) + { + _consideredSpan = consideredSpan; + _initialDepth = initialDepth; + _indentWhitespace = indentWhitespace; + _useElasticTrivia = useElasticTrivia; + _eolTrivia = useElasticTrivia + ? SyntaxFactory.ElasticEndOfLine(eolWhitespace) + : SyntaxFactory.EndOfLine(eolWhitespace); + _afterLineBreak = true; + } + + internal static TNode Normalize(TNode node, string indentWhitespace, string eolWhitespace, + bool useElasticTrivia = false) + where TNode : SyntaxNode + { + var normalizer = new RoslynSyntaxNormalizer(node.FullSpan, GetDeclarationDepth(node), indentWhitespace, + eolWhitespace, useElasticTrivia); + return (TNode) normalizer.Visit(node); + } + + public override SyntaxToken VisitToken(SyntaxToken token) + { + if (token.Kind() == SyntaxKind.None || (token.IsMissing && token.FullSpan.Length == 0)) + { + return token; + } + + try + { + var tk = token; + + var depth = GetDeclarationDepth(token); + + tk = tk.WithLeadingTrivia(RewriteTrivia( + token.LeadingTrivia, + depth, + isTrailing: false, + indentAfterLineBreak: NeedsIndentAfterLineBreak(token), + mustHaveSeparator: false, + lineBreaksAfter: 0)); + + var nextToken = this.GetNextRelevantToken(token); + + _afterLineBreak = IsLineBreak(token); + _afterIndentation = false; + + var lineBreaksAfter = LineBreaksAfter(token, nextToken); + var needsSeparatorAfter = NeedsSeparator(token, nextToken); + tk = tk.WithTrailingTrivia(RewriteTrivia( + token.TrailingTrivia, + depth, + isTrailing: true, + indentAfterLineBreak: false, + mustHaveSeparator: needsSeparatorAfter, + lineBreaksAfter: lineBreaksAfter)); + + return tk; + } + finally + { + // to help debugging + _previousToken = token; + } + } + + private SyntaxToken GetNextRelevantToken(SyntaxToken token) + { + // get next token, skipping zero width tokens except for end-of-directive tokens + var nextToken = token; + + do + { + nextToken = nextToken.GetNextToken(true, true); + } while (nextToken != default && nextToken.Span.Length == 0 && nextToken.Kind() != SyntaxKind.EndOfDirectiveToken); + + return _consideredSpan.Contains(nextToken.FullSpan) ? nextToken : default; + } + + private SyntaxTrivia GetIndentation(int count) + { + count = Math.Max(count - _initialDepth, 0); + + int capacity = count + 1; + if (_indentations == null) + { + _indentations = ImmutableArray.CreateBuilder(capacity); + } + + // grow indentation collection if necessary + for (int i = _indentations.Count; i <= count; i++) + { + string text = i == 0 + ? "" + : _indentations[i - 1] + _indentWhitespace; + _indentations.Add( + _useElasticTrivia ? SyntaxFactory.ElasticWhitespace(text) : SyntaxFactory.Whitespace(text)); + } + + return _indentations[count]; + } + + private static bool NeedsIndentAfterLineBreak(SyntaxToken token) + { + return !token.IsKind(SyntaxKind.EndOfFileToken); + } + + private int LineBreaksAfter(SyntaxToken currentToken, SyntaxToken nextToken) + { + if (_inSingleLineInterpolation) + { + return 0; + } + + if (currentToken.IsKind(SyntaxKind.EndOfDirectiveToken)) + { + return 1; + } + + if (nextToken.Kind() == SyntaxKind.None) + { + return 0; + } + + // none of the following tests currently have meaning for structured trivia + if (_isInStructuredTrivia) + { + return 0; + } + + if (nextToken.IsKind(SyntaxKind.CloseBraceToken) && + IsAccessorListWithoutAccessorsWithBlockBody(currentToken.Parent?.Parent)) + { + return 0; + } + + switch (currentToken.Kind()) + { + case SyntaxKind.None: + return 0; + + case SyntaxKind.OpenBraceToken: + return LineBreaksAfterOpenBrace(currentToken, nextToken); + + case SyntaxKind.FinallyKeyword: + return 1; + + case SyntaxKind.CloseBraceToken: + return LineBreaksAfterCloseBrace(currentToken, nextToken); + + case SyntaxKind.CloseParenToken: + if (currentToken.Parent is PositionalPatternClauseSyntax) + { + //don't break inside a recursive pattern + return 0; + } + + // Note: the `where` case handles constraints on method declarations + // and also `where` clauses (consistently with other LINQ cases below) + return (currentToken.Parent is StatementSyntax && nextToken.Parent != currentToken.Parent) + || nextToken.Kind() == SyntaxKind.OpenBraceToken + || nextToken.Kind() == SyntaxKind.WhereKeyword + ? 1 + : 0; + + case SyntaxKind.CloseBracketToken: + if (currentToken.Parent is AttributeListSyntax && currentToken.Parent.Parent is not ParameterSyntax) + { + return 1; + } + + break; + + case SyntaxKind.SemicolonToken: + return LineBreaksAfterSemicolon(currentToken, nextToken); + + case SyntaxKind.CommaToken: + return currentToken.Parent is EnumDeclarationSyntax or SwitchExpressionSyntax ? 1 : 0; + case SyntaxKind.ElseKeyword: + return nextToken.Kind() != SyntaxKind.IfKeyword ? 1 : 0; + case SyntaxKind.ColonToken: + if (currentToken.Parent is LabeledStatementSyntax or SwitchLabelSyntax) + { + return 1; + } + + break; + case SyntaxKind.SwitchKeyword when currentToken.Parent is SwitchExpressionSyntax: + return 1; + } + + if ((nextToken.IsKind(SyntaxKind.FromKeyword) && nextToken.Parent.IsKind(SyntaxKind.FromClause)) || + (nextToken.IsKind(SyntaxKind.LetKeyword) && nextToken.Parent.IsKind(SyntaxKind.LetClause)) || + (nextToken.IsKind(SyntaxKind.WhereKeyword) && nextToken.Parent.IsKind(SyntaxKind.WhereClause)) || + (nextToken.IsKind(SyntaxKind.JoinKeyword) && nextToken.Parent.IsKind(SyntaxKind.JoinClause)) || + (nextToken.IsKind(SyntaxKind.JoinKeyword) && nextToken.Parent.IsKind(SyntaxKind.JoinIntoClause)) || + (nextToken.IsKind(SyntaxKind.OrderByKeyword) && nextToken.Parent.IsKind(SyntaxKind.OrderByClause)) || + (nextToken.IsKind(SyntaxKind.SelectKeyword) && nextToken.Parent.IsKind(SyntaxKind.SelectClause)) || + (nextToken.IsKind(SyntaxKind.GroupKeyword) && nextToken.Parent.IsKind(SyntaxKind.GroupClause))) + { + return 1; + } + + return nextToken.Kind() switch + { + SyntaxKind.OpenBraceToken => LineBreaksBeforeOpenBrace(nextToken), + SyntaxKind.CloseBraceToken => LineBreaksBeforeCloseBrace(nextToken), + SyntaxKind.ElseKeyword => 1, + SyntaxKind.FinallyKeyword => 1, + SyntaxKind.OpenBracketToken => nextToken.Parent is AttributeListSyntax && + nextToken.Parent.Parent is not ParameterSyntax + ? 1 + : 0, + SyntaxKind.WhereKeyword => currentToken.Parent is TypeParameterListSyntax ? 1 : 0, + _ => 0 + }; + } + + private static bool IsAccessorListWithoutAccessorsWithBlockBody(SyntaxNode? node) + => node is AccessorListSyntax accessorList && + accessorList.Accessors.All(a => a.Body == null); + + private static bool IsAccessorListFollowedByInitializer(SyntaxNode? node) + => node is AccessorListSyntax accessorList && + node.Parent is PropertyDeclarationSyntax { Initializer: { } }; + + private static int LineBreaksBeforeOpenBrace(SyntaxToken openBraceToken) + { + Debug.Assert(openBraceToken.IsKind(SyntaxKind.OpenBraceToken)); + if (openBraceToken.Parent.IsKind(SyntaxKind.Interpolation) || + openBraceToken.Parent is InitializerExpressionSyntax or PropertyPatternClauseSyntax || + IsAccessorListWithoutAccessorsWithBlockBody(openBraceToken.Parent)) + { + return 0; + } + + return 1; + } + + private static int LineBreaksBeforeCloseBrace(SyntaxToken closeBraceToken) + { + Debug.Assert(closeBraceToken.IsKind(SyntaxKind.CloseBraceToken)); + if (closeBraceToken.Parent.IsKind(SyntaxKind.Interpolation) || + closeBraceToken.Parent is InitializerExpressionSyntax or PropertyPatternClauseSyntax) + { + return 0; + } + + return 1; + } + + private static int LineBreaksAfterOpenBrace(SyntaxToken currentToken, SyntaxToken nextToken) + { + if (currentToken.Parent is InitializerExpressionSyntax or PropertyPatternClauseSyntax || + currentToken.Parent.IsKind(SyntaxKind.Interpolation) || + IsAccessorListWithoutAccessorsWithBlockBody(currentToken.Parent)) + { + return 0; + } + + return 1; + } + + private static int LineBreaksAfterCloseBrace(SyntaxToken currentToken, SyntaxToken nextToken) + { + if (currentToken.Parent is InitializerExpressionSyntax or SwitchExpressionSyntax + or PropertyPatternClauseSyntax || + currentToken.Parent.IsKind(SyntaxKind.Interpolation) || + currentToken.Parent?.Parent is AnonymousFunctionExpressionSyntax || + IsAccessorListFollowedByInitializer(currentToken.Parent)) + { + return 0; + } + + var kind = nextToken.Kind(); + switch (kind) + { + case SyntaxKind.EndOfFileToken: + case SyntaxKind.CloseBraceToken: + case SyntaxKind.CatchKeyword: + case SyntaxKind.FinallyKeyword: + case SyntaxKind.ElseKeyword: + return 1; + default: + if (kind == SyntaxKind.WhileKeyword && + nextToken.Parent.IsKind(SyntaxKind.DoStatement)) + { + return 1; + } + + return 2; + } + } + + private static int LineBreaksAfterSemicolon(SyntaxToken currentToken, SyntaxToken nextToken) + { + if (currentToken.Parent.IsKind(SyntaxKind.ForStatement)) + { + return 0; + } + + if (nextToken.Kind() == SyntaxKind.CloseBraceToken) + { + return 1; + } + + if (currentToken.Parent.IsKind(SyntaxKind.UsingDirective)) + { + return nextToken.Parent.IsKind(SyntaxKind.UsingDirective) ? 1 : 2; + } + + if (currentToken.Parent.IsKind(SyntaxKind.ExternAliasDirective)) + { + return nextToken.Parent.IsKind(SyntaxKind.ExternAliasDirective) ? 1 : 2; + } + + if (currentToken.Parent is AccessorDeclarationSyntax && + IsAccessorListWithoutAccessorsWithBlockBody(currentToken.Parent.Parent)) + { + return 0; + } + + return 1; + } + + private static bool NeedsSeparatorForPropertyPattern(SyntaxToken token, SyntaxToken next) + { + PropertyPatternClauseSyntax? propPattern; + if (token.Parent.IsKind(SyntaxKind.PropertyPatternClause)) + { + propPattern = (PropertyPatternClauseSyntax) token.Parent; + } + else if (next.Parent.IsKind(SyntaxKind.PropertyPatternClause)) + { + propPattern = (PropertyPatternClauseSyntax) next.Parent; + } + else + { + return false; + } + + var tokenIsOpenBrace = token.IsKind(SyntaxKind.OpenBraceToken); + var nextIsOpenBrace = next.IsKind(SyntaxKind.OpenBraceToken); + var tokenIsCloseBrace = token.IsKind(SyntaxKind.CloseBraceToken); + var nextIsCloseBrace = next.IsKind(SyntaxKind.CloseBraceToken); + + //inner + if (tokenIsOpenBrace) + { + return true; + } + + if (nextIsCloseBrace) + { + return true; + } + + if (propPattern.Parent is RecursivePatternSyntax rps) + { + //outer + if (nextIsOpenBrace) + { + if (rps.Type != null || rps.PositionalPatternClause != null) + { + return true; + } + + return false; + } + + if (tokenIsCloseBrace) + { + if (rps.Designation is null) + { + return false; + } + + return true; + } + } + + return false; + } + + private static bool NeedsSeparatorForPositionalPattern(SyntaxToken token, SyntaxToken next) + { + PositionalPatternClauseSyntax? posPattern; + if (token.Parent.IsKind(SyntaxKind.PositionalPatternClause)) + { + posPattern = (PositionalPatternClauseSyntax) token.Parent; + } + else if (next.Parent.IsKind(SyntaxKind.PositionalPatternClause)) + { + posPattern = (PositionalPatternClauseSyntax) next.Parent; + } + else + { + return false; + } + + var tokenIsOpenParen = token.IsKind(SyntaxKind.OpenParenToken); + var nextIsOpenParen = next.IsKind(SyntaxKind.OpenParenToken); + var tokenIsCloseParen = token.IsKind(SyntaxKind.CloseParenToken); + var nextIsCloseParen = next.IsKind(SyntaxKind.CloseParenToken); + + //inner + if (tokenIsOpenParen) + { + return false; + } + + if (nextIsCloseParen) + { + return false; + } + + if (posPattern.Parent is RecursivePatternSyntax rps) + { + //outer + if (nextIsOpenParen) + { + if (rps.Type != null) + { + return true; + } + + return false; + } + + if (tokenIsCloseParen) + { + if (rps.PropertyPatternClause is not null) + { + return false; + } + + if (rps.Designation is null) + { + return false; + } + + return true; + } + } + + return false; + } + + private static bool NeedsSeparator(SyntaxToken token, SyntaxToken next) + { + if (token.Parent == null || next.Parent == null) + { + return false; + } + + if (IsAccessorListWithoutAccessorsWithBlockBody(next.Parent) || + IsAccessorListWithoutAccessorsWithBlockBody(next.Parent.Parent)) + { + // when the accessors are formatted inline, the separator is needed + // unless there is a semicolon. For example: "{ get; set; }" + return !next.IsKind(SyntaxKind.SemicolonToken); + } + + if (IsXmlTextToken(token.Kind()) || IsXmlTextToken(next.Kind())) + { + return false; + } + + if (next.Kind() == SyntaxKind.EndOfDirectiveToken) + { + // In a directive, there's often no token between the directive keyword and + // the end-of-directive, so we may need a separator. + return IsKeyword(token.Kind()) && next.LeadingTrivia.Span.Length > 0; + } + + if ((token.Parent is AssignmentExpressionSyntax && AssignmentTokenNeedsSeparator(token.Kind())) || + (next.Parent is AssignmentExpressionSyntax && AssignmentTokenNeedsSeparator(next.Kind())) || + (token.Parent is BinaryExpressionSyntax && BinaryTokenNeedsSeparator(token.Kind())) || + (next.Parent is BinaryExpressionSyntax && BinaryTokenNeedsSeparator(next.Kind()))) + { + return true; + } + + if (token.IsKind(SyntaxKind.GreaterThanToken) && token.Parent.IsKind(SyntaxKind.TypeArgumentList)) + { + if (!SyntaxFacts.IsPunctuation(next.Kind())) + { + return true; + } + } + + if (token.IsKind(SyntaxKind.GreaterThanToken) && token.Parent.IsKind(SyntaxKind.FunctionPointerParameterList)) + { + return true; + } + + if (token.IsKind(SyntaxKind.CommaToken) && + !next.IsKind(SyntaxKind.CommaToken) && + !token.Parent.IsKind(SyntaxKind.EnumDeclaration)) + { + return true; + } + + if (token.Kind() == SyntaxKind.SemicolonToken + && !(next.Kind() == SyntaxKind.SemicolonToken || next.Kind() == SyntaxKind.CloseParenToken)) + { + return true; + } + + if (next.IsKind(SyntaxKind.SwitchKeyword) && next.Parent is SwitchExpressionSyntax) + { + return true; + } + + if (token.IsKind(SyntaxKind.QuestionToken) + && (token.Parent.IsKind(SyntaxKind.ConditionalExpression) || token.Parent is TypeSyntax) + && !token.Parent.Parent.IsKind(SyntaxKind.TypeArgumentList)) + { + return true; + } + + if (token.IsKind(SyntaxKind.ColonToken)) + { + return !token.Parent.IsKind(SyntaxKind.InterpolationFormatClause) && + !token.Parent.IsKind(SyntaxKind.XmlPrefix); + } + + if (next.IsKind(SyntaxKind.ColonToken)) + { + if (next.Parent.IsKind(SyntaxKind.BaseList) || + next.Parent.IsKind(SyntaxKind.TypeParameterConstraintClause) || + next.Parent is ConstructorInitializerSyntax) + { + return true; + } + } + + if (token.IsKind(SyntaxKind.CloseBracketToken) && IsWord(next.Kind())) + { + return true; + } + + // We don't want to add extra space after cast, we want space only after tuple + if (token.IsKind(SyntaxKind.CloseParenToken) && IsWord(next.Kind()) && + token.Parent.IsKind(SyntaxKind.TupleType) == true) + { + return true; + } + + if ((next.IsKind(SyntaxKind.QuestionToken) || next.IsKind(SyntaxKind.ColonToken)) + && next.Parent.IsKind(SyntaxKind.ConditionalExpression)) + { + return true; + } + + if (token.IsKind(SyntaxKind.EqualsToken)) + { + return !token.Parent.IsKind(SyntaxKind.XmlTextAttribute); + } + + if (next.IsKind(SyntaxKind.EqualsToken)) + { + return !next.Parent.IsKind(SyntaxKind.XmlTextAttribute); + } + + // Rules for function pointer below are taken from: + // https://github.com/dotnet/roslyn/blob/1cca63b5d8ea170f8d8e88e1574aa3ebe354c23b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/SpacingFormattingRule.cs#L321-L413 + if (token.Parent.IsKind(SyntaxKind.FunctionPointerType)) + { + // No spacing between delegate and * + if (next.IsKind(SyntaxKind.AsteriskToken) && token.IsKind(SyntaxKind.DelegateKeyword)) + { + return false; + } + + // Force a space between * and the calling convention + if (token.IsKind(SyntaxKind.AsteriskToken) && + next.Parent.IsKind(SyntaxKind.FunctionPointerCallingConvention)) + { + switch (next.Kind()) + { + case SyntaxKind.IdentifierToken: + case SyntaxKind.ManagedKeyword: + case SyntaxKind.UnmanagedKeyword: + return true; + } + } + } + + if (next.Parent.IsKind(SyntaxKind.FunctionPointerParameterList) && next.IsKind(SyntaxKind.LessThanToken)) + { + switch (token.Kind()) + { + // No spacing between the * and < tokens if there is no calling convention + case SyntaxKind.AsteriskToken: + // No spacing between the calling convention and opening angle bracket of function pointer types: + // delegate* managed< + case SyntaxKind.ManagedKeyword: + case SyntaxKind.UnmanagedKeyword: + // No spacing between the calling convention specifier and the opening angle + // delegate* unmanaged[Cdecl]< + case SyntaxKind.CloseBracketToken + when token.Parent.IsKind(SyntaxKind.FunctionPointerUnmanagedCallingConventionList): + return false; + } + } + + // No space between unmanaged and the [ + // delegate* unmanaged[ + if (token.Parent.IsKind(SyntaxKind.FunctionPointerCallingConvention) && + next.Parent.IsKind(SyntaxKind.FunctionPointerUnmanagedCallingConventionList) && + next.IsKind(SyntaxKind.OpenBracketToken)) + { + return false; + } + + // Function pointer calling convention adjustments + if (next.Parent.IsKind(SyntaxKind.FunctionPointerUnmanagedCallingConventionList) && + token.Parent.IsKind(SyntaxKind.FunctionPointerUnmanagedCallingConventionList)) + { + if (next.IsKind(SyntaxKind.IdentifierToken)) + { + if (token.IsKind(SyntaxKind.OpenBracketToken)) + { + return false; + } + // Space after the , + // unmanaged[Cdecl, Thiscall + + if (token.IsKind(SyntaxKind.CommaToken)) + { + return true; + } + } + + // No space between identifier and comma + // unmanaged[Cdecl, + if (next.IsKind(SyntaxKind.CommaToken)) + { + return false; + } + + // No space before the ] + // unmanaged[Cdecl] + if (next.IsKind(SyntaxKind.CloseBracketToken)) + { + return false; + } + } + + // No space after the < in function pointer parameter lists + // delegate* in function pointer parameter lists + // delegate* + if (next.IsKind(SyntaxKind.GreaterThanToken) && next.Parent.IsKind(SyntaxKind.FunctionPointerParameterList)) + { + return false; + } + + if (token.IsKind(SyntaxKind.EqualsGreaterThanToken) || next.IsKind(SyntaxKind.EqualsGreaterThanToken)) + { + return true; + } + + // Can happen in directives (e.g. #line 1 "file") + if (IsLiteral(token.Kind()) && IsLiteral(next.Kind())) + { + return true; + } + + // No space before an asterisk that's part of a PointerTypeSyntax. + if (next.IsKind(SyntaxKind.AsteriskToken) && next.Parent is PointerTypeSyntax) + { + return false; + } + + // The last asterisk of a pointer declaration should be followed by a space. + if (token.IsKind(SyntaxKind.AsteriskToken) && token.Parent is PointerTypeSyntax && + (next.IsKind(SyntaxKind.IdentifierToken) || next.Parent.IsKind(SyntaxKind.IndexerDeclaration))) + { + return true; + } + + if (IsKeyword(token.Kind())) + { + if (!next.IsKind(SyntaxKind.ColonToken) && + !next.IsKind(SyntaxKind.DotToken) && + !next.IsKind(SyntaxKind.QuestionToken) && + !next.IsKind(SyntaxKind.SemicolonToken) && + !next.IsKind(SyntaxKind.OpenBracketToken) && + (!next.IsKind(SyntaxKind.OpenParenToken) || KeywordNeedsSeparatorBeforeOpenParen(token.Kind()) || + next.Parent.IsKind(SyntaxKind.TupleType)) && + !next.IsKind(SyntaxKind.CloseParenToken) && + !next.IsKind(SyntaxKind.CloseBraceToken) && + !next.IsKind(SyntaxKind.ColonColonToken) && + !next.IsKind(SyntaxKind.GreaterThanToken) && + !next.IsKind(SyntaxKind.CommaToken)) + { + return true; + } + } + + if (IsWord(token.Kind()) && IsWord(next.Kind())) + { + return true; + } + + if (token.Span.Length > 1 && next.Span.Length > 1) + { + var tokenLastChar = token.Text.Last(); + var nextFirstChar = next.Text.First(); + if (tokenLastChar == nextFirstChar && TokenCharacterCanBeDoubled(tokenLastChar)) + { + return true; + } + } + + if (token.Parent is RelationalPatternSyntax) + { + //>, >=, <, <= + return true; + } + + switch (next.Kind()) + { + case SyntaxKind.AndKeyword: + case SyntaxKind.OrKeyword: + return true; + } + + switch (token.Kind()) + { + case SyntaxKind.AndKeyword: + case SyntaxKind.OrKeyword: + case SyntaxKind.NotKeyword: + return true; + } + + if (NeedsSeparatorForPropertyPattern(token, next)) + { + return true; + } + + if (NeedsSeparatorForPositionalPattern(token, next)) + { + return true; + } + + return false; + } + + private static bool IsLiteral(SyntaxKind kind) + { + return kind switch + { + SyntaxKind.IdentifierToken => + //case SyntaxKind.Unknown: + true, + SyntaxKind.StringLiteralToken => + //case SyntaxKind.Unknown: + true, + SyntaxKind.CharacterLiteralToken => + //case SyntaxKind.Unknown: + true, + SyntaxKind.NumericLiteralToken => + //case SyntaxKind.Unknown: + true, + SyntaxKind.XmlTextLiteralToken => + //case SyntaxKind.Unknown: + true, + SyntaxKind.XmlTextLiteralNewLineToken => + //case SyntaxKind.Unknown: + true, + SyntaxKind.XmlEntityLiteralToken => + //case SyntaxKind.Unknown: + true, + _ => false + }; + } + + public override SyntaxNode? VisitXmlTextAttribute(XmlTextAttributeSyntax node) + { + var attribute = base.VisitXmlTextAttribute(node); + return attribute is null or { HasTrailingTrivia: true } ? attribute : attribute.WithTrailingTrivia(GetSpace()); + } + + private static bool KeywordNeedsSeparatorBeforeOpenParen(SyntaxKind kind) + { + return kind switch + { + SyntaxKind.TypeOfKeyword => false, + SyntaxKind.DefaultKeyword => false, + SyntaxKind.NewKeyword => false, + SyntaxKind.BaseKeyword => false, + SyntaxKind.ThisKeyword => false, + SyntaxKind.CheckedKeyword => false, + SyntaxKind.UncheckedKeyword => false, + SyntaxKind.SizeOfKeyword => false, + SyntaxKind.ArgListKeyword => false, + _ => true + }; + } + + private static bool IsXmlTextToken(SyntaxKind kind) + { + return kind switch + { + SyntaxKind.XmlTextLiteralNewLineToken => true, + SyntaxKind.XmlTextLiteralToken => true, + _ => false + }; + } + + private static bool BinaryTokenNeedsSeparator(SyntaxKind kind) + { + return kind switch + { + SyntaxKind.DotToken => false, + SyntaxKind.MinusGreaterThanToken => false, + _ => SyntaxFacts.GetBinaryExpression(kind) != SyntaxKind.None + }; + } + + private static bool AssignmentTokenNeedsSeparator(SyntaxKind kind) + { + return SyntaxFacts.GetAssignmentExpression(kind) != SyntaxKind.None; + } + + private SyntaxTriviaList RewriteTrivia( + SyntaxTriviaList triviaList, + int depth, + bool isTrailing, + bool indentAfterLineBreak, + bool mustHaveSeparator, + int lineBreaksAfter) + { + var currentTriviaList = ImmutableArray.CreateBuilder(triviaList.Count); + foreach (var trivia in triviaList) + { + if (trivia.IsKind(SyntaxKind.WhitespaceTrivia) || + trivia.IsKind(SyntaxKind.EndOfLineTrivia) || + trivia.FullSpan.Length == 0) + { + continue; + } + + var needsSeparator = + (currentTriviaList.Count > 0 && NeedsSeparatorBetween(currentTriviaList.Last())) || + (currentTriviaList.Count == 0 && isTrailing); + + var needsLineBreak = NeedsLineBreakBefore(trivia, isTrailing) + || (currentTriviaList.Count > 0 && + NeedsLineBreakBetween(currentTriviaList.Last(), trivia, isTrailing)); + + if (needsLineBreak && !_afterLineBreak) + { + currentTriviaList.Add(GetEndOfLine()); + _afterLineBreak = true; + _afterIndentation = false; + } + + if (_afterLineBreak) + { + if (!_afterIndentation && NeedsIndentAfterLineBreak(trivia)) + { + currentTriviaList.Add(this.GetIndentation(GetDeclarationDepth(trivia))); + _afterIndentation = true; + } + } + else if (needsSeparator) + { + currentTriviaList.Add(GetSpace()); + _afterLineBreak = false; + _afterIndentation = false; + } + + if (trivia.HasStructure) + { + var tr = this.VisitStructuredTrivia(trivia); + currentTriviaList.Add(tr); + } + else if (trivia.IsKind(SyntaxKind.DocumentationCommentExteriorTrivia)) + { + // recreate exterior to remove any leading whitespace + currentTriviaList.Add(s_trimmedDocCommentExterior); + } + else + { + currentTriviaList.Add(trivia); + } + + if (NeedsLineBreakAfter(trivia, isTrailing) + && (currentTriviaList.Count == 0 || !EndsInLineBreak(currentTriviaList.Last()))) + { + currentTriviaList.Add(GetEndOfLine()); + _afterLineBreak = true; + _afterIndentation = false; + } + } + + if (lineBreaksAfter > 0) + { + if (currentTriviaList.Count > 0 + && EndsInLineBreak(currentTriviaList.Last())) + { + lineBreaksAfter--; + } + + for (int i = 0; i < lineBreaksAfter; i++) + { + currentTriviaList.Add(GetEndOfLine()); + _afterLineBreak = true; + _afterIndentation = false; + } + } + else if (indentAfterLineBreak && _afterLineBreak && !_afterIndentation) + { + currentTriviaList.Add(this.GetIndentation(depth)); + _afterIndentation = true; + } + else if (mustHaveSeparator) + { + currentTriviaList.Add(GetSpace()); + _afterLineBreak = false; + _afterIndentation = false; + } + + return currentTriviaList.Count switch + { + 0 => default, + 1 => SyntaxFactory.TriviaList(currentTriviaList.First()), + _ => SyntaxFactory.TriviaList(currentTriviaList) + }; + } + + private static readonly SyntaxTrivia + s_trimmedDocCommentExterior = SyntaxFactory.DocumentationCommentExterior("///"); + + private SyntaxTrivia GetSpace() + { + return _useElasticTrivia ? SyntaxFactory.ElasticSpace : SyntaxFactory.Space; + } + + private SyntaxTrivia GetEndOfLine() + { + return _eolTrivia; + } + + private SyntaxTrivia VisitStructuredTrivia(SyntaxTrivia trivia) + { + bool oldIsInStructuredTrivia = _isInStructuredTrivia; + _isInStructuredTrivia = true; + + SyntaxToken oldPreviousToken = _previousToken; + _previousToken = default; + + SyntaxTrivia result = VisitTrivia(trivia); + + _isInStructuredTrivia = oldIsInStructuredTrivia; + _previousToken = oldPreviousToken; + + return result; + } + + private static bool NeedsSeparatorBetween(SyntaxTrivia trivia) + { + return trivia.Kind() switch + { + SyntaxKind.None => false, + SyntaxKind.WhitespaceTrivia => false, + SyntaxKind.DocumentationCommentExteriorTrivia => false, + _ => !SyntaxFacts.IsPreprocessorDirective(trivia.Kind()) + }; + } + + private static bool NeedsLineBreakBetween(SyntaxTrivia trivia, SyntaxTrivia next, bool isTrailingTrivia) + { + return NeedsLineBreakAfter(trivia, isTrailingTrivia) + || NeedsLineBreakBefore(next, isTrailingTrivia); + } + + private static bool NeedsLineBreakBefore(SyntaxTrivia trivia, bool isTrailingTrivia) + { + var kind = trivia.Kind(); + return kind switch + { + SyntaxKind.DocumentationCommentExteriorTrivia => !isTrailingTrivia, + _ => SyntaxFacts.IsPreprocessorDirective(kind) + }; + } + + private static bool NeedsLineBreakAfter(SyntaxTrivia trivia, bool isTrailingTrivia) + { + var kind = trivia.Kind(); + return kind switch + { + SyntaxKind.SingleLineCommentTrivia => true, + SyntaxKind.MultiLineCommentTrivia => !isTrailingTrivia, + _ => SyntaxFacts.IsPreprocessorDirective(kind) + }; + } + + private static bool NeedsIndentAfterLineBreak(SyntaxTrivia trivia) + { + return trivia.Kind() switch + { + SyntaxKind.SingleLineCommentTrivia => true, + SyntaxKind.MultiLineCommentTrivia => true, + SyntaxKind.DocumentationCommentExteriorTrivia => true, + SyntaxKind.SingleLineDocumentationCommentTrivia => true, + SyntaxKind.MultiLineDocumentationCommentTrivia => true, + _ => false + }; + } + + private static bool IsLineBreak(SyntaxToken token) + { + return token.Kind() == SyntaxKind.XmlTextLiteralNewLineToken; + } + + private static bool EndsInLineBreak(SyntaxTrivia trivia) + { + if (trivia.Kind() == SyntaxKind.EndOfLineTrivia) + { + return true; + } + + if (trivia.Kind() == SyntaxKind.PreprocessingMessageTrivia || trivia.Kind() == SyntaxKind.DisabledTextTrivia) + { + var text = trivia.ToFullString(); + return text.Length > 0 && SyntaxFacts.IsNewLine(text.Last()); + } + + if (trivia.HasStructure) + { + var node = trivia.GetStructure()!; + var trailing = node.GetTrailingTrivia(); + if (trailing.Count > 0) + { + return EndsInLineBreak(trailing.Last()); + } + + return IsLineBreak(node.GetLastToken()); + } + + return false; + } + + private static bool IsWord(SyntaxKind kind) + { + return kind == SyntaxKind.IdentifierToken || IsKeyword(kind); + } + + private static bool IsKeyword(SyntaxKind kind) + { + return SyntaxFacts.IsKeywordKind(kind) || SyntaxFacts.IsPreprocessorKeyword(kind); + } + + private static bool TokenCharacterCanBeDoubled(char c) + { + return c switch + { + '+' => true, + '-' => true, + '<' => true, + ':' => true, + '?' => true, + '=' => true, + '"' => true, + _ => false + }; + } + + private static int GetDeclarationDepth(SyntaxToken token) + { + return GetDeclarationDepth(token.Parent); + } + + private static int GetDeclarationDepth(SyntaxTrivia trivia) + { + if (SyntaxFacts.IsPreprocessorDirective(trivia.Kind())) + { + return 0; + } + + return GetDeclarationDepth(trivia.Token); + } + + private static int GetDeclarationDepth(SyntaxNode? node) + { + if (node != null) + { + if (node.IsStructuredTrivia) + { + var tr = ((StructuredTriviaSyntax) node).ParentTrivia; + return GetDeclarationDepth(tr); + } + + if (node.Parent != null) + { + if (node.Parent.IsKind(SyntaxKind.CompilationUnit)) + { + return 0; + } + + int parentDepth = GetDeclarationDepth(node.Parent); + + if (node.Parent.Kind() is SyntaxKind.GlobalStatement) + { + return parentDepth; + } + + if (node.IsKind(SyntaxKind.IfStatement) && node.Parent.IsKind(SyntaxKind.ElseClause)) + { + return parentDepth; + } + + if (node.Parent is BlockSyntax || node is StatementSyntax and not BlockSyntax) + { + // all nested statements are indented one level + return parentDepth + 1; + } + + if (node is MemberDeclarationSyntax or AccessorDeclarationSyntax or TypeParameterConstraintClauseSyntax + or SwitchSectionSyntax or SwitchExpressionArmSyntax or UsingDirectiveSyntax + or ExternAliasDirectiveSyntax or QueryExpressionSyntax or QueryContinuationSyntax) + { + return parentDepth + 1; + } + + return parentDepth; + } + } + + return 0; + } + + public override SyntaxNode? VisitInterpolatedStringExpression(InterpolatedStringExpressionSyntax node) + { + if (node.StringStartToken.Kind() == SyntaxKind.InterpolatedStringStartToken) + { + //Just for non verbatim strings we want to make sure that the formatting of interpolations does not emit line breaks. + //See: https://github.com/dotnet/roslyn/issues/50742 + // + //The flag _inSingleLineInterpolation is set to true while visiting InterpolatedStringExpressionSyntax and checked in LineBreaksAfter + //to suppress adding newlines. + var old = _inSingleLineInterpolation; + _inSingleLineInterpolation = true; + try + { + return base.VisitInterpolatedStringExpression(node); + } + finally + { + _inSingleLineInterpolation = old; + } + } + + return base.VisitInterpolatedStringExpression(node); + } +} \ No newline at end of file diff --git a/SharpGenTools.Sdk/SharpGenTask.PropertyCache.cs b/SharpGenTools.Sdk/SharpGenTask.PropertyCache.cs index 792662c3..5db45967 100644 --- a/SharpGenTools.Sdk/SharpGenTask.PropertyCache.cs +++ b/SharpGenTools.Sdk/SharpGenTask.PropertyCache.cs @@ -18,7 +18,6 @@ public sealed partial class SharpGenTask { private const int CacheFormatSignature = ('S' << 0) | ('G' << 8) | ('P' << 16) | ('C' << 24); private const int CacheFormatVersion = 1; - private static readonly Encoding TextEncoding = Encoding.UTF8; private static HashAlgorithm CreateSettingsHash() => SHA256.Create(); diff --git a/SharpGenTools.Sdk/SharpGenTask.cs b/SharpGenTools.Sdk/SharpGenTask.cs index 9f1411b2..37c73d86 100644 --- a/SharpGenTools.Sdk/SharpGenTask.cs +++ b/SharpGenTools.Sdk/SharpGenTask.cs @@ -11,6 +11,7 @@ using System.Xml; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; +using Microsoft.CodeAnalysis.CSharp; using SharpGen; using SharpGen.Config; using SharpGen.CppModel; @@ -462,8 +463,9 @@ out var configsWithExtensionHeaders RoslynGenerator generator = new(); - var generatedCode = generator.Run(solution, ioc); - File.WriteAllText(GeneratedCodeFile, generatedCode); + using var codeStream = File.Open(GeneratedCodeFile, FileMode.Create, FileAccess.Write); + using var codeWriter = new StreamWriter(codeStream, DefaultEncoding); + generator.Run(solution, ioc).GetCompilationUnitRoot().WriteTo(codeWriter); return !SharpGenLogger.HasErrors; } From e10f5c6ec4786184cfb46cc4b21a873fae8ee56e Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Wed, 1 Jun 2022 11:43:19 +0200 Subject: [PATCH 12/55] Implement nullable handle in other places. --- SharpGen.Runtime/COM/ComObject.cs | 29 +++++++++----- .../CallbackBase.ReflectionCache.cs | 4 +- .../CallbackBase.ReflectionImpl.cs | 2 +- SharpGen.Runtime/CallbackBase.cs | 2 +- SharpGen.Runtime/CppObjectShadow.cs | 2 +- SharpGen.Runtime/MarshallingHelpers.cs | 40 ++++++++++++++----- SharpGen.Runtime/Result.cs | 3 +- SharpGen.Runtime/TypeDataStorage.cs | 8 ++-- SharpGen.Runtime/VtblAttribute.cs | 7 ++-- 9 files changed, 65 insertions(+), 32 deletions(-) diff --git a/SharpGen.Runtime/COM/ComObject.cs b/SharpGen.Runtime/COM/ComObject.cs index f4849bc7..a56867a6 100644 --- a/SharpGen.Runtime/COM/ComObject.cs +++ b/SharpGen.Runtime/COM/ComObject.cs @@ -18,6 +18,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +#nullable enable + using System; using System.Reflection; using System.Runtime.InteropServices; @@ -31,11 +33,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 +48,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 +57,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 +66,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__; } @@ -108,7 +113,7 @@ public virtual IntPtr QueryInterfaceOrNull(Guid guid) public virtual T QueryInterface() where T : ComObject { QueryInterface(typeof(T).GetTypeInfo().GUID, out var parentPtr).CheckError(); - return MarshallingHelpers.FromPointer(parentPtr); + return MarshallingHelpers.FromPointer(parentPtr)!; } /// @@ -164,7 +169,7 @@ 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(IntPtr comPointer) where T : ComObject { using var tempObject = new ComObject(comPointer); return tempObject.QueryInterfaceOrNull(); @@ -178,16 +183,20 @@ 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() 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/CallbackBase.ReflectionCache.cs b/SharpGen.Runtime/CallbackBase.ReflectionCache.cs index 5cbeecb4..17d6a057 100644 --- a/SharpGen.Runtime/CallbackBase.ReflectionCache.cs +++ b/SharpGen.Runtime/CallbackBase.ReflectionCache.cs @@ -11,13 +11,15 @@ public abstract partial class CallbackBase private CallbackTypeInfo GetTypeInfo() { - CallbackTypeInfo info; + CallbackTypeInfo? info; var type = GetType(); var cache = TypeReflectionCache; lock (cache) + { if (cache.TryGetValue(type, out info)) return info; + } info = new CallbackTypeInfo(type); diff --git a/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs b/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs index bfe9b6b1..70cfae8a 100644 --- a/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs +++ b/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs @@ -15,7 +15,7 @@ public abstract unsafe partial class CallbackBase private GCHandle CreateShadow(TypeInfo type) { - var shadow = (CppObjectShadow) Activator.CreateInstance(type.AsType()); + var shadow = (CppObjectShadow) Activator.CreateInstance(type.AsType())!; // Initialize the shadow with the callback shadow.Initialize(ThisHandle); diff --git a/SharpGen.Runtime/CallbackBase.cs b/SharpGen.Runtime/CallbackBase.cs index e362ca5a..b96ba2f5 100644 --- a/SharpGen.Runtime/CallbackBase.cs +++ b/SharpGen.Runtime/CallbackBase.cs @@ -204,7 +204,7 @@ internal IReadOnlyCollection Shadows HashSet shadows = new(ReferenceEqualityComparer.Instance); - foreach (CppObjectCallableWrapper* ccw in _ccw.Values) + foreach (CppObjectCallableWrapper* ccw in _ccw!.Values) { var handle = ccw->Shadow; if (!handle.IsAllocated) diff --git a/SharpGen.Runtime/CppObjectShadow.cs b/SharpGen.Runtime/CppObjectShadow.cs index 00d40992..ea2be973 100644 --- a/SharpGen.Runtime/CppObjectShadow.cs +++ b/SharpGen.Runtime/CppObjectShadow.cs @@ -24,6 +24,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace SharpGen.Runtime; @@ -40,7 +41,6 @@ public abstract unsafe class CppObjectShadow static CppObjectShadow() { - Debug.Assert(Marshal.SizeOf(typeof(CppObjectCallableWrapper)) == CppObjectCallableWrapper.Size); Debug.Assert(sizeof(CppObjectCallableWrapper) == CppObjectCallableWrapper.Size); } diff --git a/SharpGen.Runtime/MarshallingHelpers.cs b/SharpGen.Runtime/MarshallingHelpers.cs index a3aa2b93..76311485 100644 --- a/SharpGen.Runtime/MarshallingHelpers.cs +++ b/SharpGen.Runtime/MarshallingHelpers.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.Runtime.CompilerServices; @@ -11,8 +13,17 @@ 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) 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. @@ -20,25 +31,34 @@ 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(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(), }; /// @@ -50,9 +70,9 @@ public static uint Release(TCallback callback) where TCallback : ICal public static IntPtr ToCallbackPtr(ICallbackable callback) where TCallback : ICallbackable => callback switch { - null => IntPtr.Zero, CppObject cpp => cpp.NativePointer, - CallbackBase managed => managed.Find() + CallbackBase managed => managed.Find(), + _ => IntPtr.Zero, }; /// @@ -63,7 +83,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 +93,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/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/TypeDataStorage.cs b/SharpGen.Runtime/TypeDataStorage.cs index 7860d5e6..9635c7f2 100644 --- a/SharpGen.Runtime/TypeDataStorage.cs +++ b/SharpGen.Runtime/TypeDataStorage.cs @@ -67,7 +67,7 @@ internal static void Register(void* vtbl) where T : ICallbackable internal static void Register(Guid guid, void* vtbl) => vtblByGuid[guid] = new IntPtr(vtbl); - internal static IntPtr[] GetSourceVtbl() where T : ICallbackable + internal static IntPtr[]? GetSourceVtbl() where T : ICallbackable { #if !FORCE_REFLECTION_ONLY if (Storage.SourceVtbl is { } storedVtbl) @@ -77,14 +77,14 @@ internal static IntPtr[] GetSourceVtbl() where T : ICallbackable return GetSourceVtblFromReflection(typeof(T)); } - private static IntPtr[] GetSourceVtblFromReflection(Type type) + private static IntPtr[]? GetSourceVtblFromReflection(Type type) { const string vtbl = "Vtbl"; var vtblAttribute = VtblAttribute.Get(type); Debug.Assert(vtblAttribute is not null, $"Type {type.FullName} has no Vtbl attribute"); - var vtblType = vtblAttribute.Type; + var vtblType = vtblAttribute!.Type; #if NETSTANDARD1_3 static bool Predicate(MemberInfo x) => x.Name == vtbl; @@ -175,6 +175,6 @@ private record struct RegisterInheritanceItem(TypeInfo Type, int InterfaceCount, public static class Storage where T : ICallbackable { public static Guid Guid; - public static IntPtr[] SourceVtbl; + public static IntPtr[]? SourceVtbl; } } \ No newline at end of file diff --git a/SharpGen.Runtime/VtblAttribute.cs b/SharpGen.Runtime/VtblAttribute.cs index 7ea97689..39130744 100644 --- a/SharpGen.Runtime/VtblAttribute.cs +++ b/SharpGen.Runtime/VtblAttribute.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; @@ -28,9 +29,9 @@ public VtblAttribute(Type holder) } [MethodImpl(Utilities.MethodAggressiveOptimization)] - internal static VtblAttribute Get(Type type) => Get(type.GetTypeInfo()); + internal static VtblAttribute? Get(Type type) => Get(type.GetTypeInfo()); [MethodImpl(Utilities.MethodAggressiveOptimization)] - internal static VtblAttribute Get(TypeInfo type) => type.GetCustomAttribute(); + internal static VtblAttribute? Get(TypeInfo type) => type.GetCustomAttribute(); internal static bool Has(Type type) => Get(type.GetTypeInfo()) != null; internal static bool Has(TypeInfo type) => Get(type) != null; } \ No newline at end of file From 1fbe2cf6e7f9cf3e30e563bc26f7e796a18ece59 Mon Sep 17 00:00:00 2001 From: Sewer 56 Date: Sun, 26 Jun 2022 14:34:29 +0100 Subject: [PATCH 13/55] Added: Assembly Trimming Annotations for SharpGen.Runtime --- SharpGen.Runtime.Trim.Dummy/Program.cs | 6 +++ .../SharpGen.Runtime.Trim.Dummy.csproj | 20 ++++++++++ SharpGen.Runtime/COM/ComObject.cs | 37 ++++++++++++++++--- SharpGen.Runtime/CallbackBase.Reflection.cs | 28 ++++++++++++-- .../CallbackBase.ReflectionCache.cs | 3 +- .../CallbackBase.ReflectionImpl.cs | 11 +++++- SharpGen.Runtime/InterfaceArray.cs | 6 ++- SharpGen.Runtime/MarshallingHelpers.cs | 13 ++++++- SharpGen.Runtime/ShadowAttribute.cs | 10 ++++- SharpGen.Runtime/SharpGen.Runtime.csproj | 3 +- .../TrimmingWrappers/TrimmingExtensions.cs | 28 ++++++++++++++ SharpGen.Runtime/TypeDataStorage.cs | 15 ++++++-- SharpGen.Runtime/VtblAttribute.cs | 10 ++++- SharpGenTools.sln | 17 +++++---- 14 files changed, 179 insertions(+), 28 deletions(-) create mode 100644 SharpGen.Runtime.Trim.Dummy/Program.cs create mode 100644 SharpGen.Runtime.Trim.Dummy/SharpGen.Runtime.Trim.Dummy.csproj create mode 100644 SharpGen.Runtime/TrimmingWrappers/TrimmingExtensions.cs diff --git a/SharpGen.Runtime.Trim.Dummy/Program.cs b/SharpGen.Runtime.Trim.Dummy/Program.cs new file mode 100644 index 00000000..341250d9 --- /dev/null +++ b/SharpGen.Runtime.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 SharpGenTools"); \ 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..5926a1ec --- /dev/null +++ b/SharpGen.Runtime.Trim.Dummy/SharpGen.Runtime.Trim.Dummy.csproj @@ -0,0 +1,20 @@ + + + + Exe + net7.0 + enable + enable + true + false + + + + + + + + + + + diff --git a/SharpGen.Runtime/COM/ComObject.cs b/SharpGen.Runtime/COM/ComObject.cs index a56867a6..7a0c48c2 100644 --- a/SharpGen.Runtime/COM/ComObject.cs +++ b/SharpGen.Runtime/COM/ComObject.cs @@ -21,6 +21,7 @@ #nullable enable using System; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Versioning; @@ -110,7 +111,11 @@ 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)!; @@ -128,7 +133,11 @@ public virtual T QueryInterface() where T : ComObject #if NET5_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 ) @@ -139,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(); @@ -157,7 +170,11 @@ public static T As(IntPtr iunknownPtr) where T : ComObject #if NET5_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)); /// @@ -169,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(); @@ -183,7 +204,11 @@ public static T QueryInterface(object comObject) where T : ComObject => /// ms682521 /// IUnknown::QueryInterface /// IUnknown::QueryInterface - public virtual T? QueryInterfaceOrNull() where T : ComObject + 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)); } diff --git a/SharpGen.Runtime/CallbackBase.Reflection.cs b/SharpGen.Runtime/CallbackBase.Reflection.cs index 9bf2493b..4bcecfd4 100644 --- a/SharpGen.Runtime/CallbackBase.Reflection.cs +++ b/SharpGen.Runtime/CallbackBase.Reflection.cs @@ -2,8 +2,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; +using SharpGen.Runtime.TrimmingWrappers; namespace SharpGen.Runtime; @@ -11,10 +13,17 @@ public abstract partial class CallbackBase { private readonly struct ImmediateShadowInterfaceInfo { +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif public readonly TypeInfo Type; public readonly List ImplementedInterfaces; - public ImmediateShadowInterfaceInfo(TypeInfo type) + public ImmediateShadowInterfaceInfo( +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + TypeInfo type) { Type = type; ImplementedInterfaces = new(6); @@ -35,16 +44,27 @@ public ImmediateShadowInterfaceInfo(TypeInfo type) // 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(Type type) : this(type.GetTypeInfo()) + public CallbackTypeInfo( +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + Type type) : this(type.GetTypeInfo()) { } - private CallbackTypeInfo(TypeInfo type) + private CallbackTypeInfo( +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + TypeInfo type) { this.type = type ?? throw new ArgumentNullException(nameof(type)); } @@ -111,7 +131,7 @@ private ImmediateShadowInterfaceInfo[] BuildVtblList() foreach (var implementedInterface in type.ImplementedInterfaces) { - var item = implementedInterface.GetTypeInfo(); + var item = implementedInterface.GetTypeInfoWithPreservedInterfaces(); // Only process interfaces that have vtbl if (!VtblAttribute.Has(item)) diff --git a/SharpGen.Runtime/CallbackBase.ReflectionCache.cs b/SharpGen.Runtime/CallbackBase.ReflectionCache.cs index 17d6a057..b74cf6c7 100644 --- a/SharpGen.Runtime/CallbackBase.ReflectionCache.cs +++ b/SharpGen.Runtime/CallbackBase.ReflectionCache.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using SharpGen.Runtime.TrimmingWrappers; namespace SharpGen.Runtime; @@ -12,7 +13,7 @@ public abstract partial class CallbackBase private CallbackTypeInfo GetTypeInfo() { CallbackTypeInfo? info; - var type = GetType(); + var type = this.GetTypeWithPreservedInterfaces(); var cache = TypeReflectionCache; lock (cache) diff --git a/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs b/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs index 70cfae8a..61076089 100644 --- a/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs +++ b/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; @@ -13,7 +14,11 @@ public abstract unsafe partial class CallbackBase { protected virtual Guid[] BuildGuidList() => GetTypeInfo().Guids; - private GCHandle CreateShadow(TypeInfo type) + private GCHandle CreateShadow( +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] +#endif + TypeInfo type) { var shadow = (CppObjectShadow) Activator.CreateInstance(type.AsType())!; @@ -23,6 +28,10 @@ private GCHandle CreateShadow(TypeInfo type) 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. 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/MarshallingHelpers.cs b/SharpGen.Runtime/MarshallingHelpers.cs index 76311485..89941624 100644 --- a/SharpGen.Runtime/MarshallingHelpers.cs +++ b/SharpGen.Runtime/MarshallingHelpers.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace SharpGen.Runtime; @@ -13,7 +14,11 @@ 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 + 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; @@ -31,7 +36,11 @@ 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(UIntPtr cppObjectPtr) where T : CppObject + 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; 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 530e8e9a..776d19b6 100644 --- a/SharpGen.Runtime/SharpGen.Runtime.csproj +++ b/SharpGen.Runtime/SharpGen.Runtime.csproj @@ -5,6 +5,7 @@ - net5.0;netcoreapp3.0;netstandard2.1;netcoreapp2.1;netstandard2.0;net471;net46;net45;netstandard1.3 + net6.0;net5.0;netcoreapp3.0;netstandard2.1;netcoreapp2.1;netstandard2.0;net471;net46;net45;netstandard1.3 Support classes for code generated by SharpGen. true false diff --git a/SharpGen.Runtime/TrimmingWrappers/TrimmingExtensions.cs b/SharpGen.Runtime/TrimmingWrappers/TrimmingExtensions.cs new file mode 100644 index 00000000..38e9080d --- /dev/null +++ b/SharpGen.Runtime/TrimmingWrappers/TrimmingExtensions.cs @@ -0,0 +1,28 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +namespace SharpGen.Runtime.TrimmingWrappers +{ + internal static class TrimmingExtensions + { +#if NET6_0_OR_GREATER + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2068", Justification = "We're preserving nested interfaces via wrapper method.")] +#endif + public static TypeInfo GetTypeInfoWithPreservedInterfaces(this Type type) + { + return type.GetTypeInfo(); + } + + +#if NET6_0_OR_GREATER + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2073", Justification = "We're preserving nested interfaces via wrapper method.")] +#endif + public static Type GetTypeWithPreservedInterfaces(this object obj) + { + return obj.GetType(); + } + } +} diff --git a/SharpGen.Runtime/TypeDataStorage.cs b/SharpGen.Runtime/TypeDataStorage.cs index 9635c7f2..de046d8e 100644 --- a/SharpGen.Runtime/TypeDataStorage.cs +++ b/SharpGen.Runtime/TypeDataStorage.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; +using SharpGen.Runtime.TrimmingWrappers; namespace SharpGen.Runtime; @@ -117,7 +118,11 @@ internal static void Register(void* vtbl) where T : ICallbackable return null; } - internal static bool GetTargetVtbl(TypeInfo type, out void* pointer) + internal static bool GetTargetVtbl( +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + TypeInfo type, out void* pointer) { #if !FORCE_REFLECTION_ONLY if (vtblByGuid.TryGetValue(type.GUID, out var ptr)) @@ -137,7 +142,11 @@ internal static bool GetTargetVtbl(TypeInfo type, out void* pointer) return false; } - private static IntPtr RegisterFromReflection(TypeInfo type, IntPtr[] sourceVtbl) + private static IntPtr RegisterFromReflection( +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] +#endif + TypeInfo type, IntPtr[] sourceVtbl) { var callbackable = typeof(ICallbackable).GetTypeInfo(); @@ -146,7 +155,7 @@ private static IntPtr RegisterFromReflection(TypeInfo type, IntPtr[] sourceVtbl) foreach (var iface in type.ImplementedInterfaces) { - var typeInfo = iface.GetTypeInfo(); + var typeInfo = iface.GetTypeInfoWithPreservedInterfaces(); if (callbackable == typeInfo || !callbackable.IsAssignableFrom(typeInfo)) continue; diff --git a/SharpGen.Runtime/VtblAttribute.cs b/SharpGen.Runtime/VtblAttribute.cs index 39130744..3e74d58d 100644 --- a/SharpGen.Runtime/VtblAttribute.cs +++ b/SharpGen.Runtime/VtblAttribute.cs @@ -1,6 +1,7 @@ #nullable enable using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; @@ -15,13 +16,20 @@ public sealed class VtblAttribute : Attribute /// /// Type of the associated virtual method table /// +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] +#endif public Type Type { get; } /// /// Initializes a new instance of class. /// /// Type of the associated virtual method table - public VtblAttribute(Type holder) + public VtblAttribute( +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] +#endif + Type holder) { Type = holder ?? throw new ArgumentNullException(nameof(holder)); diff --git a/SharpGenTools.sln b/SharpGenTools.sln index 3ef816a0..990d941b 100644 --- a/SharpGenTools.sln +++ b/SharpGenTools.sln @@ -1,7 +1,6 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2036 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32611.2 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpGen", "SharpGen\SharpGen.csproj", "{31F85A16-CB01-4456-BE3C-76E9FF3A1343}" EndProject @@ -11,9 +10,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpGen.UnitTests", "Sharp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpGen.Runtime", "SharpGen.Runtime\SharpGen.Runtime.csproj", "{6302D087-BC5E-4AA0-BA4D-2115590D60E2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpGen.Platform", "SharpGen.Platform\SharpGen.Platform.csproj", "{A7FF5742-4C58-487C-ADD1-2FF2382D7D99}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpGen.Platform", "SharpGen.Platform\SharpGen.Platform.csproj", "{A7FF5742-4C58-487C-ADD1-2FF2382D7D99}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpGen.Generator", "SharpGen.Generator\SharpGen.Generator.csproj", "{B4D7CA39-8C95-4472-91E6-7D9E51AC2E1F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpGen.Generator", "SharpGen.Generator\SharpGen.Generator.csproj", "{B4D7CA39-8C95-4472-91E6-7D9E51AC2E1F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpGen.Runtime.Trim.Dummy", "SharpGen.Runtime.Trim.Dummy\SharpGen.Runtime.Trim.Dummy.csproj", "{D72FF74B-6FBB-4394-ADA5-E184339F8A80}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -45,12 +46,14 @@ Global {B4D7CA39-8C95-4472-91E6-7D9E51AC2E1F}.Debug|Any CPU.Build.0 = Debug|Any CPU {B4D7CA39-8C95-4472-91E6-7D9E51AC2E1F}.Release|Any CPU.ActiveCfg = Release|Any CPU {B4D7CA39-8C95-4472-91E6-7D9E51AC2E1F}.Release|Any CPU.Build.0 = Release|Any CPU + {D72FF74B-6FBB-4394-ADA5-E184339F8A80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D72FF74B-6FBB-4394-ADA5-E184339F8A80}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D72FF74B-6FBB-4394-ADA5-E184339F8A80}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D72FF74B-6FBB-4394-ADA5-E184339F8A80}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(NestedProjects) = preSolution - EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E022185F-62DA-4472-98A7-DA67215F61B1} EndGlobalSection From d3ad945a4f459fa450cbd5f822a70d5d436687df Mon Sep 17 00:00:00 2001 From: Sewer 56 Date: Sun, 26 Jun 2022 15:39:38 +0100 Subject: [PATCH 14/55] Improved: Annotation for TrimmingExtensions --- SharpGen.Runtime/CallbackBase.Reflection.cs | 2 +- .../CallbackBase.ReflectionCache.cs | 2 +- SharpGen.Runtime/SharpGen.Runtime.csproj | 4 ++-- .../TrimmingWrappers/TrimmingExtensions.cs | 17 +++++++++++++---- SharpGen.Runtime/TypeDataStorage.cs | 2 +- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/SharpGen.Runtime/CallbackBase.Reflection.cs b/SharpGen.Runtime/CallbackBase.Reflection.cs index 4bcecfd4..a265bed5 100644 --- a/SharpGen.Runtime/CallbackBase.Reflection.cs +++ b/SharpGen.Runtime/CallbackBase.Reflection.cs @@ -131,7 +131,7 @@ private ImmediateShadowInterfaceInfo[] BuildVtblList() foreach (var implementedInterface in type.ImplementedInterfaces) { - var item = implementedInterface.GetTypeInfoWithPreservedInterfaces(); + var item = implementedInterface.GetTypeInfoWithNestedPreservedInterfaces(); // Only process interfaces that have vtbl if (!VtblAttribute.Has(item)) diff --git a/SharpGen.Runtime/CallbackBase.ReflectionCache.cs b/SharpGen.Runtime/CallbackBase.ReflectionCache.cs index b74cf6c7..7b3d6582 100644 --- a/SharpGen.Runtime/CallbackBase.ReflectionCache.cs +++ b/SharpGen.Runtime/CallbackBase.ReflectionCache.cs @@ -13,7 +13,7 @@ public abstract partial class CallbackBase private CallbackTypeInfo GetTypeInfo() { CallbackTypeInfo? info; - var type = this.GetTypeWithPreservedInterfaces(); + var type = this.GetTypeWithNestedPreservedInterfaces(); var cache = TypeReflectionCache; lock (cache) diff --git a/SharpGen.Runtime/SharpGen.Runtime.csproj b/SharpGen.Runtime/SharpGen.Runtime.csproj index 776d19b6..6f3c832f 100644 --- a/SharpGen.Runtime/SharpGen.Runtime.csproj +++ b/SharpGen.Runtime/SharpGen.Runtime.csproj @@ -5,8 +5,8 @@ + + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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/SharpGen.Generator/SharpGen.Generator.csproj b/SharpGen.Generator/SharpGen.Generator.csproj index 278150d3..3fb6dbce 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 + - - netstandard2.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 7013215a..120ccc24 100644 --- a/SharpGen.Generator/SharpGenModuleGenerator.GenerateModule.cs +++ b/SharpGen.Generator/SharpGenModuleGenerator.GenerateModule.cs @@ -268,7 +268,7 @@ 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 d0b9e2c3..c5afd455 100644 --- a/SharpGen.Generator/SharpGenModuleGenerator.GenerateUtilities.cs +++ b/SharpGen.Generator/SharpGenModuleGenerator.GenerateUtilities.cs @@ -45,44 +45,4 @@ public sealed partial class SharpGenModuleGenerator ) ) ); - - private static void GenerateUtilities(GeneratorExecutionContext context) - { - List attributes = new(1); - - var moduleInitType = context.Compilation.GetTypeByMetadataName(ModuleInitializerAttributeName); - if (moduleInitType is not { IsReferenceType: true, IsGenericType: false } || - !context.Compilation.IsSymbolAccessibleWithin(moduleInitType, context.Compilation.Assembly)) - 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 96b53bcc..bd7be72b 100644 --- a/SharpGen.Generator/SharpGenModuleGenerator.cs +++ b/SharpGen.Generator/SharpGenModuleGenerator.cs @@ -63,8 +63,6 @@ public void Execute(GeneratorExecutionContext context) if (context.CancellationToken.IsCancellationRequested) return; - GenerateUtilities(context); - if (context.CancellationToken.IsCancellationRequested) return; diff --git a/SharpGen.Platform/SharpGen.Platform.csproj b/SharpGen.Platform/SharpGen.Platform.csproj index 26844435..057c21ae 100644 --- a/SharpGen.Platform/SharpGen.Platform.csproj +++ b/SharpGen.Platform/SharpGen.Platform.csproj @@ -1,53 +1,51 @@ - + + Library + net472;net6.0 + 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 + + diff --git a/SharpGen.Runtime/SharpGen.Runtime.csproj b/SharpGen.Runtime/SharpGen.Runtime.csproj index daa3face..6ab350ae 100644 --- a/SharpGen.Runtime/SharpGen.Runtime.csproj +++ b/SharpGen.Runtime/SharpGen.Runtime.csproj @@ -1,48 +1,43 @@ - + - - - - - - net6.0;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 - + netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0;net7.0;net461;net471 + Support classes for code generated by SharpGen. + true + false + true + true + preview + true + - - - - - - + + + + + + - - - - + + + + - - - + + + - - - + + + diff --git a/SharpGen.UnitTests/SharpGen.UnitTests.csproj b/SharpGen.UnitTests/SharpGen.UnitTests.csproj index 920321b6..40ac5058 100644 --- a/SharpGen.UnitTests/SharpGen.UnitTests.csproj +++ b/SharpGen.UnitTests/SharpGen.UnitTests.csproj @@ -1,27 +1,22 @@  + + net6.0 + false + true + - + + + + - - net5.0 - false - true - - - - - - - - - - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/SharpGen/Generator/RoslynSyntaxNormalizer.cs b/SharpGen/Generator/RoslynSyntaxNormalizer.cs index 3472f536..10a4d43f 100644 --- a/SharpGen/Generator/RoslynSyntaxNormalizer.cs +++ b/SharpGen/Generator/RoslynSyntaxNormalizer.cs @@ -111,7 +111,7 @@ private SyntaxToken GetNextRelevantToken(SyntaxToken token) do { nextToken = nextToken.GetNextToken(true, true); - } while (nextToken != default && nextToken.Span.Length == 0 && nextToken.Kind() != SyntaxKind.EndOfDirectiveToken); + } while (nextToken != default && nextToken.Span.Length == 0 && !nextToken.IsKind(SyntaxKind.EndOfDirectiveToken)); return _consideredSpan.Contains(nextToken.FullSpan) ? nextToken : default; } @@ -156,7 +156,7 @@ private int LineBreaksAfter(SyntaxToken currentToken, SyntaxToken nextToken) return 1; } - if (nextToken.Kind() == SyntaxKind.None) + if (nextToken.IsKind(SyntaxKind.None)) { return 0; } @@ -197,8 +197,8 @@ private int LineBreaksAfter(SyntaxToken currentToken, SyntaxToken nextToken) // Note: the `where` case handles constraints on method declarations // and also `where` clauses (consistently with other LINQ cases below) return (currentToken.Parent is StatementSyntax && nextToken.Parent != currentToken.Parent) - || nextToken.Kind() == SyntaxKind.OpenBraceToken - || nextToken.Kind() == SyntaxKind.WhereKeyword + || nextToken.IsKind(SyntaxKind.OpenBraceToken) + || nextToken.IsKind(SyntaxKind.WhereKeyword) ? 1 : 0; diff --git a/SharpGen/SharpGen.csproj b/SharpGen/SharpGen.csproj index 74864ea9..fd610dc4 100644 --- a/SharpGen/SharpGen.csproj +++ b/SharpGen/SharpGen.csproj @@ -1,26 +1,19 @@  - + + Library + netstandard2.0 + true + - - Library - netstandard2.0 - + + + - - - - - - - - - - - - - <_Parameter1>SharpGen.UnitTests, PublicKey=$(SharpGenPublicKey) - - + + + <_Parameter1>SharpGen.UnitTests, PublicKey=$(SharpGenPublicKey) + + \ No newline at end of file diff --git a/SharpGenTools.Sdk/Extensibility/ExtensibilityDriver.cs b/SharpGenTools.Sdk/Extensibility/ExtensibilityDriver.cs index 3063f70c..810ce0b5 100644 --- a/SharpGenTools.Sdk/Extensibility/ExtensibilityDriver.cs +++ b/SharpGenTools.Sdk/Extensibility/ExtensibilityDriver.cs @@ -54,7 +54,7 @@ private void ResolveExtensibilityPoints(LoggerBase logger, out ImmutableArray(); - void ErrorHandler(object o, ExtensionLoadFailureEventArgs e) + void ErrorHandler(object? o, ExtensionLoadFailureEventArgs e) { var analyzerReference = o as ExtensionReference; Debug.Assert(analyzerReference != null); diff --git a/SharpGenTools.Sdk/Extensibility/ExtensionFileReference.cs b/SharpGenTools.Sdk/Extensibility/ExtensionFileReference.cs index 5577f2ae..48fcc008 100644 --- a/SharpGenTools.Sdk/Extensibility/ExtensionFileReference.cs +++ b/SharpGenTools.Sdk/Extensibility/ExtensionFileReference.cs @@ -97,7 +97,9 @@ public bool Equals(ExtensionReference? other) } public override int GetHashCode() - => HashCode.Combine(RuntimeHelpers.GetHashCode(AssemblyLoader), FullPath.GetHashCode()); + { + return HashCode.Combine(RuntimeHelpers.GetHashCode(AssemblyLoader), FullPath.GetHashCode()); + } public override ImmutableArray GetDocumentationProviders() { diff --git a/SharpGenTools.Sdk/SharpGenTask.PropertyCache.cs b/SharpGenTools.Sdk/SharpGenTask.PropertyCache.cs index 5db45967..040d6ebb 100644 --- a/SharpGenTools.Sdk/SharpGenTask.PropertyCache.cs +++ b/SharpGenTools.Sdk/SharpGenTask.PropertyCache.cs @@ -29,10 +29,14 @@ private bool GeneratePropertyCache() }; if (!string.Equals(PlatformName, "AnyCPU", StringComparison.InvariantCultureIgnoreCase)) - parts.Add(PlatformName); + { + parts.Add(PlatformName!); + } if (!string.IsNullOrWhiteSpace(RuntimeIdentifier)) + { parts.Add(RuntimeIdentifier); + } CacheFile cacheFile = new(); try diff --git a/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj b/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj index 5258983b..a56f55a0 100644 --- a/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj +++ b/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj @@ -1,98 +1,97 @@  - - - - - net472;net5.0 - SharpGenTools.Sdk - true - false - - MSBuildSdk - true - true - - $([System.IO.Path]::Combine('$(IntermediateOutputPath)', 'Sdk.Version.props')) - - - true - true - win;unix - MSBuild tooling for SharpGen. Tooling for generating efficient C# code from C++ and COM headers. - true - false - IncludeDefaultProjectBuildOutputInPack - - - - - - - - - - - - Code - - - Code - - - Code - - - - - - - - - - - - - - - Content - PreserveNewest - - Build;DebugSymbolsProjectOutputGroup - - - - - - - - - - - - - - - $([System.IO.Path]::Combine('$(IntermediateOutputPath)', 'Sdk.Version.props')) - - - - - <_VersionPropsLine Include="<Project>" /> - <_VersionPropsLine Include="<PropertyGroup>" /> - <_VersionPropsLine Include="<SharpGenSdkVersion Condition="'%24(SharpGenSdkVersion)'==''">$(PackageVersion)</SharpGenSdkVersion>" /> - <_VersionPropsLine Include="</PropertyGroup>" /> - <_VersionPropsLine Include="</Project>" /> - - - - - - - - - - - + + + + net472;net6.0 + SharpGenTools.Sdk + true + false + + MSBuildSdk + true + true + + $([System.IO.Path]::Combine('$(IntermediateOutputPath)', 'Sdk.Version.props')) + + + true + true + win;unix + MSBuild tooling for SharpGen. Tooling for generating efficient C# code from C++ and COM headers. + true + false + IncludeDefaultProjectBuildOutputInPack + true + + + + + + + + + + + + Code + + + Code + + + Code + + + + + + + + + + + + + + + Content + PreserveNewest + + Build;DebugSymbolsProjectOutputGroup + + + + + + + + + + + + + + + $([System.IO.Path]::Combine('$(IntermediateOutputPath)', 'Sdk.Version.props')) + + + + + <_VersionPropsLine Include="<Project>" /> + <_VersionPropsLine Include="<PropertyGroup>" /> + <_VersionPropsLine Include="<SharpGenSdkVersion Condition="'%24(SharpGenSdkVersion)'==''">$(PackageVersion)</SharpGenSdkVersion>" /> + <_VersionPropsLine Include="</PropertyGroup>" /> + <_VersionPropsLine Include="</Project>" /> + + + + + + + + + + \ No newline at end of file diff --git a/SharpGenTools.sln b/SharpGenTools.sln index 9d073060..66aed18f 100644 --- a/SharpGenTools.sln +++ b/SharpGenTools.sln @@ -18,7 +18,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpGen.Runtime.Trim.Dummy EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Trimming", "Trimming", "{14DCB75C-3646-4E74-9B98-5ABF783F0F04}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpGen.Runtime.Trim.Dummy.CallbackTest", "SharpGen.Runtime.Trim.Dummy.CallbackTest\SharpGen.Runtime.Trim.Dummy.CallbackTest.csproj", "{BB8F0A30-3FE6-4F26-9E5F-EF9A70198D18}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpGen.Runtime.Trim.Dummy.CallbackTest", "SharpGen.Runtime.Trim.Dummy.CallbackTest\SharpGen.Runtime.Trim.Dummy.CallbackTest.csproj", "{BB8F0A30-3FE6-4F26-9E5F-EF9A70198D18}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".root", ".root", "{A3BC86F0-22E9-426A-BE0E-A2176A405501}" + ProjectSection(SolutionItems) = preProject + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + Directory.Packages.props = Directory.Packages.props + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/global.json b/global.json index a1727a81..949010bd 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,5 @@ { "msbuild-sdks": { - "MSBuild.Sdk.Extras": "3.0.44", - "Microsoft.Build.CentralPackageVersions" : "2.1.3", - "Microsoft.DotNet.PackageValidation" : "1.0.0-preview.7.21379.12" + "MSBuild.Sdk.Extras": "3.0.44" } } \ No newline at end of file From febc2644e8ff1d6b88dbfbac6c73565f4f46e2f3 Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Tue, 17 Jan 2023 12:34:14 +0100 Subject: [PATCH 33/55] FIX SharpGen.Runtime.COM build. --- .../SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj index 5f382ac7..8707f5b8 100644 --- a/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj +++ b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj @@ -1,17 +1,14 @@ - - - - net5.0;netstandard2.0;net45;netstandard1.3 + net6.0;netstandard2.0;net45;netstandard1.3 SharpGen.Runtime C# COM Interop classes for use with SharpGenTools generated libraries true From 7917a51b8203bcdd3e3fc0750b7f93f1888838ea Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Tue, 17 Jan 2023 14:11:33 +0100 Subject: [PATCH 34/55] More improvements. --- Directory.Packages.props | 3 ++- SharpGen.Platform/IncludeDirectoryResolver.cs | 6 ++++++ SharpGen.Platform/SharpGen.Platform.csproj | 2 ++ SharpGen.UnitTests/SharpGen.UnitTests.csproj | 1 + SharpGenTools.sln | 3 +++ 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 69dbd26a..44a0e143 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -28,7 +28,8 @@ - + + 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 057c21ae..8801376a 100644 --- a/SharpGen.Platform/SharpGen.Platform.csproj +++ b/SharpGen.Platform/SharpGen.Platform.csproj @@ -4,9 +4,11 @@ Library net472;net6.0 true + true + diff --git a/SharpGen.UnitTests/SharpGen.UnitTests.csproj b/SharpGen.UnitTests/SharpGen.UnitTests.csproj index 40ac5058..dfbd4ee3 100644 --- a/SharpGen.UnitTests/SharpGen.UnitTests.csproj +++ b/SharpGen.UnitTests/SharpGen.UnitTests.csproj @@ -17,6 +17,7 @@ + \ No newline at end of file diff --git a/SharpGenTools.sln b/SharpGenTools.sln index 66aed18f..6d482c46 100644 --- a/SharpGenTools.sln +++ b/SharpGenTools.sln @@ -27,6 +27,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".root", ".root", "{A3BC86F0 Directory.Packages.props = Directory.Packages.props EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F671E5B9-5D3D-44EF-8B6F-F3702FB70DF1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -70,6 +72,7 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution + {13B246C9-F127-4AC5-8847-E9214C0ABD70} = {F671E5B9-5D3D-44EF-8B6F-F3702FB70DF1} {D72FF74B-6FBB-4394-ADA5-E184339F8A80} = {14DCB75C-3646-4E74-9B98-5ABF783F0F04} {BB8F0A30-3FE6-4F26-9E5F-EF9A70198D18} = {14DCB75C-3646-4E74-9B98-5ABF783F0F04} EndGlobalSection From 6cfa544246409aad237ab7dd6ac5ab4d052f9349 Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Thu, 19 Jan 2023 10:55:24 +0100 Subject: [PATCH 35/55] Remove net5 and move to net6 and improve build logic. --- README.md | 6 +-- SdkTests/Managed.props | 6 +-- SharpGen.Generator/SharpGenModuleGenerator.cs | 2 +- SharpGen.Runtime/COM/ComObject.cs | 18 ++++---- SharpGen.Runtime/COM/ComObjectVtbl.cs | 20 ++++----- SharpGen.Runtime/COM/InspectableVtbl.cs | 20 ++++----- SharpGen.Runtime/CallbackBase.Reflection.cs | 10 ----- .../CallbackBase.ReflectionImpl.cs | 4 +- SharpGen.Runtime/CallbackBase.cs | 6 +-- SharpGen.Runtime/InterfaceArray.cs | 2 +- SharpGen.Runtime/MarshallingHelpers.cs | 4 +- SharpGen.Runtime/NativeLong.cs | 6 +-- SharpGen.Runtime/NativeULong.cs | 44 +++++++++++-------- SharpGen.Runtime/ShadowAttribute.cs | 4 +- SharpGen.Runtime/SharpGen.Runtime.csproj | 4 +- .../Trimming/TrimmingExtensions.cs | 8 ---- SharpGen.Runtime/Trimming/TrimmingHelpers.cs | 2 +- SharpGen.Runtime/TypeDataStorage.cs | 4 -- SharpGen.Runtime/VtblAttribute.cs | 4 +- SharpGen/Generator/GeneratorHelpers.cs | 2 +- SharpGenTools.Sdk/Sdk.targets | 2 +- build.ps1 | 2 +- build/run-outerloop-tests.ps1 | 2 +- 23 files changed, 83 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index 9033fa6d..2cd78032 100644 --- a/README.md +++ b/README.md @@ -31,14 +31,14 @@ 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) + * .NET SDK (6 or newer) + * .NET Core SDK (3.1 or newer) * Visual Studio 2017.3 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: 6.0 or newer. * CMake: 3.0 or newer. * SDK tests require x64 Windows, VS2019 with x86 and x64 C++ compilers, recent PowerShell version. diff --git a/SdkTests/Managed.props b/SdkTests/Managed.props index c55d73c3..01aafa44 100644 --- a/SdkTests/Managed.props +++ b/SdkTests/Managed.props @@ -12,12 +12,12 @@ - net5.0 + net6.0 x86 - netcoreapp2.1;net5.0 + netcoreapp2.1;net6.0 @@ -59,7 +59,7 @@ + Condition="'$(TargetFramework)' != 'net472' and '$(TargetFramework)' != 'netcoreapp2.1' and '$(TargetFramework)' != 'net6.0'"/> diff --git a/SharpGen.Generator/SharpGenModuleGenerator.cs b/SharpGen.Generator/SharpGenModuleGenerator.cs index bd7be72b..0070d33b 100644 --- a/SharpGen.Generator/SharpGenModuleGenerator.cs +++ b/SharpGen.Generator/SharpGenModuleGenerator.cs @@ -30,7 +30,7 @@ public sealed partial class SharpGenModuleGenerator : ISourceGenerator Identifier( TriviaList ( - Trivia(IfDirectiveTrivia(IdentifierName("NET5_0_OR_GREATER"), true, false, false)), + Trivia(IfDirectiveTrivia(IdentifierName("NET6_0_OR_GREATER"), true, false, false)), DisabledText(@"[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage(""ReflectionAnalysis"", ""IL2111"")] [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage(""ReflectionAnalysis"", ""IL2110"")] "), diff --git a/SharpGen.Runtime/COM/ComObject.cs b/SharpGen.Runtime/COM/ComObject.cs index 2060fe0f..b1ce6bd7 100644 --- a/SharpGen.Runtime/COM/ComObject.cs +++ b/SharpGen.Runtime/COM/ComObject.cs @@ -75,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)) @@ -112,7 +112,7 @@ public virtual IntPtr QueryInterfaceOrNull(Guid guid) /// IUnknown::QueryInterface /// IUnknown::QueryInterface public virtual T QueryInterface< -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] #endif T>() where T : ComObject @@ -130,11 +130,11 @@ public virtual T QueryInterface< /// ms682521 /// IUnknown::QueryInterface /// IUnknown::QueryInterface -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER [SupportedOSPlatform("windows")] #endif public static T As< -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] #endif T>(object comObject) where T : ComObject => As(Marshal.GetIUnknownForObject(comObject)); @@ -149,7 +149,7 @@ public static T As< /// IUnknown::QueryInterface /// IUnknown::QueryInterface public static T As< -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] #endif T>(IntPtr iunknownPtr) where T : ComObject @@ -167,11 +167,11 @@ public static T As< /// ms682521 /// IUnknown::QueryInterface /// IUnknown::QueryInterface -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER [SupportedOSPlatform("windows")] #endif public static T QueryInterface< -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] #endif T>(object comObject) where T : ComObject => @@ -187,7 +187,7 @@ public static T QueryInterface< /// IUnknown::QueryInterface /// IUnknown::QueryInterface public static T? QueryInterfaceOrNull< -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] #endif T>(IntPtr comPointer) where T : ComObject @@ -205,7 +205,7 @@ public static T? QueryInterfaceOrNull< /// IUnknown::QueryInterface /// IUnknown::QueryInterface public virtual T? QueryInterfaceOrNull< -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] #endif T>() where T : ComObject 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 7fbc3165..4d3f75f0 100644 --- a/SharpGen.Runtime/CallbackBase.Reflection.cs +++ b/SharpGen.Runtime/CallbackBase.Reflection.cs @@ -15,8 +15,6 @@ private readonly struct ImmediateShadowInterfaceInfo { #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] -#elif NET5_0_OR_GREATER - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] #endif public readonly TypeInfo Type; public readonly List ImplementedInterfaces; @@ -24,8 +22,6 @@ private readonly struct ImmediateShadowInterfaceInfo public ImmediateShadowInterfaceInfo( #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] -#elif NET5_0_OR_GREATER - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] #endif TypeInfo type) { @@ -50,8 +46,6 @@ private class CallbackTypeInfo { #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] -#elif NET5_0_OR_GREATER - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] #endif private readonly TypeInfo type; private ImmediateShadowInterfaceInfo[]? _vtbls; @@ -61,8 +55,6 @@ private class CallbackTypeInfo public CallbackTypeInfo( #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] -#elif NET5_0_OR_GREATER - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] #endif Type type) : this(type.GetTypeInfo()) { @@ -71,8 +63,6 @@ public CallbackTypeInfo( private CallbackTypeInfo( #if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] -#elif NET5_0_OR_GREATER - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] #endif TypeInfo type) { diff --git a/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs b/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs index ff22c848..61076089 100644 --- a/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs +++ b/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs @@ -15,7 +15,7 @@ public abstract unsafe partial class CallbackBase protected virtual Guid[] BuildGuidList() => GetTypeInfo().Guids; private GCHandle CreateShadow( -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] #endif TypeInfo type) @@ -28,7 +28,7 @@ private GCHandle CreateShadow( return GCHandle.Alloc(shadow, GCHandleType.Normal); } -#if NET5_0_OR_GREATER +#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 diff --git a/SharpGen.Runtime/CallbackBase.cs b/SharpGen.Runtime/CallbackBase.cs index b96ba2f5..0be15dff 100644 --- a/SharpGen.Runtime/CallbackBase.cs +++ b/SharpGen.Runtime/CallbackBase.cs @@ -40,7 +40,7 @@ public abstract unsafe partial class CallbackBase : DisposeBase, ICallbackable private Dictionary? _ccw; private IntPtr _guidPtr; private IntPtr[]? _guids; -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER private uint _refCount = 1; #else private int _refCount = 1; @@ -80,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); @@ -89,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); diff --git a/SharpGen.Runtime/InterfaceArray.cs b/SharpGen.Runtime/InterfaceArray.cs index 887f3aae..cad61622 100644 --- a/SharpGen.Runtime/InterfaceArray.cs +++ b/SharpGen.Runtime/InterfaceArray.cs @@ -34,7 +34,7 @@ namespace SharpGen.Runtime; [DebuggerDisplay("Count={" + nameof(Length) + "}")] [SuppressMessage("ReSharper", "ConvertToAutoProperty")] public unsafe struct InterfaceArray< -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] #endif T> : IReadOnlyList, IEnlightenedDisposable, IDisposable diff --git a/SharpGen.Runtime/MarshallingHelpers.cs b/SharpGen.Runtime/MarshallingHelpers.cs index db0ce342..89941624 100644 --- a/SharpGen.Runtime/MarshallingHelpers.cs +++ b/SharpGen.Runtime/MarshallingHelpers.cs @@ -15,7 +15,7 @@ public static partial class MarshallingHelpers /// The native pointer to a C++ object. /// An instance of T bound to the native pointer public static T? FromPointer< -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] #endif T>(IntPtr cppObjectPtr) where T : CppObject @@ -37,7 +37,7 @@ public static T? FromPointer< /// The native pointer to a C++ object. /// An instance of T bound to the native pointer public static T? FromPointer< -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] #endif T>(UIntPtr cppObjectPtr) where T : CppObject diff --git a/SharpGen.Runtime/NativeLong.cs b/SharpGen.Runtime/NativeLong.cs index 951b8f65..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_OR_GREATER +#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_OR_GREATER - 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 a652b5ff..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_OR_GREATER +#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_OR_GREATER - 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/ShadowAttribute.cs b/SharpGen.Runtime/ShadowAttribute.cs index 9e7b28df..6e4fe2e7 100644 --- a/SharpGen.Runtime/ShadowAttribute.cs +++ b/SharpGen.Runtime/ShadowAttribute.cs @@ -35,7 +35,7 @@ public sealed class ShadowAttribute : Attribute /// /// Type of the associated shadow /// -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] #endif public Type Type { get; } @@ -45,7 +45,7 @@ public sealed class ShadowAttribute : Attribute /// /// Type of the associated shadow public ShadowAttribute( -#if NET5_0_OR_GREATER +#if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] #endif Type holder) diff --git a/SharpGen.Runtime/SharpGen.Runtime.csproj b/SharpGen.Runtime/SharpGen.Runtime.csproj index 6ab350ae..a1f81491 100644 --- a/SharpGen.Runtime/SharpGen.Runtime.csproj +++ b/SharpGen.Runtime/SharpGen.Runtime.csproj @@ -2,8 +2,8 @@ - net6.0;netstandard2.0;net45;netstandard1.3 - SharpGen.Runtime - C# COM Interop classes for use with SharpGenTools generated libraries - true - - true - true - true - $(CoreCompileDependsOn);SharpGenSetRoslynGeneratedPath - - - - true - false - - - - - - - - - - - $(IntermediateOutputPath)Generated - - - - - - true - - - - - - false - - - - - - + + + + + netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0;net7.0;net461;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 + + + + + + From 42f446e37c5e99681d754a846c14e1a2a8205a8a Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Thu, 19 Jan 2023 11:37:47 +0100 Subject: [PATCH 37/55] Include net7 SDK in global.json --- SharpGen.Runtime/SharpGen.Runtime.csproj | 16 ++++++++-------- global.json | 10 +++++++--- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/SharpGen.Runtime/SharpGen.Runtime.csproj b/SharpGen.Runtime/SharpGen.Runtime.csproj index a1f81491..d1d6c2cc 100644 --- a/SharpGen.Runtime/SharpGen.Runtime.csproj +++ b/SharpGen.Runtime/SharpGen.Runtime.csproj @@ -2,14 +2,14 @@ + net6.0/net7.0 for full assembly trimming + net6.0/net7.0 for function pointers codegen and Native(U)Long API surface and partial assembly trimming + netcoreapp3.0 for smaller [empty] dependency tree (w/o System.Runtime.CompilerServices.Unsafe) + netstandard2.1 is .NET Core 3.0, but System.Memory is already inbox since 2.1 + netstandard2.0 for smaller dependency tree than netstandard1.3 + net471 for even smaller dependency tree on latest .NET Framework versions (w/o System.Runtime.InteropServices.RuntimeInformation) + net46 for using proper FormattableString instead of Shim/* version + --> netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0;net7.0;net461;net471 Support classes for code generated by SharpGen. true diff --git a/global.json b/global.json index 949010bd..b6038be0 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,9 @@ { - "msbuild-sdks": { - "MSBuild.Sdk.Extras": "3.0.44" - } + "sdk": { + "version": "7.0.101", + "rollForward": "latestFeature" + }, + "msbuild-sdks": { + "MSBuild.Sdk.Extras": "3.0.44" + } } \ No newline at end of file From 33c448af66ee065838668e3812f7627d10d60355 Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Thu, 19 Jan 2023 11:54:47 +0100 Subject: [PATCH 38/55] Improve SdkTests build logic. --- Directory.Packages.props | 1 + SdkTests/Directory.Build.props | 2 -- SdkTests/Directory.Build.targets | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 44a0e143..b3f2b3fb 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -32,6 +32,7 @@ + \ 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 @@ /> - - From c37e9783bfac30d40816b760dcd54261cd8d667d Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Thu, 19 Jan 2023 12:04:01 +0100 Subject: [PATCH 39/55] Use netcoreapp3.1 instead of netcoreapp2.1 in Tests --- SdkTests/Managed.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SdkTests/Managed.props b/SdkTests/Managed.props index 01aafa44..5175ab23 100644 --- a/SdkTests/Managed.props +++ b/SdkTests/Managed.props @@ -17,7 +17,7 @@ - netcoreapp2.1;net6.0 + netcoreapp3.1;net6.0 @@ -59,7 +59,7 @@ + Condition="'$(TargetFramework)' != 'net472' and '$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net6.0'"/> From 461a942deb05c6f82489d11eccfb3c05b2d85def Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Thu, 19 Jan 2023 12:35:18 +0100 Subject: [PATCH 40/55] Improve roslyn obsolete calls and copy SharpGen.Runtime.COM nuget packages as well. --- SharpGen/Generator/PropertyCodeGenerator.cs | 2 +- SharpGen/Generator/RoslynSyntaxNormalizer.cs | 22 ++++++++++---------- azure-pipelines.yml | 7 +++++++ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/SharpGen/Generator/PropertyCodeGenerator.cs b/SharpGen/Generator/PropertyCodeGenerator.cs index 865dcddf..87612197 100644 --- a/SharpGen/Generator/PropertyCodeGenerator.cs +++ b/SharpGen/Generator/PropertyCodeGenerator.cs @@ -138,7 +138,7 @@ public override IEnumerable GenerateCode(CsProperty csE { var paramByRef = GetMarshaller(csElement.Setter.Parameters[0]) .GenerateManagedArgument(csElement.Setter.Parameters[0]) - .RefOrOutKeyword.Kind() == SyntaxKind.RefKeyword; + .RefOrOutKeyword.IsKind(SyntaxKind.RefKeyword); accessors.Add(AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) .WithExpressionBody(ArrowExpressionClause( diff --git a/SharpGen/Generator/RoslynSyntaxNormalizer.cs b/SharpGen/Generator/RoslynSyntaxNormalizer.cs index 10a4d43f..85ffd379 100644 --- a/SharpGen/Generator/RoslynSyntaxNormalizer.cs +++ b/SharpGen/Generator/RoslynSyntaxNormalizer.cs @@ -60,7 +60,7 @@ internal static TNode Normalize(TNode node, string indentWhitespace, stri public override SyntaxToken VisitToken(SyntaxToken token) { - if (token.Kind() == SyntaxKind.None || (token.IsMissing && token.FullSpan.Length == 0)) + if (token.IsKind(SyntaxKind.None) || (token.IsMissing && token.FullSpan.Length == 0)) { return token; } @@ -197,7 +197,7 @@ private int LineBreaksAfter(SyntaxToken currentToken, SyntaxToken nextToken) // Note: the `where` case handles constraints on method declarations // and also `where` clauses (consistently with other LINQ cases below) return (currentToken.Parent is StatementSyntax && nextToken.Parent != currentToken.Parent) - || nextToken.IsKind(SyntaxKind.OpenBraceToken) + || nextToken.IsKind(SyntaxKind.OpenBraceToken) || nextToken.IsKind(SyntaxKind.WhereKeyword) ? 1 : 0; @@ -216,7 +216,7 @@ private int LineBreaksAfter(SyntaxToken currentToken, SyntaxToken nextToken) case SyntaxKind.CommaToken: return currentToken.Parent is EnumDeclarationSyntax or SwitchExpressionSyntax ? 1 : 0; case SyntaxKind.ElseKeyword: - return nextToken.Kind() != SyntaxKind.IfKeyword ? 1 : 0; + return !nextToken.IsKind(SyntaxKind.IfKeyword) ? 1 : 0; case SyntaxKind.ColonToken: if (currentToken.Parent is LabeledStatementSyntax or SwitchLabelSyntax) { @@ -338,7 +338,7 @@ private static int LineBreaksAfterSemicolon(SyntaxToken currentToken, SyntaxToke return 0; } - if (nextToken.Kind() == SyntaxKind.CloseBraceToken) + if (nextToken.IsKind(SyntaxKind.CloseBraceToken)) { return 1; } @@ -505,7 +505,7 @@ private static bool NeedsSeparator(SyntaxToken token, SyntaxToken next) return false; } - if (next.Kind() == SyntaxKind.EndOfDirectiveToken) + if (next.IsKind(SyntaxKind.EndOfDirectiveToken)) { // In a directive, there's often no token between the directive keyword and // the end-of-directive, so we may need a separator. @@ -540,8 +540,8 @@ private static bool NeedsSeparator(SyntaxToken token, SyntaxToken next) return true; } - if (token.Kind() == SyntaxKind.SemicolonToken - && !(next.Kind() == SyntaxKind.SemicolonToken || next.Kind() == SyntaxKind.CloseParenToken)) + if (token.IsKind(SyntaxKind.SemicolonToken) + && !(next.IsKind(SyntaxKind.SemicolonToken) || next.IsKind(SyntaxKind.CloseParenToken))) { return true; } @@ -1059,17 +1059,17 @@ private static bool NeedsIndentAfterLineBreak(SyntaxTrivia trivia) private static bool IsLineBreak(SyntaxToken token) { - return token.Kind() == SyntaxKind.XmlTextLiteralNewLineToken; + return token.IsKind(SyntaxKind.XmlTextLiteralNewLineToken); } private static bool EndsInLineBreak(SyntaxTrivia trivia) { - if (trivia.Kind() == SyntaxKind.EndOfLineTrivia) + if (trivia.IsKind(SyntaxKind.EndOfLineTrivia)) { return true; } - if (trivia.Kind() == SyntaxKind.PreprocessingMessageTrivia || trivia.Kind() == SyntaxKind.DisabledTextTrivia) + if (trivia.IsKind(SyntaxKind.PreprocessingMessageTrivia) || trivia.IsKind(SyntaxKind.DisabledTextTrivia)) { var text = trivia.ToFullString(); return text.Length > 0 && SyntaxFacts.IsNewLine(text.Last()); @@ -1181,7 +1181,7 @@ or SwitchSectionSyntax or SwitchExpressionArmSyntax or UsingDirectiveSyntax public override SyntaxNode? VisitInterpolatedStringExpression(InterpolatedStringExpressionSyntax node) { - if (node.StringStartToken.Kind() == SyntaxKind.InterpolatedStringStartToken) + if (node.StringStartToken.IsKind(SyntaxKind.InterpolatedStringStartToken)) { //Just for non verbatim strings we want to make sure that the formatting of interpolations does not emit line breaks. //See: https://github.com/dotnet/roslyn/issues/50742 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 18d189f7..e22f4ce2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -74,6 +74,13 @@ stages: contents: 'Sharp*/bin/Release/Sharp*@(.nupkg|.snupkg)' targetFolder: '$(Build.ArtifactStagingDirectory)' flattenFolders: true + - task: CopyFiles@2 + condition: eq(variables['Config'], 'Release') + displayName: 'Copy SharpGen.Runtime.COM NuGet packages to Artifact staging directory' + inputs: + contents: 'SharpGen.Runtime.COM/SharpGen.Runtime.COM/bin/Release/Sharp*@(.nupkg|.snupkg)' + targetFolder: '$(Build.ArtifactStagingDirectory)' + flattenFolders: true - task: PublishBuildArtifacts@1 condition: eq(variables['Config'], 'Release') displayName: Publish NuGet Packages From 1ed1c3388eaecba02d4908688a45341d37b76aa6 Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Mon, 4 Sep 2023 17:04:49 +0200 Subject: [PATCH 41/55] Update some packages. --- Directory.Build.props | 2 +- Directory.Packages.props | 10 +++++----- docs/getting-started.rst | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 4c271374..709c9368 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -42,7 +42,7 @@ jkoritzinsky - (c) 2010-2017 Alexandre Mutel, 2017-2018 Jeremy Koritzinsky + (c) 2010-2017 Alexandre Mutel, 2017-2023 Jeremy Koritzinsky MIT https://github.com/SharpGenTools/SharpGenTools SharpGen;CodeGen;CPlusPlus;PInvoke;Native;COM diff --git a/Directory.Packages.props b/Directory.Packages.props index b3f2b3fb..d4bdf747 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -10,7 +10,7 @@ - + @@ -24,11 +24,11 @@ - - - + + + - + diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 0dba94f6..7764b615 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -37,7 +37,7 @@ Create a file named ``Mapping.xml`` with the following content: Let's go through each of these elements. * The ``config`` tag: This is the root tag for your configuration file. The ``id`` attribute uniquely identifes your config file during your build. - * The ``assembly`` tag: Identifies what assembl for which you are generating code. + * The ``assembly`` tag: Identifies what assembly for which you are generating code. * The ``namespace`` tag: Identifies the root namespace for code generated from this config file. * The ``depends`` tag: Declares dependencies on other config files. Optional, but can help ensure that all dependencies are correctly loaded. From a5d3085aee987fd0c3589f2841a40df9c41b75e5 Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Mon, 18 Sep 2023 11:23:49 +0200 Subject: [PATCH 42/55] PolySharp manage PrivateAssets="all" --- SharpGen.Generator/SharpGen.Generator.csproj | 2 +- SharpGen.Runtime/SharpGen.Runtime.csproj | 2 +- SharpGen/SharpGen.csproj | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/SharpGen.Generator/SharpGen.Generator.csproj b/SharpGen.Generator/SharpGen.Generator.csproj index 3fb6dbce..c45d9cdc 100644 --- a/SharpGen.Generator/SharpGen.Generator.csproj +++ b/SharpGen.Generator/SharpGen.Generator.csproj @@ -12,7 +12,7 @@ - + diff --git a/SharpGen.Runtime/SharpGen.Runtime.csproj b/SharpGen.Runtime/SharpGen.Runtime.csproj index d1d6c2cc..d09138e6 100644 --- a/SharpGen.Runtime/SharpGen.Runtime.csproj +++ b/SharpGen.Runtime/SharpGen.Runtime.csproj @@ -21,7 +21,7 @@ - + diff --git a/SharpGen/SharpGen.csproj b/SharpGen/SharpGen.csproj index fd610dc4..e23c62f5 100644 --- a/SharpGen/SharpGen.csproj +++ b/SharpGen/SharpGen.csproj @@ -3,7 +3,6 @@ Library netstandard2.0 - true From 9248d45da1b68d47912f411c495d9b667eac9ba8 Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Mon, 18 Sep 2023 12:00:49 +0200 Subject: [PATCH 43/55] Make Internals visible to SharpGen.Runtime.COM --- SharpGen.Runtime/SharpGen.Runtime.csproj | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SharpGen.Runtime/SharpGen.Runtime.csproj b/SharpGen.Runtime/SharpGen.Runtime.csproj index d09138e6..d9cbedaa 100644 --- a/SharpGen.Runtime/SharpGen.Runtime.csproj +++ b/SharpGen.Runtime/SharpGen.Runtime.csproj @@ -40,4 +40,11 @@ + + + <_Parameter1>SharpGen.Runtime.COM, PublicKey=$(SharpGenPublicKey) + + + + From b047722772764f5732a27900a9b0f171b81b0a45 Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Fri, 6 Oct 2023 09:09:35 +0200 Subject: [PATCH 44/55] Improve versioning and build logic + add initial github CI workflow. --- .github/workflows/build.yml | 37 +++++++++++++++++++ Directory.Build.props | 25 ++++--------- NuGet.config | 1 + .../NuGet.config | 9 ----- .../SharpGen.Runtime.COM/NuGet.config | 9 ----- 5 files changed, 45 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/build.yml delete mode 100644 SharpGen.Runtime.COM/SharpGen.Runtime.COM.Trim.Dummy/NuGet.config delete mode 100644 SharpGen.Runtime.COM/SharpGen.Runtime.COM/NuGet.config diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..ed804943 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,37 @@ +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 8 SDK + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '8.0.x' + dotnet-quality: 'preview' + + - 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 709c9368..1b77e717 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,26 +1,14 @@ - 2.0.0 + 2.1.0 + beta + $(VersionPrefix)-$(VersionSuffix) - - - - $(VersionPrefix)-local - - - - - $(ReleaseTag) - - - - - $(VersionPrefix)-ci.$(BuildNumber) - - - + + $(MSBuildThisFileDirectory) + 10 @@ -45,6 +33,7 @@ (c) 2010-2017 Alexandre Mutel, 2017-2023 Jeremy Koritzinsky MIT https://github.com/SharpGenTools/SharpGenTools + $(MSBuildThisFileDirectory)artifacts/ SharpGen;CodeGen;CPlusPlus;PInvoke;Native;COM https://github.com/SharpGenTools/SharpGenTools 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/SharpGen.Runtime.COM/SharpGen.Runtime.COM.Trim.Dummy/NuGet.config b/SharpGen.Runtime.COM/SharpGen.Runtime.COM.Trim.Dummy/NuGet.config deleted file mode 100644 index c010a0f4..00000000 --- a/SharpGen.Runtime.COM/SharpGen.Runtime.COM.Trim.Dummy/NuGet.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/SharpGen.Runtime.COM/SharpGen.Runtime.COM/NuGet.config b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/NuGet.config deleted file mode 100644 index 36c68fbb..00000000 --- a/SharpGen.Runtime.COM/SharpGen.Runtime.COM/NuGet.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file From 7165a8d0c9ec5fbb4aa39881d25ba0d2dd41fcb2 Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Fri, 6 Oct 2023 09:14:24 +0200 Subject: [PATCH 45/55] Add artifacts folder and remove azure-pipelines.yml --- artifacts/README.md | 3 ++ azure-pipelines.yml | 127 -------------------------------------------- 2 files changed, 3 insertions(+), 127 deletions(-) create mode 100644 artifacts/README.md delete mode 100644 azure-pipelines.yml diff --git a/artifacts/README.md b/artifacts/README.md new file mode 100644 index 00000000..d935006f --- /dev/null +++ b/artifacts/README.md @@ -0,0 +1,3 @@ +# Artifacts + +Package artifacts goes here \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index e22f4ce2..00000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,127 +0,0 @@ -trigger: - branches: - include: - - main - tags: - include: - - v*.* -pr: -- main - -variables: - buildNumber: $[counter(variables['build.reason'], 1000)] - -stages: -- stage: Build - jobs: - - job: SharpGenTools_Windows - pool: - vmImage: 'windows-2022' - strategy: - matrix: - Debug: - Config: 'Debug' - Release: - Config: 'Release' - variables: - - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE - value: "true" - - name: DOTNET_ROLL_FORWARD - value: "Major" - - name: ReleaseVersion - value: '' - - name: ContinuousIntegrationBuild - value: true - - group: SharpGenTools - steps: - - pwsh: | - if ($env:SourceBranch -match 'refs/tags/v(?.+)') - { - Write-Host "##vso[task.setvariable variable=ReleaseVersion;]$($Matches.Version)" - } - displayName: 'Get Release Name (if this is a tag-triggered build)' - env: - SourceBranch : '$(Build.SourceBranch)' - - pwsh: | - $env:MSBuildEnableWorkloadResolver=$false - dotnet tool restore - displayName: 'Restore .NET tools' - - pwsh: ./build.ps1 -Configuration $(Config) - displayName: 'Build and test' - env: - ReleaseTag: '$(ReleaseVersion)' - BuildNumber: '$(buildNumber)' - - task: PublishTestResults@2 - displayName: 'Publish unit test results' - inputs: - testResultsFormat: 'VSTest' - testResultsFiles: '*.trx' - buildConfiguration: '$(Config)' - searchFolder: '$(Build.SourcesDirectory)/artifacts/test-results' - - pwsh: dotnet reportgenerator -reports:"artifacts\coverage\*.xml" -targetdir:"artifacts\reports" -reporttypes:Cobertura -title:SharpGen - condition: eq(variables['Config'], 'Debug') - displayName: 'Merge coverage reports' - - task: PublishCodeCoverageResults@1 - condition: eq(variables['Config'], 'Debug') - displayName: 'Publish code coverage results' - inputs: - summaryFileLocation: '$(Build.SourcesDirectory)/artifacts/reports/*.xml' - codeCoverageTool: Cobertura - - task: CopyFiles@2 - condition: eq(variables['Config'], 'Release') - displayName: 'Copy NuGet packages to Artifact staging directory' - inputs: - contents: 'Sharp*/bin/Release/Sharp*@(.nupkg|.snupkg)' - targetFolder: '$(Build.ArtifactStagingDirectory)' - flattenFolders: true - - task: CopyFiles@2 - condition: eq(variables['Config'], 'Release') - displayName: 'Copy SharpGen.Runtime.COM NuGet packages to Artifact staging directory' - inputs: - contents: 'SharpGen.Runtime.COM/SharpGen.Runtime.COM/bin/Release/Sharp*@(.nupkg|.snupkg)' - targetFolder: '$(Build.ArtifactStagingDirectory)' - flattenFolders: true - - task: PublishBuildArtifacts@1 - condition: eq(variables['Config'], 'Release') - displayName: Publish NuGet Packages - inputs: - pathToPublish: '$(Build.ArtifactStagingDirectory)' - artifactName: NuGet Packages -- stage: Deploy - condition: or(eq(variables['Build.SourceBranchName'], 'main'), startsWith(variables['Build.SourceBranch'], 'refs/tags/v')) - dependsOn: Build - jobs: - - job: MyGet_Deploy - condition: not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')) - pool: - vmImage: 'windows-2022' - steps: - - checkout: none - - task: DownloadBuildArtifacts@0 - inputs: - buildType: 'current' - downloadType: 'single' - artifactName: 'NuGet Packages' - downloadPath: '$(Build.ArtifactStagingDirectory)' - - task: NuGetCommand@2 - inputs: - command: 'push' - nugetFeedType: 'external' - publishFeedCredentials: 'MyGet' - - job: NuGet_Deploy - condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') - pool: - vmImage: 'windows-2022' - steps: - - checkout: none - - task: DownloadBuildArtifacts@0 - inputs: - buildType: 'current' - downloadType: 'single' - artifactName: 'NuGet Packages' - downloadPath: '$(Build.ArtifactStagingDirectory)' - - task: NuGetCommand@2 - inputs: - command: 'push' - nugetFeedType: 'external' - publishFeedCredentials: 'NuGet' From 3f0698ba94485238a324e44a53ef2448b83502e2 Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Fri, 6 Oct 2023 09:21:58 +0200 Subject: [PATCH 46/55] [ci skip+ Remove release-drafter.yml --- .github/release-drafter.yml | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .github/release-drafter.yml 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 From 9556bab7c90cd8c4292746a50b28a9f186cb965c Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Fri, 6 Oct 2023 09:27:10 +0200 Subject: [PATCH 47/55] Update README.md --- README.md | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 2cd78032..59420b44 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # SharpGenTools -[![Build Status](https://dev.azure.com/SharpGenTools/SharpGenTools/_apis/build/status/SharpGenTools?branchName=main)](https://dev.azure.com/SharpGenTools/SharpGenTools/_build/latest?definitionId=1&branchName=main) [![MyGet Pre Release](https://img.shields.io/myget/sharpgentools/vpre/SharpGenTools.Sdk.svg)](https://www.myget.org/feed/Packages/sharpgentools) [![NuGet](https://img.shields.io/nuget/v/SharpGenTools.Sdk.svg)](https://www.nuget.org/packages/SharpGenTools.Sdk) [![Docs](https://readthedocs.org/projects/sharpgentools/badge/?version=latest)](https://sharpgentools.readthedocs.io/en/latest/) [![codecov](https://codecov.io/gh/SharpGenTools/SharpGenTools/branch/main/graph/badge.svg)](https://codecov.io/gh/SharpGenTools/SharpGenTools) [![CodeFactor](https://www.codefactor.io/repository/github/sharpgentools/sharpgentools/badge)](https://www.codefactor.io/repository/github/sharpgentools/sharpgentools) +[![Build status](https://github.com/SharpGenTools/SharpGenTools/workflows/Build/badge.svg)](https://github.com/SharpGenTools/SharpGenTools/actions) +[![NuGet](https://img.shields.io/nuget/v/SharpGenTools.Sdk.svg)](https://www.nuget.org/packages/SharpGenTools.Sdk) [![Docs](https://readthedocs.org/projects/sharpgentools/badge/?version=latest)](https://sharpgentools.readthedocs.io/en/latest/) [![codecov](https://codecov.io/gh/SharpGenTools/SharpGenTools/branch/main/graph/badge.svg)](https://codecov.io/gh/SharpGenTools/SharpGenTools) [![CodeFactor](https://www.codefactor.io/repository/github/sharpgentools/sharpgentools/badge)](https://www.codefactor.io/repository/github/sharpgentools/sharpgentools) Accurate and high performance C++ interop code generator for C#. @@ -38,18 +39,6 @@ Accurate and high performance C++ interop code generator for C#. ### To Build -* .NET SDK: 6.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 From f885ccda4de36a1b0614197f58fc26dd6d1c082f Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Fri, 6 Oct 2023 09:50:38 +0200 Subject: [PATCH 48/55] Add .net8 support. --- Directory.Build.props | 103 ++++++++++-------- Directory.Build.targets | 9 +- README.md | 4 +- SharpGen.Platform/SharpGen.Platform.csproj | 2 +- .../SharpGen.Runtime.COM.csproj | 6 +- SharpGen.Runtime/SharpGen.Runtime.csproj | 11 +- SharpGen.UnitTests/SharpGen.UnitTests.csproj | 2 +- SharpGenTools.Sdk/SharpGenTools.Sdk.csproj | 2 +- global.json | 4 - 9 files changed, 75 insertions(+), 68 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 1b77e717..1b85602f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,51 +1,60 @@ - - 2.1.0 - beta - $(VersionPrefix)-$(VersionSuffix) - - - - $(MSBuildThisFileDirectory) - - - - 10 - 00240000048000009400000006020000002400005253413100040000010001003dab93dc845fe6b52b20d86918a54f7300fa6959d56e9743c6f721857346811cd6a82d12132856755ab87e014127322421694fb522ad98fc3c6b65b389ab18ee3bbdec5c2ad5a8bef05599a3615c3e6afdade7eb2cf571b5ede7feb026b099fa94ee73f2f8dadcb6b1be62f7c984226eb0508d5ca6c3e394605c5cb0fa0851a2 - $(MSBuildThisFileDirectory)SharpGenTools.snk - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - - - - false - true - - - - false - false - true - - - - jkoritzinsky - (c) 2010-2017 Alexandre Mutel, 2017-2023 Jeremy Koritzinsky - MIT - https://github.com/SharpGenTools/SharpGenTools - $(MSBuildThisFileDirectory)artifacts/ - SharpGen;CodeGen;CPlusPlus;PInvoke;Native;COM - https://github.com/SharpGenTools/SharpGenTools - - - true - - - true - - - true - snupkg - + + 2.1.1 + 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 + 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/README.md b/README.md index 59420b44..d7d5c364 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,9 @@ 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 (6 or newer) + * .NET SDK (7 or newer) * .NET Core SDK (3.1 or newer) - * Visual Studio 2017.3 with desktop .NET workload, .NET Framework 4.7.2 SDK 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 diff --git a/SharpGen.Platform/SharpGen.Platform.csproj b/SharpGen.Platform/SharpGen.Platform.csproj index 8801376a..90abb4a2 100644 --- a/SharpGen.Platform/SharpGen.Platform.csproj +++ b/SharpGen.Platform/SharpGen.Platform.csproj @@ -2,7 +2,7 @@ Library - net472;net6.0 + net472;net7.0 true true diff --git a/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj index a42fb2a1..47b28ba1 100644 --- a/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj +++ b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj @@ -3,15 +3,15 @@ - netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0;net7.0;net461;net471 + netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0;net7.0;net8.0;net461;net471 SharpGen.Runtime C# COM Interop classes for use with SharpGenTools generated libraries true diff --git a/SharpGen.Runtime/SharpGen.Runtime.csproj b/SharpGen.Runtime/SharpGen.Runtime.csproj index d9cbedaa..34c8acb7 100644 --- a/SharpGen.Runtime/SharpGen.Runtime.csproj +++ b/SharpGen.Runtime/SharpGen.Runtime.csproj @@ -2,18 +2,19 @@ - netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0;net7.0;net461;net471 + netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0;net7.0;net8.0;net461;net471 Support classes for code generated by SharpGen. true false + true true true preview @@ -22,8 +23,8 @@ - - + + diff --git a/SharpGen.UnitTests/SharpGen.UnitTests.csproj b/SharpGen.UnitTests/SharpGen.UnitTests.csproj index dfbd4ee3..98c14ba5 100644 --- a/SharpGen.UnitTests/SharpGen.UnitTests.csproj +++ b/SharpGen.UnitTests/SharpGen.UnitTests.csproj @@ -1,6 +1,6 @@  - net6.0 + net7.0 false true diff --git a/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj b/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj index a56f55a0..ff8db181 100644 --- a/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj +++ b/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj @@ -3,7 +3,7 @@ - net472;net6.0 + net472;net7.0 SharpGenTools.Sdk true false diff --git a/global.json b/global.json index b6038be0..f3cb0dc9 100644 --- a/global.json +++ b/global.json @@ -1,8 +1,4 @@ { - "sdk": { - "version": "7.0.101", - "rollForward": "latestFeature" - }, "msbuild-sdks": { "MSBuild.Sdk.Extras": "3.0.44" } From c54a6d56978c2dc6206942ac32e23cda159d8346 Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Fri, 6 Oct 2023 10:19:24 +0200 Subject: [PATCH 49/55] Drop net6.0 and keep net7.0 and net8.0, improve build logic. --- SharpGen.Platform/SharpGen.Platform.csproj | 2 +- .../SharpGen.Runtime.COM.Trim.Dummy.csproj | 2 +- .../SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj | 6 +++--- .../SharpGen.Runtime.Trim.Dummy.CallbackTest.csproj | 2 +- .../SharpGen.Runtime.Trim.Dummy.csproj | 2 +- SharpGen.Runtime/SharpGen.Runtime.csproj | 12 ++++++------ SharpGenTools.Sdk/Sdk.targets | 2 +- SharpGenTools.Sdk/SharpGenTools.Sdk.csproj | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/SharpGen.Platform/SharpGen.Platform.csproj b/SharpGen.Platform/SharpGen.Platform.csproj index 90abb4a2..c010db33 100644 --- a/SharpGen.Platform/SharpGen.Platform.csproj +++ b/SharpGen.Platform/SharpGen.Platform.csproj @@ -10,7 +10,7 @@ - + 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 index c764e6b8..98282471 100644 --- 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 @@ -2,7 +2,7 @@ Exe - net6.0 + net7.0 enable enable true diff --git a/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj index 47b28ba1..aca7c55b 100644 --- a/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj +++ b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj @@ -3,15 +3,15 @@ - netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0;net7.0;net8.0;net461;net471 + netstandard2.0;netstandard2.1;netcoreapp3.1;net7.0;net8.0;net461;net471 SharpGen.Runtime C# COM Interop classes for use with SharpGenTools generated libraries true 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 index 77fe1d2f..80ca2b72 100644 --- a/SharpGen.Runtime.Trim.Dummy.CallbackTest/SharpGen.Runtime.Trim.Dummy.CallbackTest.csproj +++ b/SharpGen.Runtime.Trim.Dummy.CallbackTest/SharpGen.Runtime.Trim.Dummy.CallbackTest.csproj @@ -1,7 +1,7 @@ - net6.0 + net7.0 enable enable true diff --git a/SharpGen.Runtime.Trim.Dummy/SharpGen.Runtime.Trim.Dummy.csproj b/SharpGen.Runtime.Trim.Dummy/SharpGen.Runtime.Trim.Dummy.csproj index 66e95fe4..0bde68b5 100644 --- a/SharpGen.Runtime.Trim.Dummy/SharpGen.Runtime.Trim.Dummy.csproj +++ b/SharpGen.Runtime.Trim.Dummy/SharpGen.Runtime.Trim.Dummy.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net7.0 enable enable true diff --git a/SharpGen.Runtime/SharpGen.Runtime.csproj b/SharpGen.Runtime/SharpGen.Runtime.csproj index 34c8acb7..5932f6ca 100644 --- a/SharpGen.Runtime/SharpGen.Runtime.csproj +++ b/SharpGen.Runtime/SharpGen.Runtime.csproj @@ -2,15 +2,15 @@ - netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0;net7.0;net8.0;net461;net471 + netstandard2.0;netstandard2.1;netcoreapp3.1;net7.0;net8.0;net461;net471 Support classes for code generated by SharpGen. true false @@ -23,8 +23,8 @@ - - + + @@ -37,7 +37,7 @@ - + diff --git a/SharpGenTools.Sdk/Sdk.targets b/SharpGenTools.Sdk/Sdk.targets index 79291c4f..183c183f 100644 --- a/SharpGenTools.Sdk/Sdk.targets +++ b/SharpGenTools.Sdk/Sdk.targets @@ -67,7 +67,7 @@ - net6.0 + net7.0 net472 diff --git a/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj b/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj index ff8db181..e84ab313 100644 --- a/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj +++ b/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj @@ -31,7 +31,7 @@ - + Code From 33376aa5a460838c8f642d086cb04bf7226cca71 Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Mon, 9 Oct 2023 09:22:45 +0200 Subject: [PATCH 50/55] Improve MemoryHelpers, add lot of new unsafe helpers + use NativeMemory on NET6 or greather instead of Marshal. --- Directory.Build.props | 2 +- .../CallbackBase.ReflectionImpl.cs | 2 +- SharpGen.Runtime/MarshallingHelpers.cs | 19 +- SharpGen.Runtime/MemoryHelpers.Alloc.cs | 105 +++++- SharpGen.Runtime/MemoryHelpers.cs | 302 ++++++++++++++++-- SharpGen.Runtime/TypeDataStorage.cs | 6 +- 6 files changed, 401 insertions(+), 35 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 1b85602f..2ecdeb0e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 2.1.1 + 2.1.2 beta $(VersionPrefix)-$(VersionSuffix) diff --git a/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs b/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs index 61076089..139c39e5 100644 --- a/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs +++ b/SharpGen.Runtime/CallbackBase.ReflectionImpl.cs @@ -32,7 +32,7 @@ private GCHandle CreateShadow( [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) + protected virtual void InitializeCallableWrappers(IDictionary ccw) { // Associate all shadows with their interfaces. var typeInfo = GetTypeInfo(); diff --git a/SharpGen.Runtime/MarshallingHelpers.cs b/SharpGen.Runtime/MarshallingHelpers.cs index 89941624..708401d9 100644 --- a/SharpGen.Runtime/MarshallingHelpers.cs +++ b/SharpGen.Runtime/MarshallingHelpers.cs @@ -8,6 +8,21 @@ namespace SharpGen.Runtime; public static partial class MarshallingHelpers { + /// + /// 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(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. /// @@ -76,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 { CppObject cpp => cpp.NativePointer, CallbackBase managed => managed.Find(), - _ => IntPtr.Zero, + _ => 0, }; /// 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..9d8bf490 100644 --- a/SharpGen.Runtime/MemoryHelpers.cs +++ b/SharpGen.Runtime/MemoryHelpers.cs @@ -19,15 +19,254 @@ // THE SOFTWARE. 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 +274,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 +284,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 +293,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 +312,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 +322,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 +331,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 +341,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 +351,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 +372,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 +401,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 +428,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 +442,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 +455,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 +467,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 +481,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 +494,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 +504,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 +514,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 +524,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/TypeDataStorage.cs b/SharpGen.Runtime/TypeDataStorage.cs index ea85e5a5..19d5996c 100644 --- a/SharpGen.Runtime/TypeDataStorage.cs +++ b/SharpGen.Runtime/TypeDataStorage.cs @@ -78,7 +78,11 @@ internal static void Register(void* vtbl) where T : ICallbackable return GetSourceVtblFromReflection(typeof(T)); } - private static IntPtr[]? GetSourceVtblFromReflection(Type type) + private static IntPtr[]? GetSourceVtblFromReflection( +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] +#endif + Type type) { const string vtbl = "Vtbl"; From a469902041a780378fce55e5694a3e63d98bfb9c Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Mon, 9 Oct 2023 10:21:52 +0200 Subject: [PATCH 51/55] Make ObjectTracker.FindActiveObjects public. --- SharpGen.Runtime/Diagnostics/ObjectTracker.cs | 2 +- SharpGen.Runtime/MemoryHelpers.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) 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/MemoryHelpers.cs b/SharpGen.Runtime/MemoryHelpers.cs index 9d8bf490..57544eac 100644 --- a/SharpGen.Runtime/MemoryHelpers.cs +++ b/SharpGen.Runtime/MemoryHelpers.cs @@ -18,6 +18,8 @@ // 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; From a22348d2e1ff76dfbdc51d68800ed31e991d8b32 Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Mon, 8 Jul 2024 11:43:41 +0200 Subject: [PATCH 52/55] Update packages, add PointerUSize to map size_t and SIZE_T, ssize_t and SSIZE_T maps to PointerSize, bump version to 2.2.0-beta --- .github/workflows/build.yml | 2 +- Directory.Build.props | 4 +- Directory.Packages.props | 4 +- .../SharpGen.Runtime.COM/Mapping.xml | 2 +- SharpGen.Runtime/Mapping.xml | 84 ++++++++-------- SharpGen.Runtime/PointerSize.cs | 59 +++++++----- SharpGen.Runtime/PointerUSize.cs | 95 +++++++++++++++++++ SharpGen.UnitTests/Mapping/Interface.cs | 4 +- global.json | 5 + 9 files changed, 188 insertions(+), 71 deletions(-) create mode 100644 SharpGen.Runtime/PointerUSize.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ed804943..30e8a23f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v4 - name: Setup .NET 8 SDK - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '8.0.x' dotnet-quality: 'preview' diff --git a/Directory.Build.props b/Directory.Build.props index 2ecdeb0e..f81a8e5c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 2.1.2 + 2.2.0 beta $(VersionPrefix)-$(VersionSuffix) @@ -35,7 +35,7 @@ jkoritzinsky - (c) 2010-2017 Alexandre Mutel, 2017-2023 Jeremy Koritzinsky + (c) 2010-2017 Alexandre Mutel, 2017-2023 Jeremy Koritzinsky, 2023-2024 Amer Koleci MIT https://github.com/SharpGenTools/SharpGenTools $(MSBuildThisFileDirectory)artifacts/ diff --git a/Directory.Packages.props b/Directory.Packages.props index d4bdf747..c8a008f1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -10,12 +10,12 @@ - + - + diff --git a/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Mapping.xml b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Mapping.xml index ed0f1206..14329eee 100644 --- a/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Mapping.xml +++ b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/Mapping.xml @@ -126,7 +126,7 @@ - + diff --git a/SharpGen.Runtime/Mapping.xml b/SharpGen.Runtime/Mapping.xml index 9c2933dd..8e6c585e 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/PointerSize.cs b/SharpGen.Runtime/PointerSize.cs index 6f341390..a8dfd13d 100644 --- a/SharpGen.Runtime/PointerSize.cs +++ b/SharpGen.Runtime/PointerSize.cs @@ -19,6 +19,7 @@ // THE SOFTWARE. using System; +using System.Diagnostics.CodeAnalysis; using System.Globalization; namespace SharpGen.Runtime; @@ -27,55 +28,69 @@ 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 NET7_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( formatProvider ?? CultureInfo.CurrentCulture, string.IsNullOrEmpty(format) ? "{0}" : "{0:" + format + "}", - _size + Value ); public string ToString(string format) => ToString(format, null); - public override int GetHashCode() => _size.GetHashCode(); + public override int GetHashCode() => Value.GetHashCode(); - public bool Equals(PointerSize other) => _size.Equals(other._size); + public bool Equals(PointerSize other) => Value.Equals(other.Value); - public override bool Equals(object value) + public override bool Equals([NotNullWhen(true)] object? obj) => obj is PointerSize value && Equals(value); + +#if NET7_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..1fa5e5dd --- /dev/null +++ b/SharpGen.Runtime/PointerUSize.cs @@ -0,0 +1,95 @@ +// 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; + +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 NET7_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 NET7_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.UnitTests/Mapping/Interface.cs b/SharpGen.UnitTests/Mapping/Interface.cs index 2df09b72..4d5dbb35 100644 --- a/SharpGen.UnitTests/Mapping/Interface.cs +++ b/SharpGen.UnitTests/Mapping/Interface.cs @@ -323,7 +323,7 @@ public void VoidInBufferParameter() }, new DefineExtensionRule { - Struct = "SharpGen.Runtime.PointerSize", + Struct = "SharpGen.Runtime.PointerUSize", SizeOf = 8, IsNativePrimitive = true } @@ -331,7 +331,7 @@ public void VoidInBufferParameter() Bindings = { new BindRule("HRESULT", "SharpGen.Runtime.Result"), - new BindRule("SIZE_T", "SharpGen.Runtime.PointerSize"), + new BindRule("SIZE_T", "SharpGen.Runtime.PointerUSize"), new BindRule("UINT32", "System.UInt32"), new BindRule("void", "System.Void") } diff --git a/global.json b/global.json index f3cb0dc9..989b9161 100644 --- a/global.json +++ b/global.json @@ -1,4 +1,9 @@ { + "sdk": { + "version": "8.0.302", + "rollForward": "latestFeature", + "allowPrerelease": false + }, "msbuild-sdks": { "MSBuild.Sdk.Extras": "3.0.44" } From d60ea8183d64649199a4e870162736a42173e574 Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Tue, 3 Dec 2024 17:17:15 +0100 Subject: [PATCH 53/55] Restart improvements, add .net9 support. --- .editorconfig | 12 ++ .github/workflows/build.yml | 5 +- Directory.Build.props | 2 +- Directory.Packages.props | 30 ++--- SharpGen.Platform/SharpGen.Platform.csproj | 6 +- .../SharpGen.Runtime.COM.Trim.Dummy.csproj | 2 +- .../SharpGen.Runtime.COM.csproj | 7 +- ...Gen.Runtime.Trim.Dummy.CallbackTest.csproj | 2 +- .../SharpGen.Runtime.Trim.Dummy.csproj | 2 +- SharpGen.Runtime/SharpGen.Runtime.csproj | 21 ++-- SharpGen.Runtime/Shim/FormattableString.cs | 104 ------------------ .../Shim/FormattableStringFactory.cs | 58 ---------- SharpGen.UnitTests/SharpGen.UnitTests.csproj | 2 +- SharpGenTools.Sdk/Sdk.targets | 2 +- SharpGenTools.Sdk/SharpGenTools.Sdk.csproj | 4 +- SharpGenTools.sln | 1 + global.json | 2 +- 17 files changed, 53 insertions(+), 209 deletions(-) delete mode 100644 SharpGen.Runtime/Shim/FormattableString.cs delete mode 100644 SharpGen.Runtime/Shim/FormattableStringFactory.cs 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/workflows/build.yml b/.github/workflows/build.yml index 30e8a23f..c15b9fe7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,11 +20,10 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Setup .NET 8 SDK + - name: Setup .NET SDK uses: actions/setup-dotnet@v4 with: - dotnet-version: '8.0.x' - dotnet-quality: 'preview' + global-json-file: ./global.json - name: Pack run: dotnet pack SharpGenTools.sln --configuration Release -p:Packing=true diff --git a/Directory.Build.props b/Directory.Build.props index f81a8e5c..6c4e6855 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 2.2.0 + 2.4.0 beta $(VersionPrefix)-$(VersionSuffix) diff --git a/Directory.Packages.props b/Directory.Packages.props index c8a008f1..1396ad62 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -10,29 +10,29 @@ - + - + - - + + - - - + + + - - - - - + + + + + - - - + + + \ No newline at end of file diff --git a/SharpGen.Platform/SharpGen.Platform.csproj b/SharpGen.Platform/SharpGen.Platform.csproj index c010db33..490fca3a 100644 --- a/SharpGen.Platform/SharpGen.Platform.csproj +++ b/SharpGen.Platform/SharpGen.Platform.csproj @@ -2,15 +2,15 @@ Library - net472;net7.0 + net472;net8.0 true true - + - + 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 index 98282471..4b012e62 100644 --- 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 @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable true diff --git a/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj index aca7c55b..9a201955 100644 --- a/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj +++ b/SharpGen.Runtime.COM/SharpGen.Runtime.COM/SharpGen.Runtime.COM.csproj @@ -3,15 +3,14 @@ - netstandard2.0;netstandard2.1;netcoreapp3.1;net7.0;net8.0;net461;net471 + netstandard2.0;netstandard2.1;net8.0;net9.0;net462;net471 SharpGen.Runtime C# COM Interop classes for use with SharpGenTools generated libraries true 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 index 80ca2b72..bc27816a 100644 --- a/SharpGen.Runtime.Trim.Dummy.CallbackTest/SharpGen.Runtime.Trim.Dummy.CallbackTest.csproj +++ b/SharpGen.Runtime.Trim.Dummy.CallbackTest/SharpGen.Runtime.Trim.Dummy.CallbackTest.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable true diff --git a/SharpGen.Runtime.Trim.Dummy/SharpGen.Runtime.Trim.Dummy.csproj b/SharpGen.Runtime.Trim.Dummy/SharpGen.Runtime.Trim.Dummy.csproj index 0bde68b5..12049858 100644 --- a/SharpGen.Runtime.Trim.Dummy/SharpGen.Runtime.Trim.Dummy.csproj +++ b/SharpGen.Runtime.Trim.Dummy/SharpGen.Runtime.Trim.Dummy.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable true diff --git a/SharpGen.Runtime/SharpGen.Runtime.csproj b/SharpGen.Runtime/SharpGen.Runtime.csproj index 5932f6ca..f1b801bb 100644 --- a/SharpGen.Runtime/SharpGen.Runtime.csproj +++ b/SharpGen.Runtime/SharpGen.Runtime.csproj @@ -2,15 +2,14 @@ - netstandard2.0;netstandard2.1;netcoreapp3.1;net7.0;net8.0;net461;net471 + netstandard2.0;netstandard2.1;net8.0;net9.0;net462;net471 Support classes for code generated by SharpGen. true false @@ -23,9 +22,9 @@ - - - + + + @@ -33,11 +32,7 @@ - - - - - + 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.UnitTests/SharpGen.UnitTests.csproj b/SharpGen.UnitTests/SharpGen.UnitTests.csproj index 98c14ba5..4a5e370d 100644 --- a/SharpGen.UnitTests/SharpGen.UnitTests.csproj +++ b/SharpGen.UnitTests/SharpGen.UnitTests.csproj @@ -1,6 +1,6 @@  - net7.0 + net8.0;net9.0 false true diff --git a/SharpGenTools.Sdk/Sdk.targets b/SharpGenTools.Sdk/Sdk.targets index 183c183f..e3f98b3a 100644 --- a/SharpGenTools.Sdk/Sdk.targets +++ b/SharpGenTools.Sdk/Sdk.targets @@ -67,7 +67,7 @@ - net7.0 + net8.0 net472 diff --git a/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj b/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj index e84ab313..0cbc110d 100644 --- a/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj +++ b/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj @@ -3,7 +3,7 @@ - net472;net7.0 + net472;net8.0 SharpGenTools.Sdk true false @@ -31,7 +31,7 @@ - + Code diff --git a/SharpGenTools.sln b/SharpGenTools.sln index 6d482c46..55d0e1b7 100644 --- a/SharpGenTools.sln +++ b/SharpGenTools.sln @@ -25,6 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".root", ".root", "{A3BC86F0 Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets Directory.Packages.props = Directory.Packages.props + global.json = global.json EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F671E5B9-5D3D-44EF-8B6F-F3702FB70DF1}" diff --git a/global.json b/global.json index 989b9161..4c63b68d 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.302", + "version": "9.0.100", "rollForward": "latestFeature", "allowPrerelease": false }, From f78c5a9bdb1c40830cd02b28dd3c5e9188bf71d8 Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Tue, 3 Dec 2024 17:33:45 +0100 Subject: [PATCH 54/55] Reworked generation for 'size_t' type and .net framework support (netstandard2.0 is fine). --- Directory.Build.props | 2 +- SharpGen.Platform/SharpGen.Platform.csproj | 2 +- SharpGen.Runtime/Mapping.xml | 6 +++--- SharpGen.Runtime/PointerSize.cs | 10 ++++++---- SharpGen.Runtime/PointerUSize.cs | 10 ++++++---- SharpGen.Runtime/SharpGen.Runtime.csproj | 5 +---- SharpGen.UnitTests/SharpGen.UnitTests.csproj | 2 +- SharpGen/Transform/PrimitiveTypeCode.cs | 4 +++- SharpGen/Transform/TypeRegistry.Primitives.cs | 15 +++++++++++++-- SharpGenTools.Sdk/Sdk.targets | 2 +- SharpGenTools.Sdk/SharpGenTools.Sdk.csproj | 4 ++-- 11 files changed, 38 insertions(+), 24 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 6c4e6855..f40cb4b8 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 2.4.0 + 2.4.1 beta $(VersionPrefix)-$(VersionSuffix) diff --git a/SharpGen.Platform/SharpGen.Platform.csproj b/SharpGen.Platform/SharpGen.Platform.csproj index 490fca3a..f1cfe124 100644 --- a/SharpGen.Platform/SharpGen.Platform.csproj +++ b/SharpGen.Platform/SharpGen.Platform.csproj @@ -2,7 +2,7 @@ Library - net472;net8.0 + netstandard2.0;net8.0 true true diff --git a/SharpGen.Runtime/Mapping.xml b/SharpGen.Runtime/Mapping.xml index 8e6c585e..754b58fc 100644 --- a/SharpGen.Runtime/Mapping.xml +++ b/SharpGen.Runtime/Mapping.xml @@ -30,9 +30,9 @@ - - - + + + diff --git a/SharpGen.Runtime/PointerSize.cs b/SharpGen.Runtime/PointerSize.cs index a8dfd13d..5fd0b9ae 100644 --- a/SharpGen.Runtime/PointerSize.cs +++ b/SharpGen.Runtime/PointerSize.cs @@ -22,6 +22,8 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; +#nullable enable + namespace SharpGen.Runtime; /// @@ -29,7 +31,7 @@ namespace SharpGen.Runtime; /// Equivalent to the native type size_t. /// public readonly struct PointerSize : IEquatable, -#if NET7_0_OR_GREATER +#if NET8_0_OR_GREATER IComparable, #endif IFormattable @@ -48,13 +50,13 @@ namespace SharpGen.Runtime; 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 + "}", Value ); - public string ToString(string format) => ToString(format, null); + public string ToString(string? format) => ToString(format, null); public override int GetHashCode() => Value.GetHashCode(); @@ -62,7 +64,7 @@ public string ToString(string format, IFormatProvider formatProvider) => string. public override bool Equals([NotNullWhen(true)] object? obj) => obj is PointerSize value && Equals(value); -#if NET7_0_OR_GREATER +#if NET8_0_OR_GREATER public int CompareTo(object? obj) { if (obj is PointerSize other) diff --git a/SharpGen.Runtime/PointerUSize.cs b/SharpGen.Runtime/PointerUSize.cs index 1fa5e5dd..12036e88 100644 --- a/SharpGen.Runtime/PointerUSize.cs +++ b/SharpGen.Runtime/PointerUSize.cs @@ -22,6 +22,8 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; +#nullable enable + namespace SharpGen.Runtime; /// @@ -29,7 +31,7 @@ namespace SharpGen.Runtime; /// Equivalent to the native type size_t. /// public readonly struct PointerUSize : IEquatable, -#if NET7_0_OR_GREATER +#if NET8_0_OR_GREATER IComparable, #endif IFormattable @@ -48,13 +50,13 @@ namespace SharpGen.Runtime; 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 + "}", Value ); - public string ToString(string format) => ToString(format, null); + public string ToString(string? format) => ToString(format, null); public override int GetHashCode() => Value.GetHashCode(); @@ -63,7 +65,7 @@ public string ToString(string format, IFormatProvider formatProvider) => string. public override bool Equals([NotNullWhen(true)] object? obj) => obj is PointerUSize value && Equals(value); -#if NET7_0_OR_GREATER +#if NET8_0_OR_GREATER public int CompareTo(object? obj) { if (obj is PointerUSize other) diff --git a/SharpGen.Runtime/SharpGen.Runtime.csproj b/SharpGen.Runtime/SharpGen.Runtime.csproj index f1b801bb..cc5d63fa 100644 --- a/SharpGen.Runtime/SharpGen.Runtime.csproj +++ b/SharpGen.Runtime/SharpGen.Runtime.csproj @@ -6,10 +6,8 @@ net8.0/net9.0 for function pointers codegen and Native(U)Long API surface and partial assembly trimming netstandard2.1 is .NET Core 3.0, but System.Memory is already inbox since 2.1 netstandard2.0 for smaller dependency tree than netstandard1.3 - net471 for even smaller dependency tree on latest .NET Framework versions (w/o System.Runtime.InteropServices.RuntimeInformation) - net462 for using proper FormattableString instead of Shim/* version --> - netstandard2.0;netstandard2.1;net8.0;net9.0;net462;net471 + netstandard2.0;netstandard2.1;net8.0;net9.0 Support classes for code generated by SharpGen. true false @@ -24,7 +22,6 @@ - diff --git a/SharpGen.UnitTests/SharpGen.UnitTests.csproj b/SharpGen.UnitTests/SharpGen.UnitTests.csproj index 4a5e370d..339c6f12 100644 --- a/SharpGen.UnitTests/SharpGen.UnitTests.csproj +++ b/SharpGen.UnitTests/SharpGen.UnitTests.csproj @@ -1,6 +1,6 @@  - net8.0;net9.0 + net9.0 false true diff --git a/SharpGen/Transform/PrimitiveTypeCode.cs b/SharpGen/Transform/PrimitiveTypeCode.cs index 15a7eb06..b7209dbc 100644 --- a/SharpGen/Transform/PrimitiveTypeCode.cs +++ b/SharpGen/Transform/PrimitiveTypeCode.cs @@ -18,5 +18,7 @@ public enum PrimitiveTypeCode : byte Decimal, String, IntPtr, - UIntPtr + UIntPtr, + NInt, + NUint, } \ No newline at end of file diff --git a/SharpGen/Transform/TypeRegistry.Primitives.cs b/SharpGen/Transform/TypeRegistry.Primitives.cs index 9ecd2bbe..d406dc08 100644 --- a/SharpGen/Transform/TypeRegistry.Primitives.cs +++ b/SharpGen/Transform/TypeRegistry.Primitives.cs @@ -78,6 +78,13 @@ public partial class TypeRegistry typeof(UIntPtr), new PrimitiveTypeIdentity(PrimitiveTypeCode.UIntPtr), "System.UIntPtr" ); + public static readonly CsFundamentalType NInt = new( + typeof(nint), new PrimitiveTypeIdentity(PrimitiveTypeCode.NInt), "nint" + ); + public static readonly CsFundamentalType NUInt = new( + typeof(nuint), new PrimitiveTypeIdentity(PrimitiveTypeCode.NUint), "nuint" + ); + private static readonly Dictionary PrimitiveTypeEntriesByIdentity = new() { @@ -100,6 +107,8 @@ public partial class TypeRegistry [String.PrimitiveTypeIdentity.Value] = String, [IntPtr.PrimitiveTypeIdentity.Value] = IntPtr, [UIntPtr.PrimitiveTypeIdentity.Value] = UIntPtr, + [NInt.PrimitiveTypeIdentity.Value] = NInt, + [NUInt.PrimitiveTypeIdentity.Value] = NUInt, // ReSharper restore PossibleInvalidOperationException }; @@ -125,8 +134,8 @@ public partial class TypeRegistry ["UIntPtr"] = UIntPtr, ["System.IntPtr"] = IntPtr, ["System.UIntPtr"] = UIntPtr, - ["nint"] = IntPtr, - ["nuint"] = UIntPtr, + ["nint"] = NInt, + ["nuint"] = NUInt, }; private static readonly Dictionary PrimitiveRuntimeTypesByCode = new() @@ -148,5 +157,7 @@ public partial class TypeRegistry [PrimitiveTypeCode.String] = typeof(string), [PrimitiveTypeCode.IntPtr] = typeof(IntPtr), [PrimitiveTypeCode.UIntPtr] = typeof(UIntPtr), + [PrimitiveTypeCode.NInt] = typeof(nint), + [PrimitiveTypeCode.NUint] = typeof(nuint), }; } \ No newline at end of file diff --git a/SharpGenTools.Sdk/Sdk.targets b/SharpGenTools.Sdk/Sdk.targets index e3f98b3a..6ea4f7fc 100644 --- a/SharpGenTools.Sdk/Sdk.targets +++ b/SharpGenTools.Sdk/Sdk.targets @@ -68,7 +68,7 @@ net8.0 - net472 + netstandard2.0 diff --git a/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj b/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj index 0cbc110d..aa69d46d 100644 --- a/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj +++ b/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj @@ -3,7 +3,7 @@ - net472;net8.0 + netstandard2.0;net8.0 SharpGenTools.Sdk true false @@ -48,7 +48,7 @@ - + From 6990bcafe124a4c22515ad19cee5a081da8db67b Mon Sep 17 00:00:00 2001 From: Amer Koleci Date: Tue, 4 Feb 2025 15:50:33 +0100 Subject: [PATCH 55/55] Improve dependencies links, explicitly use System.Text.Json, bump to 2.4.2 beta --- Directory.Build.props | 2 +- Directory.Packages.props | 2 +- SharpGen.Platform/SharpGen.Platform.csproj | 8 ++++---- SharpGen.Runtime/SharpGen.Runtime.csproj | 1 + SharpGenTools.Sdk/SharpGenTools.Sdk.csproj | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index f40cb4b8..0e1462fe 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 2.4.1 + 2.4.2 beta $(VersionPrefix)-$(VersionSuffix) diff --git a/Directory.Packages.props b/Directory.Packages.props index 1396ad62..24bfa93a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,7 +15,7 @@ - + diff --git a/SharpGen.Platform/SharpGen.Platform.csproj b/SharpGen.Platform/SharpGen.Platform.csproj index f1cfe124..db9a2364 100644 --- a/SharpGen.Platform/SharpGen.Platform.csproj +++ b/SharpGen.Platform/SharpGen.Platform.csproj @@ -1,4 +1,4 @@ - + Library @@ -9,8 +9,8 @@ - - + + @@ -50,4 +50,4 @@ - + \ No newline at end of file diff --git a/SharpGen.Runtime/SharpGen.Runtime.csproj b/SharpGen.Runtime/SharpGen.Runtime.csproj index cc5d63fa..8c174e33 100644 --- a/SharpGen.Runtime/SharpGen.Runtime.csproj +++ b/SharpGen.Runtime/SharpGen.Runtime.csproj @@ -22,6 +22,7 @@ + diff --git a/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj b/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj index aa69d46d..244f6721 100644 --- a/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj +++ b/SharpGenTools.Sdk/SharpGenTools.Sdk.csproj @@ -45,7 +45,7 @@ - +