Skip to content

Commit

Permalink
Added NativeBooleanDeclaration and NativeCharDeclaration wrappers for…
Browse files Browse the repository at this point in the history
… use in function pointers where marshaling can't be customized.

See #99 for details.
  • Loading branch information
PathogenDavid committed Nov 25, 2020
1 parent 69bf661 commit 120b8de
Show file tree
Hide file tree
Showing 3 changed files with 360 additions and 0 deletions.
111 changes: 111 additions & 0 deletions Biohazrd.CSharp/#Declarations/NativeBooleanDeclaration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using Biohazrd.CSharp.Infrastructure;
using Biohazrd.Transformation;
using Biohazrd.Transformation.Infrastructure;
using static Biohazrd.CSharp.CSharpCodeWriter;

namespace Biohazrd.CSharp
{
/// <summary>This type works around the fact that .NET does not have a way to use booleans in P/Invokes without marshaling.</summary>
/// <remarks>
/// This type is also required to use 1-byte booleans in the context of function pointers, which cannot express marshaling semantics.
///
/// See https://github.com/InfectedLibraries/Biohazrd/issues/99 for details.
/// </remarks>
public sealed record NativeBooleanDeclaration : TranslatedDeclaration, ICustomTranslatedDeclaration, ICustomCSharpTranslatedDeclaration
{
public NativeBooleanDeclaration()
: base(TranslatedFile.Synthesized)
=> Name = "NativeBoolean";

TransformationResult ICustomTranslatedDeclaration.TransformChildren(ITransformation transformation, TransformationContext context)
=> this;

TransformationResult ICustomTranslatedDeclaration.TransformTypeChildren(ITypeTransformation transformation, TransformationContext context)
=> this;

void ICustomCSharpTranslatedDeclaration.GenerateOutput(ICSharpOutputGenerator outputGenerator, VisitorContext context, CSharpCodeWriter writer)
{
writer.Using("System"); // IComprable, IComparable<T>, IEquatable<T>
writer.Using("System.Runtime.InteropServices"); // StructLayoutAttribute, LayoutKind
writer.Using("System.Runtime.CompilerServices"); // MethodImplAttribute, MethodImplOptions, Unsafe
string sanitizedName = SanitizeIdentifier(Name);

// Developers should typically not use this type directly anyway, but we provide the same instance methods as System.Boolean
// for scenarios where the return type of a native function is immeidately consumed.
// IE: Console.WriteLine(MyNativeFunction().ToString());
//
// Note that we do not bother implementing IConvertible since it is not likely to be used and
// and all but one of its methods are explicit interface implementations anyway.
// That one method is seemingly never used directly: https://apisof.net/catalog/System.Boolean.System.IConvertible.GetTypeCode%28%29
writer.EnsureSeparation();
writer.WriteLine("[StructLayout(LayoutKind.Sequential)]"); // This prevents a warning on the Value field.
writer.WriteLine($"public readonly struct {sanitizedName} : IComparable, IComparable<bool>, IEquatable<bool>, IComparable<{sanitizedName}>, IEquatable<{sanitizedName}>");
using (writer.Block())
{
// Note: You get slightly better codegen in some scenarios if this is a bool with MarshalAs attached to it,
// but obviously at the expense of having the marshaler touch it. This probably only moves the cost,
// so let's avoid getting the marshaler involed at all.
writer.WriteLine("private readonly byte Value;");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine($"public static implicit operator bool({sanitizedName} b)");
writer.WriteLineIndented($"=> Unsafe.As<{sanitizedName}, bool>(ref b);");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine($"public static implicit operator {sanitizedName}(bool b)");
writer.WriteLineIndented($"=> Unsafe.As<bool, {sanitizedName}>(ref b);");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine("public override int GetHashCode()");
writer.WriteLineIndented("=> Unsafe.As<byte, bool>(ref Unsafe.AsRef(in Value)).GetHashCode();");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine("public override string ToString()");
writer.WriteLineIndented("=> Unsafe.As<byte, bool>(ref Unsafe.AsRef(in Value)).ToString();");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine("public string ToString(IFormatProvider? provider)");
writer.WriteLineIndented("=> Unsafe.As<byte, bool>(ref Unsafe.AsRef(in Value)).ToString(provider);");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine("public bool TryFormat(Span<char> destination, out int charsWritten)");
writer.WriteLineIndented("=> Unsafe.As<byte, bool>(ref Unsafe.AsRef(in Value)).TryFormat(destination, out charsWritten);");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine("public override bool Equals(object? obj)");
using (writer.Indent())
{
writer.WriteLine("=> obj switch");
writer.WriteLine('{');
using (writer.Indent())
{
writer.WriteLine("bool boolean => this == boolean,");
writer.WriteLine($"{sanitizedName} nativeBool => this == nativeBool,");
writer.WriteLine("_ => false");
}
writer.WriteLine("};");
}
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine("public bool Equals(bool other)");
writer.WriteLineIndented("=> this == other;");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine($"public bool Equals({sanitizedName} other)");
writer.WriteLineIndented("=> this == other;");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine("public int CompareTo(object? obj)");
writer.WriteLineIndented("=> Unsafe.As<byte, bool>(ref Unsafe.AsRef(in Value)).CompareTo(obj);");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine("public int CompareTo(bool value)");
writer.WriteLineIndented("=> Unsafe.As<byte, bool>(ref Unsafe.AsRef(in Value)).CompareTo(value);");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine($"public int CompareTo({sanitizedName} value)");
writer.WriteLineIndented($"=> CompareTo(Unsafe.As<{sanitizedName}, bool>(ref value));");
}
}
}
}
131 changes: 131 additions & 0 deletions Biohazrd.CSharp/#Declarations/NativeCharDeclaration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using Biohazrd.CSharp.Infrastructure;
using Biohazrd.Transformation;
using Biohazrd.Transformation.Infrastructure;
using static Biohazrd.CSharp.CSharpCodeWriter;

namespace Biohazrd.CSharp
{
/// <summary>This type works around the fact that you can't specify marshaling for chars on function pointers.</summary>
/// <remarks>See https://github.com/InfectedLibraries/Biohazrd/issues/99 for details.</remarks>
public sealed record NativeCharDeclaration : TranslatedDeclaration, ICustomTranslatedDeclaration, ICustomCSharpTranslatedDeclaration
{
public NativeCharDeclaration()
: base(TranslatedFile.Synthesized)
=> Name = "NativeChar";

TransformationResult ICustomTranslatedDeclaration.TransformChildren(ITransformation transformation, TransformationContext context)
=> this;

TransformationResult ICustomTranslatedDeclaration.TransformTypeChildren(ITypeTransformation transformation, TransformationContext context)
=> this;

void ICustomCSharpTranslatedDeclaration.GenerateOutput(ICSharpOutputGenerator outputGenerator, VisitorContext context, CSharpCodeWriter writer)
{
writer.Using("System"); // IComparable, IComparable<T>, IEquatable<T>
writer.Using("System.Runtime.InteropServices"); // StructLayoutAttribute, LayoutKind
writer.Using("System.Runtime.CompilerServices"); // MethodImplAttribute, MethodImplOptions, Unsafe
string sanitizedName = SanitizeIdentifier(Name);

// Developers should typically not use this type directly anyway, but we provide the same instance methods as System.Char
// for scenarios where the return type of a native function is immeidately consumed.
// IE: Console.WriteLine(MyNativeFunction().ToString());
//
// Note that we do not bother implementing IConvertible since it is not likely to be used and
// and all but one of its methods are explicit interface implementations anyway.
// That one method is seemingly never used directly: https://apisof.net/catalog/System.Boolean.System.IConvertible.GetTypeCode%28%29
//
// Using CharSet.Unicode is the important part here. The CLR will treat the `Value` field as blittable when the struct is marked as Unicode.
// https://github.com/dotnet/runtime/blob/29e9b5b7fd95231d9cd9d3ae351404e63cbb6d5a/src/coreclr/src/vm/fieldmarshaler.cpp#L233-L235
writer.EnsureSeparation();
writer.WriteLine("[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]");
writer.WriteLine($"public readonly struct {sanitizedName} : IComparable, IComparable<char>, IEquatable<char>, IComparable<{sanitizedName}>, IEquatable<{sanitizedName}>");
using (writer.Block())
{
writer.WriteLine("private readonly char Value;");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine($"private {sanitizedName}(char value)");
writer.WriteLineIndented("=> Value = value;");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine($"public static implicit operator char({sanitizedName} c)");
writer.WriteLineIndented("=> c.Value;");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine($"public static implicit operator {sanitizedName}(char c)");
writer.WriteLineIndented($"=> new {sanitizedName}(c);");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine($"public static bool operator ==({sanitizedName} a, {sanitizedName} b)");
writer.WriteLineIndented("=> a.Value == b.Value;");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine($"public static bool operator !=({sanitizedName} a, {sanitizedName} b)");
writer.WriteLineIndented("=> a.Value != b.Value;");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine($"public static bool operator ==(char a, {sanitizedName} b)");
writer.WriteLineIndented("=> a == b.Value;");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine($"public static bool operator !=(char a, {sanitizedName} b)");
writer.WriteLineIndented("=> a != b.Value;");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine($"public static bool operator ==({sanitizedName} a, char b)");
writer.WriteLineIndented("=> a.Value == b;");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine($"public static bool operator !=({sanitizedName} a, char b)");
writer.WriteLineIndented("=> a.Value != b;");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine("public override int GetHashCode()");
writer.WriteLineIndented("=> Value.GetHashCode();");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine("public override bool Equals(object? obj)");
using (writer.Indent())
{
writer.WriteLine("=> obj switch");
writer.WriteLine('{');
using (writer.Indent())
{
writer.WriteLine("char character => this == character,");
writer.WriteLine($"{sanitizedName} nativeChar => this == nativeChar,");
writer.WriteLine("_ => false");
}
writer.WriteLine("};");
}
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine("public bool Equals(char other)");
writer.WriteLineIndented("=> this == other;");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine($"public bool Equals({sanitizedName} other)");
writer.WriteLineIndented("=> this == other;");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine("public int CompareTo(object? obj)");
writer.WriteLineIndented("=> Value.CompareTo(obj);");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine("public int CompareTo(char other)");
writer.WriteLineIndented("=> Value.CompareTo(other);");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine($"public int CompareTo({sanitizedName} value)");
writer.WriteLineIndented("=> Value.CompareTo(value.Value);");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine("public override string ToString()");
writer.WriteLineIndented("=> Value.ToString();");
writer.WriteLine();
writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]");
writer.WriteLine("public string ToString(IFormatProvider? provider)");
writer.WriteLineIndented("=> Value.ToString(provider);");
}
}
}
}
Loading

0 comments on commit 120b8de

Please sign in to comment.