-
-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added NativeBooleanDeclaration and NativeCharDeclaration wrappers for…
… use in function pointers where marshaling can't be customized. See #99 for details.
- Loading branch information
1 parent
69bf661
commit 120b8de
Showing
3 changed files
with
360 additions
and
0 deletions.
There are no files selected for viewing
111 changes: 111 additions & 0 deletions
111
Biohazrd.CSharp/#Declarations/NativeBooleanDeclaration.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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));"); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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);"); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.