diff --git a/sources/ClangSharp.PInvokeGenerator/Abstractions/TransparentStructDesc.cs b/sources/ClangSharp.PInvokeGenerator/Abstractions/TransparentStructDesc.cs new file mode 100644 index 00000000..51e85528 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/Abstractions/TransparentStructDesc.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +namespace ClangSharp.Abstractions; + +internal struct TransparentStructDesc +{ + public string ParentName { get; set; } + public string Name { get; set; } + public string? NativeName { get; set; } + public string Type { get; set; } + public string? NativeType { get; set; } + public PInvokeGeneratorTransparentStructKind Kind { get; set; } +} diff --git a/sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.cs b/sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.cs index 30b8ccc1..7f78ea8a 100644 --- a/sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.cs +++ b/sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.cs @@ -299,7 +299,7 @@ private void AddVtblIndexAttribute(long vtblIndex, string? prefix = null, string } } - private void AddNativeTypeNameAttribute(string nativeTypeName, string? prefix = null, string? postfix = null, string? attributePrefix = null) + public void AddNativeTypeNameAttribute(string nativeTypeName, string? prefix = null, string? postfix = null, string? attributePrefix = null) { foreach (var entry in _generator.Config.NativeTypeNamesToStrip) { diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs index c0e54ee8..0b78a631 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs @@ -717,7 +717,7 @@ private void VisitFunctionDecl(FunctionDecl functionDecl) { outputBuilder.Write("Base"); } - + outputBuilder.Write('.'); outputBuilder.Write(name); outputBuilder.Write('('); @@ -3194,7 +3194,8 @@ private void VisitTypedefDecl(TypedefDecl typedefDecl, bool onlyHandleRemappings void ForFunctionProtoType(TypedefDecl typedefDecl, FunctionProtoType functionProtoType, Type? parentType, bool onlyHandleRemappings) { - if (!_config.ExcludeFnptrCodegen || onlyHandleRemappings) + var hasOutput = _config.GenerateFnPtrWrapper || _config.ExcludeFnptrCodegen; + if (!hasOutput || onlyHandleRemappings) { return; } @@ -3202,14 +3203,35 @@ void ForFunctionProtoType(TypedefDecl typedefDecl, FunctionProtoType functionPro var name = GetRemappedCursorName(typedefDecl); var escapedName = EscapeName(name); - var callingConventionName = GetCallingConvention(typedefDecl, context: null, typedefDecl.TypeForDecl); + StartUsingOutputBuilder(name); + Debug.Assert(_outputBuilder is not null); - var returnType = functionProtoType.ReturnType; - var returnTypeName = GetRemappedTypeName(typedefDecl, context: null, returnType, out var nativeTypeName); + if (_config.GenerateFnPtrWrapper) + { + var type = GetTypeName(typedefDecl, null, typedefDecl.TypeForDecl, true, false, out var nativeName); + _ = GetTypeName(typedefDecl, null, typedefDecl.UnderlyingType, true, false, out var nativeType); - StartUsingOutputBuilder(name); + if (IsNativeTypeNameEquivalent(nativeName, name)) + { + nativeName = null; + } + + var desc = new TransparentStructDesc() { + ParentName = name, + Name = escapedName, + NativeName = nativeName, + Type = type, + NativeType = nativeType, + Kind = PInvokeGeneratorTransparentStructKind.FnPtr + }; + GenerateTransparentStruct(desc); + } + else { - Debug.Assert(_outputBuilder is not null); + var callingConventionName = GetCallingConvention(typedefDecl, context: null, typedefDecl.TypeForDecl); + + var returnType = functionProtoType.ReturnType; + var returnTypeName = GetRemappedTypeName(typedefDecl, context: null, returnType, out var nativeTypeName); var desc = new FunctionOrDelegateDesc { AccessSpecifier = GetAccessSpecifier(typedefDecl, matchStar: true), diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs index 08320b66..c1af5ff6 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs @@ -513,7 +513,7 @@ static void GenerateDisableRuntimeMarshallingAttribute(PInvokeGenerator generato sw.WriteLine("using System.Runtime.CompilerServices;"); sw.WriteLine(); sw.WriteLine("[assembly: DisableRuntimeMarshalling]"); - + if (!leaveStreamOpen) { stream = null; @@ -972,408 +972,404 @@ static void GenerateTransparentStructs(PInvokeGenerator generator, Stream? strea var type = transparentStruct.Value.Name; var kind = transparentStruct.Value.Kind; - var isTypePointer = type.Contains('*', StringComparison.Ordinal); + generator.StartUsingOutputBuilder(name); - if (stream is null) - { - var outputPath = Path.Combine(config.OutputLocation, $"{name}.cs"); - stream = generator._outputStreamFactory(outputPath); - } - - using var sw = new StreamWriter(stream, s_defaultStreamWriterEncoding, DefaultStreamWriterBufferSize, leaveStreamOpen); - sw.NewLine = "\n"; + var desc = new TransparentStructDesc() { + ParentName = name, + Name = name, + Type = type, + Kind = kind + }; + generator.GenerateTransparentStruct(desc); - if (!string.IsNullOrEmpty(config.HeaderText)) - { - sw.WriteLine(config.HeaderText); - } + generator.StopUsingOutputBuilder(); + } + } + } - var indentString = " "; - var targetNamespace = generator.GetNamespace(name); + private void GenerateTransparentStruct(in TransparentStructDesc desc) + { + var name = desc.Name; + var type = desc.Type; + var kind = desc.Kind; - sw.WriteLine("using System;"); + var isTypePointer = type.Contains('*', StringComparison.Ordinal); - if (kind == PInvokeGeneratorTransparentStructKind.HandleWin32) - { - var handleNamespace = generator.GetNamespace("HANDLE"); + var sw = StartCSharpCode(); - if (targetNamespace != handleNamespace) - { - sw.Write("using "); - sw.Write(handleNamespace); - sw.WriteLine(';'); - } - } + sw.EmitSystemSupport(); - sw.WriteLine(); - - sw.Write("namespace "); - sw.Write(targetNamespace); - - if (generator.Config.GenerateFileScopedNamespaces) - { - sw.WriteLine(';'); - sw.WriteLine(); - indentString = ""; - } - else - { - sw.WriteLine(); - sw.WriteLine('{'); - } + if (kind == PInvokeGeneratorTransparentStructKind.HandleWin32) + { + var targetNamespace = GetNamespace(name); + var handleNamespace = GetNamespace("HANDLE"); - sw.Write(indentString); - sw.Write("public readonly "); + if (targetNamespace != handleNamespace) + { + sw.AddUsingDirective(handleNamespace); + } + } - if (isTypePointer || IsTransparentStructHexBased(kind)) - { - sw.Write("unsafe "); - } + if (Config.GenerateDocIncludes) + { + sw.WriteIndented("/// "); + } - sw.Write("partial struct "); - sw.Write(name); - sw.Write(" : IComparable, IComparable<"); - sw.Write(name); - sw.Write(">, IEquatable<"); - sw.Write(name); - sw.WriteLine(">, IFormattable"); + if (desc.NativeName is not null) + { + sw.AddNativeTypeNameAttribute(desc.NativeName); + } - sw.Write(indentString); - sw.WriteLine('{'); + sw.WriteIndented("public readonly "); - sw.Write(indentString); - sw.Write(" public readonly "); - sw.Write(type); - sw.WriteLine(" Value;"); - sw.WriteLine(); + if (isTypePointer || IsTransparentStructHexBased(kind)) + { + sw.Write("unsafe "); + } - // All transparent structs be created directly from the underlying type + sw.Write("partial struct "); + sw.Write(name); + sw.Write(" : "); + if (IsTransparentStructComparable(kind)) + { + sw.Write("IComparable, IComparable<"); + sw.Write(name); + sw.Write(">, IEquatable<"); + sw.Write(name); + sw.Write(">, "); + } + sw.WriteLine("IFormattable"); - sw.Write(indentString); - sw.Write(" public "); - sw.Write(name); - sw.Write('('); - sw.Write(type); - sw.WriteLine(" value)"); - sw.Write(indentString); - sw.WriteLine(" {"); - sw.Write(indentString); - sw.WriteLine(" Value = value;"); - sw.Write(indentString); - sw.WriteLine(" }"); - sw.WriteLine(); + sw.WriteBlockStart(); + { + if (desc.NativeType is not null) + { + sw.AddNativeTypeNameAttribute(desc.NativeType); + } + sw.WriteIndented("public readonly "); + sw.Write(type); + sw.WriteLine(" Value;"); + sw.WriteDivider(); - if (IsTransparentStructHandle(kind) || (kind == PInvokeGeneratorTransparentStructKind.HandleVulkan)) - { - // Handle like transparent structs define a NULL member + // All transparent structs be created directly from the underlying type - if (kind == PInvokeGeneratorTransparentStructKind.HandleWin32) - { - sw.Write(indentString); - sw.Write(" public static "); - sw.Write(name); - sw.Write(" INVALID_VALUE => new "); - sw.Write(name); + sw.WriteIndented("public "); + sw.Write(name); + sw.Write('('); + sw.Write(type); + sw.WriteLine(" value)"); + sw.WriteBlockStart(); + sw.WriteIndentedLine("Value = value;"); + sw.WriteBlockEnd(); - if (isTypePointer) - { - sw.Write("(("); - sw.Write(type); - sw.WriteLine(")(-1));"); - } - else - { - sw.WriteLine("(-1);"); - } + sw.WriteDivider(); - sw.WriteLine(); - } + if (IsTransparentStructHandle(kind) || (kind == PInvokeGeneratorTransparentStructKind.HandleVulkan)) + { + // Handle like transparent structs define a NULL member - sw.Write(indentString); - sw.Write(" public static "); + if (kind == PInvokeGeneratorTransparentStructKind.HandleWin32) + { + sw.WriteIndented("public static "); sw.Write(name); - sw.Write(" NULL => new "); + sw.Write(" INVALID_VALUE => new "); sw.Write(name); if (isTypePointer) { - sw.WriteLine("(null);"); + sw.Write("(("); + sw.Write(type); + sw.WriteLine(")(-1));"); } else { - sw.WriteLine("(0);"); + sw.WriteLine("(-1);"); } - sw.WriteLine(); + sw.WriteDivider(); } - else if (IsTransparentStructBoolean(kind)) - { - // Boolean like transparent structs define FALSE and TRUE members - sw.Write(indentString); - sw.Write(" public static "); - sw.Write(name); - sw.Write(" FALSE => new "); - sw.Write(name); - sw.WriteLine("(0);"); - sw.WriteLine(); + sw.WriteIndented("public static "); + sw.Write(name); + sw.Write(" NULL => new "); + sw.Write(name); - sw.Write(indentString); - sw.Write(" public static "); - sw.Write(name); - sw.Write(" TRUE => new "); - sw.Write(name); - sw.WriteLine("(1);"); - sw.WriteLine(); + if (isTypePointer) + { + sw.WriteLine("(null);"); + } + else + { + sw.WriteLine("(0);"); } - // All transparent structs support equality and relational comparisons with themselves + sw.WriteDivider(); + } + else if (IsTransparentStructBoolean(kind)) + { + // Boolean like transparent structs define FALSE and TRUE members + + sw.WriteIndented("public static "); + sw.Write(name); + sw.Write(" FALSE => new "); + sw.Write(name); + sw.WriteLine("(0);"); + sw.WriteDivider(); - sw.Write(indentString); - sw.Write(" public static bool operator ==("); + sw.WriteIndented("public static "); + sw.Write(name); + sw.Write(" TRUE => new "); + sw.Write(name); + sw.WriteLine("(1);"); + sw.WriteDivider(); + } + + if (IsTransparentStructComparable(kind)) + { + // Non-FnPtr transparent structs support equality and relational comparisons with themselves + + sw.WriteIndented("public static bool operator ==("); sw.Write(name); sw.Write(" left, "); sw.Write(name); sw.WriteLine(" right) => left.Value == right.Value;"); - sw.WriteLine(); + sw.WriteDivider(); - sw.Write(indentString); - sw.Write(" public static bool operator !=("); + sw.WriteIndented("public static bool operator !=("); sw.Write(name); sw.Write(" left, "); sw.Write(name); sw.WriteLine(" right) => left.Value != right.Value;"); - sw.WriteLine(); + sw.WriteDivider(); - sw.Write(indentString); - sw.Write(" public static bool operator <("); + sw.WriteIndented("public static bool operator <("); sw.Write(name); sw.Write(" left, "); sw.Write(name); sw.WriteLine(" right) => left.Value < right.Value;"); - sw.WriteLine(); + sw.WriteDivider(); - sw.Write(indentString); - sw.Write(" public static bool operator <=("); + sw.WriteIndented("public static bool operator <=("); sw.Write(name); sw.Write(" left, "); sw.Write(name); sw.WriteLine(" right) => left.Value <= right.Value;"); - sw.WriteLine(); + sw.WriteDivider(); - sw.Write(indentString); - sw.Write(" public static bool operator >("); + sw.WriteIndented("public static bool operator >("); sw.Write(name); sw.Write(" left, "); sw.Write(name); sw.WriteLine(" right) => left.Value > right.Value;"); - sw.WriteLine(); + sw.WriteDivider(); - sw.Write(indentString); - sw.Write(" public static bool operator >=("); + sw.WriteIndented("public static bool operator >=("); sw.Write(name); sw.Write(" left, "); sw.Write(name); sw.WriteLine(" right) => left.Value >= right.Value;"); - sw.WriteLine(); + sw.WriteDivider(); + } - if (IsTransparentStructHandle(kind)) - { - // Handle like transparent structs can be cast to/from void* + if (IsTransparentStructHandle(kind)) + { + // Handle like transparent structs can be cast to/from void* - sw.Write(indentString); - sw.Write(" public static explicit operator "); - sw.Write(name); - sw.Write("(void* value) => new "); - sw.Write(name); + sw.WriteIndented("public static explicit operator "); + sw.Write(name); + sw.Write("(void* value) => new "); + sw.Write(name); - if (type.Equals("void*", StringComparison.Ordinal)) + if (type.Equals("void*", StringComparison.Ordinal)) + { + sw.WriteLine("(value);"); + } + else + { + if (!IsUnsigned(type)) { - sw.WriteLine("(value);"); + sw.Write("unchecked"); } - else - { - if (!IsUnsigned(type)) - { - sw.Write("unchecked"); - } - sw.Write("(("); - sw.Write(type); - sw.WriteLine(")(value));"); - } - sw.WriteLine(); + sw.Write("(("); + sw.Write(type); + sw.WriteLine(")(value));"); + } + sw.WriteDivider(); - sw.Write(indentString); - sw.Write(" public static implicit operator void*("); - sw.Write(name); + sw.WriteIndented("public static implicit operator void*("); + sw.Write(name); - if (isTypePointer) + if (isTypePointer) + { + sw.WriteLine(" value) => value.Value;"); + } + else + { + var isUnchecked = !IsUnsigned(type); + sw.Write(" value) => "); + + if (isUnchecked) { - sw.WriteLine(" value) => value.Value;"); + sw.Write("unchecked("); } - else + sw.Write("(void*)(value.Value)"); + + if (isUnchecked) { - var isUnchecked = !IsUnsigned(type); - sw.Write(" value) => "); + sw.Write(")"); + } + sw.WriteDivider(); + } - if (isUnchecked) - { - sw.Write("unchecked("); - } - sw.Write("(void*)(value.Value)"); + sw.WriteDivider(); - if (isUnchecked) - { - sw.Write(")"); - } - sw.WriteLine(); - } + if ((kind == PInvokeGeneratorTransparentStructKind.HandleWin32) && !name.Equals("HANDLE", StringComparison.Ordinal)) + { + // Win32 handle like transparent structs can also be cast to/from HANDLE - sw.WriteLine(); + sw.WriteIndented("public static explicit operator "); + sw.Write(name); + sw.Write("(HANDLE value) => new "); + sw.Write(name); + sw.WriteLine("(value);"); + sw.WriteDivider(); - if ((kind == PInvokeGeneratorTransparentStructKind.HandleWin32) && !name.Equals("HANDLE", StringComparison.Ordinal)) - { - // Win32 handle like transparent structs can also be cast to/from HANDLE - - sw.Write(indentString); - sw.Write(" public static explicit operator "); - sw.Write(name); - sw.Write("(HANDLE value) => new "); - sw.Write(name); - sw.WriteLine("(value);"); - sw.WriteLine(); + sw.WriteIndented("public static implicit operator HANDLE("); + sw.Write(name); + sw.WriteLine(" value) => new HANDLE(value.Value);"); + sw.WriteDivider(); + } + } + else if (IsTransparentStructBoolean(kind)) + { + // Boolean like transparent structs define conversion to/from bool + // and support for usage in bool like scenarios. - sw.Write(indentString); - sw.Write(" public static implicit operator HANDLE("); - sw.Write(name); - sw.WriteLine(" value) => new HANDLE(value.Value);"); - sw.WriteLine(); - } + sw.WriteIndented("public static implicit operator bool("); + sw.Write(name); + sw.WriteLine(" value) => value.Value != 0;"); + sw.WriteDivider(); + + sw.WriteIndented("public static implicit operator "); + sw.Write(name); + sw.Write("(bool value) => new "); + sw.Write(name); + + if (type.Equals("int", StringComparison.Ordinal)) + { + sw.WriteLine("(value ? 1 : 0);"); + } + else if (type.Equals("uint", StringComparison.Ordinal)) + { + sw.WriteLine("(value ? 1u : 0u);"); } - else if (IsTransparentStructBoolean(kind)) + else { - // Boolean like transparent structs define conversion to/from bool - // and support for usage in bool like scenarios. + sw.Write("(("); + sw.Write(type); + sw.WriteLine(")(value ? 1u : 0u);"); + } - sw.Write(indentString); - sw.Write(" public static implicit operator bool("); - sw.Write(name); - sw.WriteLine(" value) => value.Value != 0;"); - sw.WriteLine(); + sw.WriteDivider(); - sw.Write(indentString); - sw.Write(" public static implicit operator "); - sw.Write(name); - sw.Write("(bool value) => new "); - sw.Write(name); + sw.WriteIndented("public static bool operator false("); + sw.Write(name); + sw.WriteLine(" value) => value.Value == 0;"); + sw.WriteDivider(); - if (type.Equals("int", StringComparison.Ordinal)) - { - sw.WriteLine("(value ? 1 : 0);"); - } - else if (type.Equals("uint", StringComparison.Ordinal)) - { - sw.WriteLine("(value ? 1u : 0u);"); - } - else - { - sw.Write("(("); - sw.Write(type); - sw.WriteLine(")(value ? 1u : 0u);"); - } + sw.WriteIndented("public static bool operator true("); + sw.Write(name); + sw.WriteLine(" value) => value.Value != 0;"); + sw.WriteDivider(); + } - sw.WriteLine(); + // All transparent structs define casts to/from the various integer types - sw.Write(indentString); - sw.Write(" public static bool operator false("); - sw.Write(name); - sw.WriteLine(" value) => value.Value == 0;"); - sw.WriteLine(); + if (kind == PInvokeGeneratorTransparentStructKind.FnPtr) + { + OutputConversions(sw, name, type, kind, type); + } + else + { + OutputConversions(sw, name, type, kind, "byte"); + OutputConversions(sw, name, type, kind, "short"); + OutputConversions(sw, name, type, kind, "sbyte"); + OutputConversions(sw, name, type, kind, "ushort"); + OutputConversions(sw, name, type, kind, "int"); + OutputConversions(sw, name, type, kind, "long"); + OutputConversions(sw, name, type, kind, "uint"); + OutputConversions(sw, name, type, kind, "ulong"); + } - sw.Write(indentString); - sw.Write(" public static bool operator true("); - sw.Write(name); - sw.WriteLine(" value) => value.Value != 0;"); - sw.WriteLine(); - } + OutputConversions(sw, name, type, kind, "nint"); + OutputConversions(sw, name, type, kind, "nuint"); - // All transparent structs define casts to/from the various integer types + if (IsTransparentStructComparable(kind)) + { + // Non-FnPtr transparent structs override CompareTo, Equals, GetHashCode - OutputConversions(sw, indentString, name, type, kind, "byte"); - OutputConversions(sw, indentString, name, type, kind, "short"); - OutputConversions(sw, indentString, name, type, kind, "int"); - OutputConversions(sw, indentString, name, type, kind, "long"); - OutputConversions(sw, indentString, name, type, kind, "nint"); - OutputConversions(sw, indentString, name, type, kind, "sbyte"); - OutputConversions(sw, indentString, name, type, kind, "ushort"); - OutputConversions(sw, indentString, name, type, kind, "uint"); - OutputConversions(sw, indentString, name, type, kind, "ulong"); - OutputConversions(sw, indentString, name, type, kind, "nuint"); + sw.WriteIndentedLine("public int CompareTo(object? obj)"); + sw.WriteBlockStart(); + { + sw.WriteIndented("if (obj is "); + sw.Write(name); + sw.WriteLine(" other)"); + sw.WriteBlockStart(); + sw.WriteIndentedLine("return CompareTo(other);"); + sw.WriteBlockEnd(); - // All transparent structs override CompareTo, Equals, GetHashCode, and ToString + sw.WriteDivider(); - sw.Write(indentString); - sw.WriteLine(" public int CompareTo(object? obj)"); - sw.Write(indentString); - sw.WriteLine(" {"); - sw.Write(indentString); - sw.Write(" if (obj is "); - sw.Write(name); - sw.WriteLine(" other)"); - sw.Write(indentString); - sw.WriteLine(" {"); - sw.Write(indentString); - sw.WriteLine(" return CompareTo(other);"); - sw.Write(indentString); - sw.WriteLine(" }"); - sw.WriteLine(); - sw.Write(indentString); - sw.Write(" return (obj is null) ? 1 : throw new ArgumentException(\"obj is not an instance of "); - sw.Write(name); - sw.WriteLine(".\");"); - sw.Write(indentString); - sw.WriteLine(" }"); - sw.WriteLine(); + sw.WriteIndented("return (obj is null) ? 1 : throw new ArgumentException(\"obj is not an instance of "); + sw.Write(name); + sw.WriteLine(".\");"); + } + sw.WriteBlockEnd(); + + sw.WriteDivider(); - sw.Write(indentString); - sw.Write(" public int CompareTo("); + sw.WriteIndented("public int CompareTo("); sw.Write(name); + sw.Write(" other) => "); if (isTypePointer) { - sw.WriteLine(" other) => ((nuint)(Value)).CompareTo((nuint)(other.Value));"); + sw.WriteLine("((nuint)(Value)).CompareTo((nuint)(other.Value));"); } else { - sw.WriteLine(" other) => Value.CompareTo(other.Value);"); + sw.WriteLine("Value.CompareTo(other.Value);"); } - sw.WriteLine(); + sw.WriteDivider(); - sw.Write(indentString); - sw.Write(" public override bool Equals(object? obj) => (obj is "); + sw.WriteIndented("public override bool Equals(object? obj) => (obj is "); sw.Write(name); sw.WriteLine(" other) && Equals(other);"); - sw.WriteLine(); - sw.Write(indentString); - sw.Write(" public bool Equals("); + sw.WriteDivider(); + + sw.WriteIndented("public bool Equals("); sw.Write(name); + sw.Write(" other) => "); if (isTypePointer) { - sw.WriteLine(" other) => ((nuint)(Value)).Equals((nuint)(other.Value));"); + sw.WriteLine("((nuint)(Value)).Equals((nuint)(other.Value));"); } else { - sw.WriteLine(" other) => Value.Equals(other.Value);"); + sw.WriteLine("Value.Equals(other.Value);"); } - sw.WriteLine(); + sw.WriteDivider(); - sw.Write(indentString); - sw.Write(" public override int GetHashCode() => "); + sw.WriteIndented("public override int GetHashCode() => "); if (isTypePointer) { @@ -1385,122 +1381,122 @@ static void GenerateTransparentStructs(PInvokeGenerator generator, Stream? strea } sw.WriteLine(".GetHashCode();"); - sw.WriteLine(); - sw.Write(indentString); - sw.Write(" public override string ToString() => "); + sw.WriteDivider(); + } - if (isTypePointer) - { - sw.Write("((nuint)(Value))"); - } - else - { - sw.Write("Value"); - } + // All transparent structs override ToString - sw.Write(".ToString("); + sw.WriteIndented("public override string ToString() => "); - if (IsTransparentStructHexBased(kind)) - { - var (typeSrcSize, typeDstSize, typeSign) = GetSizeAndSignOf(type); + if (isTypePointer) + { + sw.Write("((nuint)(Value))"); + } + else + { + sw.Write("Value"); + } - if (typeSrcSize != typeDstSize) - { - sw.Write("(sizeof(nint) == 4) ? \"X8\" : \"X16\""); - } - else - { - sw.Write('"'); - sw.Write('X'); - sw.Write(typeSrcSize * 2); - sw.Write('"'); - } - } + sw.Write(".ToString("); - sw.WriteLine(");"); - sw.WriteLine(); - - sw.Write(indentString); - sw.Write(" public string ToString(string? format, IFormatProvider? formatProvider) => "); + if (IsTransparentStructHexBased(kind)) + { + var (typeSrcSize, typeDstSize, typeSign) = GetSizeAndSignOf(type); - if (isTypePointer) + if (typeSrcSize != typeDstSize) { - sw.Write("((nuint)(Value))"); + sw.Write("(sizeof(nint) == 4) ? \"X8\" : \"X16\""); } else { - sw.Write("Value"); + sw.Write('"'); + sw.Write('X'); + sw.Write(typeSrcSize * 2); + sw.Write('"'); } + } - sw.WriteLine(".ToString(format, formatProvider);"); + sw.WriteLine(");"); - sw.Write(indentString); - sw.WriteLine('}'); + sw.WriteDivider(); - if (!generator.Config.GenerateFileScopedNamespaces) - { - sw.WriteLine('}'); - } + sw.WriteIndented("public string ToString(string? format, IFormatProvider? formatProvider) => "); - if (!leaveStreamOpen) - { - stream = null; - } + if (isTypePointer) + { + sw.Write("((nuint)(Value))"); } - - static (int srcSize, int dstSize, int sign) GetSizeAndSignOf(string type) + else { - if (type.Contains('*', StringComparison.Ordinal)) - { - return (8, 4, +1); - } - - return type switch { - "sbyte" => (1, 1, -1), - "byte" => (1, 1, +1), - "short" => (2, 2, -1), - "ushort" => (2, 2, +1), - "int" => (4, 4, -1), - "uint" => (4, 4, +1), - "nint" => (8, 4, -1), - "nuint" => (8, 4, +1), - "long" => (8, 8, -1), - "ulong" => (8, 8, +1), - _ => (0, 0, 0), - }; + sw.Write("Value"); } - static void OutputConversions(StreamWriter sw, string indentString, string name, string type, PInvokeGeneratorTransparentStructKind kind, string target) + sw.WriteLine(".ToString(format, formatProvider);"); + } + sw.WriteBlockEnd(); + + StopCSharpCode(); + + static (int srcSize, int dstSize, int sign) GetSizeAndSignOf(string type) + { + if (type.Contains('*', StringComparison.Ordinal)) { - var (typeSrcSize, typeDstSize, typeSign) = GetSizeAndSignOf(type); - var (targetSrcSize, targetDstSize, targetSign) = GetSizeAndSignOf(target); + return (8, 4, +1); + } - var isTypePointer = type.Contains('*', StringComparison.Ordinal); - var isPointerToNativeCast = (isTypePointer && target.Equals("nint", StringComparison.Ordinal)) || (isTypePointer && target.Equals("nuint", StringComparison.Ordinal)); + return type switch { + "sbyte" => (1, 1, -1), + "byte" => (1, 1, +1), + "short" => (2, 2, -1), + "ushort" => (2, 2, +1), + "int" => (4, 4, -1), + "uint" => (4, 4, +1), + "nint" => (8, 4, -1), + "nuint" => (8, 4, +1), + "long" => (8, 8, -1), + "ulong" => (8, 8, +1), + _ => (0, 0, 0), + }; + } - // public static castFromKind operator name(target value) => new name((type)(value)); + static void OutputConversions(CSharpOutputBuilder sw, string name, string type, PInvokeGeneratorTransparentStructKind kind, string target) + { + var (typeSrcSize, typeDstSize, typeSign) = GetSizeAndSignOf(type); + var (targetSrcSize, targetDstSize, targetSign) = GetSizeAndSignOf(target); - var castFromKind = "implicit"; - var areEquivalentTypeAndTarget = (type == target) || isPointerToNativeCast - || (type.Equals("nint", StringComparison.Ordinal) && target.Equals("int", StringComparison.Ordinal)) - || (type.Equals("nuint", StringComparison.Ordinal) && target.Equals("uint", StringComparison.Ordinal)) - || (type.Equals("long", StringComparison.Ordinal) && target.Equals("nint", StringComparison.Ordinal)) - || (type.Equals("ulong", StringComparison.Ordinal) && target.Equals("nuint", StringComparison.Ordinal)); + var isTypePointer = type.Contains('*', StringComparison.Ordinal); + var isPointerToNativeCast = (isTypePointer && target.Equals("nint", StringComparison.Ordinal)) || (isTypePointer && target.Equals("nuint", StringComparison.Ordinal)); - if (((typeDstSize <= targetSrcSize) && !areEquivalentTypeAndTarget) || ((targetSign == -1) && (typeSign == +1)) || IsTransparentStructHandle(kind)) - { - castFromKind = "explicit"; - } + // public static castFromKind operator name(target value) => new name((type)(value)); - sw.Write(indentString); - sw.Write(" public static "); - sw.Write(castFromKind); - sw.Write(" operator "); - sw.Write(name); - sw.Write('('); - sw.Write(target); - sw.Write(" value) => new "); + var castFromKind = "implicit"; + var areEquivalentType = type == target; + var areEquivalentTypeAndTarget = areEquivalentType || isPointerToNativeCast + || (type.Equals("nint", StringComparison.Ordinal) && target.Equals("int", StringComparison.Ordinal)) + || (type.Equals("nuint", StringComparison.Ordinal) && target.Equals("uint", StringComparison.Ordinal)) + || (type.Equals("long", StringComparison.Ordinal) && target.Equals("nint", StringComparison.Ordinal)) + || (type.Equals("ulong", StringComparison.Ordinal) && target.Equals("nuint", StringComparison.Ordinal)); + + var isForcedExplicit = !areEquivalentType && (kind == PInvokeGeneratorTransparentStructKind.FnPtr); + + var isDowncast = (typeDstSize <= targetSrcSize) && !areEquivalentTypeAndTarget; + var isSignChange = (targetSign == -1) && (typeSign == +1); + if (isForcedExplicit || isDowncast || isSignChange || IsTransparentStructHandle(kind)) + { + castFromKind = "explicit"; + } + + sw.WriteIndented("public static "); + sw.Write(castFromKind); + sw.Write(" operator "); + sw.Write(name); + sw.Write('('); + sw.Write(target); + sw.Write(" value)"); + sw.BeginBody(isExpressionBody: true); + { + sw.Write("new "); sw.Write(name); sw.Write('('); @@ -1518,32 +1514,37 @@ static void OutputConversions(StreamWriter sw, string indentString, string name, sw.Write("))"); } - sw.WriteLine(");"); - sw.WriteLine(); + sw.Write(")"); + sw.WriteSemicolon(); + } + sw.EndBody(true); + sw.WriteDivider(); - // public static castToKind operator target(name value) => ((target)(value.Value)); + // public static castToKind operator target(name value) => ((target)(value.Value)); - var castToKind = "implicit"; - areEquivalentTypeAndTarget = (type == target) || isPointerToNativeCast - || (type.Equals("int", StringComparison.Ordinal) && target.Equals("nint", StringComparison.Ordinal)) - || (type.Equals("uint", StringComparison.Ordinal) && target.Equals("nuint", StringComparison.Ordinal)) - || (type.Equals("nint", StringComparison.Ordinal) && target.Equals("long", StringComparison.Ordinal)) - || (type.Equals("nuint", StringComparison.Ordinal) && target.Equals("ulong", StringComparison.Ordinal)); + var castToKind = "implicit"; + areEquivalentTypeAndTarget = (type == target) || isPointerToNativeCast + || (type.Equals("int", StringComparison.Ordinal) && target.Equals("nint", StringComparison.Ordinal)) + || (type.Equals("uint", StringComparison.Ordinal) && target.Equals("nuint", StringComparison.Ordinal)) + || (type.Equals("nint", StringComparison.Ordinal) && target.Equals("long", StringComparison.Ordinal)) + || (type.Equals("nuint", StringComparison.Ordinal) && target.Equals("ulong", StringComparison.Ordinal)); - if (((targetDstSize <= typeSrcSize) && !areEquivalentTypeAndTarget) || ((typeSign == -1) && (targetSign == +1))) - { - castToKind = "explicit"; - } - - sw.Write(indentString); - sw.Write(" public static "); - sw.Write(castToKind); - sw.Write(" operator "); - sw.Write(target); - sw.Write('('); - sw.Write(name); - sw.Write(" value) => "); + isDowncast = (targetDstSize <= typeSrcSize) && !areEquivalentTypeAndTarget; + isSignChange = (typeSign == -1) && (targetSign == +1); + if (isForcedExplicit || isDowncast || isSignChange) + { + castToKind = "explicit"; + } + sw.WriteIndented("public static "); + sw.Write(castToKind); + sw.Write(" operator "); + sw.Write(target); + sw.Write('('); + sw.Write(name); + sw.Write(" value)"); + sw.BeginBody(isExpressionBody: true); + { if (castToKind.Equals("explicit", StringComparison.Ordinal) || isPointerToNativeCast) { sw.Write('('); @@ -1558,9 +1559,10 @@ static void OutputConversions(StreamWriter sw, string indentString, string name, sw.Write(')'); } - sw.WriteLine(';'); - sw.WriteLine(); + sw.WriteSemicolon(); } + sw.EndBody(true); + sw.WriteDivider(); } } @@ -3117,7 +3119,6 @@ private string GetRemappedName(string name, Cursor? cursor, bool tryRemapOperato if (_config.RemappedNames.TryGetValue(tmpName, out remappedName)) { - wasRemapped = true; _ = _usedRemappings.Add(tmpName); return AddUsingDirectiveIfNeeded(_outputBuilder, remappedName, skipUsing); @@ -3765,6 +3766,12 @@ private string GetTypeName(Cursor? cursor, Cursor? context, Type rootType, Type // platform size, based on whatever parameters were passed into clang. var remappedName = GetRemappedName(result.typeName, cursor, tryRemapOperatorName: false, out var wasRemapped, skipUsing: true); + + if (_config.GenerateFnPtrWrapper && !ignoreTransparentStructsWhereRequired && IsFunctionPointer(cursor, typedefType)) + { + wasRemapped = true; + } + result.typeName = wasRemapped ? remappedName : GetTypeName(cursor, context, rootType, typedefType.Decl.UnderlyingType, ignoreTransparentStructsWhereRequired, isTemplate, out _); } else if (type is UsingType usingType) @@ -3789,6 +3796,39 @@ private string GetTypeName(Cursor? cursor, Cursor? context, Type rootType, Type nativeTypeName = result.nativeTypeName; return result.typeName; + + bool IsFunctionPointer(Cursor? cursor, Type underlyingType) + { + bool ForPointeeType(Cursor? cursor, Type pointeeType) + { + if (IsType(cursor, pointeeType, out _)) + { + return true; + } + + // Do not recurse if the pointee is a pointer, + // we do not want to detect pointers to function pointers. + return false; + } + + if (IsType(cursor, underlyingType, out var pointerType)) + { + return ForPointeeType(cursor, pointerType.PointeeType); + } + else if (IsType(cursor, underlyingType, out var referenceType)) + { + return ForPointeeType(cursor, referenceType.PointeeType); + } + else if (IsType(cursor, underlyingType, out var templateSpecializationType)) + { + if (templateSpecializationType.IsTypeAlias) + { + return IsFunctionPointer(cursor, templateSpecializationType.AliasedType); + } + } + + return false; + } } private string GetTypeNameForPointeeType(Cursor? cursor, Cursor? context, Type rootType, Type pointeeType, bool ignoreTransparentStructsWhereRequired, bool isTemplate, out string nativePointeeTypeName, out bool isAdjusted) @@ -5403,13 +5443,16 @@ private static bool IsTransparentStructBoolean(PInvokeGeneratorTransparentStruct => kind is PInvokeGeneratorTransparentStructKind.Boolean; private static bool IsTransparentStructHandle(PInvokeGeneratorTransparentStructKind kind) - => kind is PInvokeGeneratorTransparentStructKind.Handle + => kind is PInvokeGeneratorTransparentStructKind.Handle or PInvokeGeneratorTransparentStructKind.HandleWin32; private static bool IsTransparentStructHexBased(PInvokeGeneratorTransparentStructKind kind) => IsTransparentStructHandle(kind) || (kind == PInvokeGeneratorTransparentStructKind.TypedefHex); + private static bool IsTransparentStructComparable(PInvokeGeneratorTransparentStructKind kind) + => kind is not PInvokeGeneratorTransparentStructKind.FnPtr; + private bool IsUnchecked(string targetTypeName, Stmt stmt) { if (IsPrevContextDecl(out var parentVarDecl, out _)) @@ -6149,7 +6192,7 @@ private bool NeedsReturnFixup(CXXMethodDecl cxxMethodDecl) private static bool NeedsNewKeyword(string name) { - return name.Equals("Equals",StringComparison.Ordinal) + return name.Equals("Equals", StringComparison.Ordinal) || name.Equals("GetHashCode", StringComparison.Ordinal) || name.Equals("GetType", StringComparison.Ordinal) || name.Equals("MemberwiseClone", StringComparison.Ordinal) diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs index 3dfb786b..394b765f 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs @@ -227,6 +227,8 @@ public IReadOnlyCollection ExcludedNames public bool GenerateFileScopedNamespaces => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateFileScopedNamespaces); + public bool GenerateFnPtrWrapper => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateFnPtrWrapper); + public bool GenerateGenericPointerWrapper => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateGenericPointerWrapper); public bool GenerateGuidMember => _options.HasFlag(PInvokeGeneratorConfigurationOptions.GenerateGuidMember); diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfigurationOptions.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfigurationOptions.cs index db91ccd4..3c12233a 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfigurationOptions.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfigurationOptions.cs @@ -86,4 +86,6 @@ public enum PInvokeGeneratorConfigurationOptions : long GenerateCallConvMemberFunction = 1L << 37, GenerateGenericPointerWrapper = 1L << 38, + + GenerateFnPtrWrapper = 1L << 39, } diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorTransparentStructKind.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorTransparentStructKind.cs index 8f61061e..e85b4157 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorTransparentStructKind.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorTransparentStructKind.cs @@ -9,4 +9,5 @@ public enum PInvokeGeneratorTransparentStructKind HandleWin32 = 4, TypedefHex = 5, HandleVulkan = 6, + FnPtr = 7, } diff --git a/sources/ClangSharpPInvokeGenerator/Program.cs b/sources/ClangSharpPInvokeGenerator/Program.cs index ab53111e..e87a664d 100644 --- a/sources/ClangSharpPInvokeGenerator/Program.cs +++ b/sources/ClangSharpPInvokeGenerator/Program.cs @@ -172,6 +172,7 @@ public static class Program new TwoColumnHelpRow("generate-native-bitfield-attribute", "[NativeBitfield(\"\", offset: #, length: #)] attribute should be generated to document the encountered bitfield layout."), new TwoColumnHelpRow("generate-native-inheritance-attribute", "[NativeInheritance(\"\")] attribute should be generated to document the encountered C++ base type."), new TwoColumnHelpRow("generate-generic-pointer-wrapper", "Pointer should be used for limited generic type support."), + new TwoColumnHelpRow("generate-fnptr-wrapper", "Function pointers should be wrapped in transparent structs instead of delegates."), new TwoColumnHelpRow("generate-setslastsystemerror-attribute", "[SetsLastSystemError] attribute should be generated rather than using SetLastError = true."), new TwoColumnHelpRow("generate-template-bindings", "Bindings for template-definitions should be generated. This is currently experimental."), new TwoColumnHelpRow("generate-unmanaged-constants", "Unmanaged constants should be generated using static ref readonly properties. This is currently experimental."), @@ -573,6 +574,12 @@ public static void Run(InvocationContext context) break; } + case "generate-fnptr-wrapper": + { + configOptions |= PInvokeGeneratorConfigurationOptions.GenerateFnPtrWrapper; + break; + } + case "generate-setslastsystemerror-attribute": { configOptions |= PInvokeGeneratorConfigurationOptions.GenerateSetsLastSystemErrorAttribute;