Skip to content

Commit 262af7b

Browse files
author
dahall
committed
Buildable checkin for new structure marshaling
1 parent 13d92ec commit 262af7b

File tree

9 files changed

+2140
-3
lines changed

9 files changed

+2140
-3
lines changed

Core/Marshaler/Extensions.cs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
using System.Linq;
2+
using System.Numerics;
3+
using System.Reflection;
4+
using System.Runtime.CompilerServices;
5+
using Vanara.Extensions.Reflection;
6+
using Vanara.PInvoke;
7+
8+
namespace Vanara.Marshaler;
9+
10+
internal static class Extensions
11+
{
12+
public const BindingFlags allInstFields = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
13+
14+
public static bool HasMembers(this Type type)
15+
{
16+
var f = type.GetFields(allInstFields);
17+
return f.Length switch
18+
{
19+
0 => false,
20+
> 1 => true,
21+
_ => f[0].FieldType != type
22+
};
23+
}
24+
25+
/// <summary>Gets a value that determines if the type is a blittable type.</summary>
26+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
27+
public static bool IsMarshaledType(this Type type) => type.GetCustomAttribute<MarshaledAttribute>() != null;
28+
29+
//public static bool IsBlittable2(this Type type) => type.IsPrimitive || type.IsMarshalByRef || type.IsEnum ||
30+
// (type.IsLayoutSequential && type.GetFields(allInstFields).All(fi => fi.FieldType.IsBlittable2()));
31+
public static bool IsBlittable2(this Type? type)
32+
{
33+
// Try to prevent exception-based check by doing a bunch of checks
34+
if (type is not null && (type.IsBlittablePrimitive() || type.IsBlittableArray() ||
35+
(!type.IsPrimitive && type.IsLayoutSequential && type.GetFields(allInstFields).All(Vanara.Extensions.Reflection.ReflectionExtensions.IsBlittableField))))
36+
{
37+
// Final check: Non-blittable types cannot allocate pinned handle
38+
try
39+
{
40+
Marshal.SizeOf(type!);
41+
return true;
42+
}
43+
catch { }
44+
}
45+
return false;
46+
}
47+
48+
public static ISafeMemoryHandle CreateEx<TMem>(this TMem h, SizeT size) where TMem : ISafeMemoryHandleFactory => CreateSafeMemory<TMem>(size);
49+
50+
public static ISafeMemoryHandle CreateSafeMemory<TMem>(SizeT size) where TMem : ISafeMemoryHandleFactory =>
51+
#if NET7_0_OR_GREATER
52+
TMem.Create(size);
53+
#else
54+
typeof(TMem).GetMethod("Create", BindingFlags.Public | BindingFlags.Static, null, [typeof(SizeT)], null)?.
55+
Invoke(null, [size]) as ISafeMemoryHandle ?? throw new NotSupportedException($"Cannot create SafeMemoryHandle of type {typeof(TMem).Name}.");
56+
#endif
57+
58+
public static ISafeMemoryHandle CreateSafeMemory<TMem>(byte[] bytes) where TMem : ISafeMemoryHandleFactory =>
59+
#if NET7_0_OR_GREATER
60+
TMem.Create(bytes);
61+
#else
62+
typeof(TMem).GetMethod("Create", BindingFlags.Public | BindingFlags.Static, null, [typeof(byte[])], null)?.
63+
Invoke(null, [bytes]) as ISafeMemoryHandle ?? throw new NotSupportedException($"Cannot create SafeMemoryHandle of type {typeof(TMem).Name}.");
64+
#endif
65+
66+
public static T Mask<T>(this T value, int bitsToMask) where T :
67+
#if NET7_0_OR_GREATER
68+
IBinaryInteger<T> => (T.One << bitsToMask) - T.One & value;
69+
#elif NETFRAMEWORK || NETSTANDARD2_0_OR_GREATER
70+
unmanaged, IConvertible => (T)(object)((1UL << bitsToMask) - 1 & value.ToUInt64(null));
71+
#else
72+
unmanaged, IConvertible => (T)(dynamic)((1UL << bitsToMask) - 1 & value.ToUInt64(null));
73+
#endif
74+
75+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
76+
public static T MaskAndShift<T>(this T value, int bitsToMask, int shift = 0) where T :
77+
#if NET7_0_OR_GREATER
78+
IBinaryInteger<T> => value.Mask(bitsToMask) << shift;
79+
#elif NETFRAMEWORK || NETSTANDARD2_0_OR_GREATER
80+
unmanaged, IConvertible => (T)(object)(value.ToUInt64(null).Mask(bitsToMask) << shift);
81+
#else
82+
unmanaged, IConvertible => (T)(dynamic)(value.ToUInt64(null).Mask(bitsToMask) << shift);
83+
#endif
84+
85+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
86+
public static object AdjustBitness(this IntPtr ptr, Bitness bitness) => bitness == Bitness.X32bit ? ptr.ToInt32() : ptr.ToInt64();
87+
88+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
89+
public static Bitness Resolve(this Bitness b) => b == Bitness.Auto ? (Bitness)(IntPtr.Size * 8) : b;
90+
91+
public static object? ChangeType(object? value, Type dest) => value switch
92+
{
93+
null => null,
94+
IntPtr ip => Convert.ChangeType(ip.ToInt64(), dest),
95+
UIntPtr up => Convert.ChangeType(up.ToUInt64(), dest),
96+
_ when dest == typeof(IntPtr) => (IntPtr)(long)Convert.ChangeType(value, typeof(long)),
97+
_ when dest == typeof(UIntPtr) => (UIntPtr)(ulong)Convert.ChangeType(value, typeof(ulong)),
98+
_ => Convert.ChangeType(value, dest),
99+
};
100+
101+
public static Encoding ToEncoding(this StringEncoding stringEncoding) => stringEncoding switch
102+
{
103+
StringEncoding.ASCII => Encoding.ASCII,
104+
StringEncoding.Unicode => Encoding.Unicode,
105+
StringEncoding.UTF8 => Encoding.UTF8,
106+
StringEncoding.UTF32 => Encoding.UTF32,
107+
_ => Encoding.Default,
108+
};
109+
}
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
using Vanara.Extensions.Reflection;
2+
3+
namespace Vanara.Marshaler;
4+
5+
/// <summary>Indicates the data layout of the marshaled array.</summary>
6+
public enum ArrayLayout
7+
{
8+
/// <summary>Array of sequential values with a fixed size specified by SizeConst property. ArraySubType property should be used.</summary>
9+
ByValArray,
10+
11+
/*// OLE defined SAFEARRAY
12+
SafeArray,*/
13+
14+
/// <summary>
15+
/// Pointer to an array of sequential values with a size specified by another field. ArraySubType property should be used if marshaling
16+
/// needed for subtype. SizeFieldName must be used.
17+
/// </summary>
18+
LPArray,
19+
20+
/*// Pointer to an array of sequential values terminated by a null or default value. ArraySubType property should be used if marshaling needed for subtype.
21+
LPArrayNullTerm,*/
22+
23+
/// <summary>Pointer to an array of pointers to strings. SizeFieldName must be used.</summary>
24+
StringPtrArray,
25+
26+
/// <summary>
27+
/// Pointer to a null-terminated array of pointers to strings where the final pointer is null. Size is inferred from the array.
28+
/// </summary>
29+
StringPtrArrayNullTerm,
30+
31+
/// <summary>Pointer to a null-terminated array of pointers to strings. Size is inferred from the array.</summary>
32+
ConcatenatedStringArray,
33+
}
34+
35+
/// <summary>Specifies the number of bits in a pointer for a marshaled value.</summary>
36+
public enum Bitness
37+
{
38+
/// <summary>Selects the bit count automatically based on the current OS.</summary>
39+
Auto = 0,
40+
41+
/// <summary>Implies a 32-bit OS.</summary>
42+
X32bit = 32,
43+
44+
/// <summary>Implies a 64-bit OS.</summary>
45+
X64bit = 64,
46+
}
47+
48+
/// <summary>Determines the layout of the structure or class when marshaled.</summary>
49+
public enum LayoutModel
50+
{
51+
/// <summary>The layout matches the order of the fields in the structure or class.</summary>
52+
Sequential,
53+
54+
/// <summary>
55+
/// The layout is a union of all the fields in the structure or class. In this instance, all field values but one should be set to their
56+
/// default values.
57+
/// </summary>
58+
Union
59+
}
60+
61+
/// <summary>Identifies the type of encoding used to read and write binary representations of strings.</summary>
62+
public enum StringEncoding
63+
{
64+
/// <summary>The automatic encoding. Typically Unicode.</summary>
65+
Default = CharSet.Ansi,
66+
67+
/// <summary>ANSI encoding.</summary>
68+
[CorrespondingType(typeof(ASCIIEncoding))]
69+
ASCII = 11,
70+
71+
/// <summary>Unicode encoding.</summary>
72+
[CorrespondingType(typeof(UnicodeEncoding))]
73+
Unicode = CharSet.Unicode,
74+
75+
/// <summary>UTF-8 encoding.</summary>
76+
[CorrespondingType(typeof(UTF8Encoding))]
77+
UTF8 = 12,
78+
79+
/// <summary>UTF-32 encoding.</summary>
80+
[CorrespondingType(typeof(UTF32Encoding))]
81+
UTF32 = 13,
82+
}
83+
84+
/// <summary>Attribute that can be applied to classes and structures to indicate that they support custom marshaling.</summary>
85+
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
86+
public class MarshaledAttribute(LayoutModel layout = LayoutModel.Sequential) : Attribute()
87+
{
88+
private int pack = 0;
89+
90+
/// <summary>Gets or sets the bitness.</summary>
91+
/// <value>The bitness.</value>
92+
public Bitness Bitness { get; set; } = Bitness.Auto;
93+
94+
/// <summary>Gets the layout.</summary>
95+
/// <value>The layout.</value>
96+
public LayoutModel Layout { get; } = layout;
97+
98+
/// <summary>Gets or sets the pack.</summary>
99+
/// <value>The pack.</value>
100+
/// <exception cref="System.ArgumentOutOfRangeException">value</exception>
101+
public int Pack { get => pack; set => pack = value >= 0 && (value == 0 || value.IsPow2()) ? value : throw new ArgumentOutOfRangeException(nameof(value)); }
102+
103+
/// <summary>Gets or sets the size.</summary>
104+
/// <value>The size.</value>
105+
public int Size { get; set; } = 0;
106+
107+
/// <summary>Gets or sets the character encoding.</summary>
108+
/// <value>The character encoding.</value>
109+
public StringEncoding StringEncoding { get; set; } = StringEncoding.Unicode;
110+
111+
internal Encoding Encoding => StringEncoding.ToEncoding();
112+
}
113+
114+
/// <summary>A set of attributes to facilitate custom marshaling.</summary>
115+
public static class MarshalFieldAs
116+
{
117+
internal interface IMarshalAsAttr
118+
{ }
119+
120+
/// <summary>
121+
/// Attribute that can be applied to fields in a structure or class to indicate that the field is an array of values in a specified
122+
/// layout and size.
123+
/// </summary>
124+
/// <param name="layout"></param>
125+
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
126+
public class ArrayPtrAttribute(ArrayLayout layout) : Attribute, IMarshalAsAttr
127+
{
128+
/// <summary>Gets the layout of the marshaled array.</summary>
129+
/// <value>The array layout.</value>
130+
public ArrayLayout Layout { get; } = layout;
131+
132+
/// <summary>
133+
/// Gets or sets a value indicating whether to use the first element of this array as a field value. This follows the Win32 API
134+
/// pattern of using a fixed array with an <c>ANYSIZE</c> size value.
135+
/// </summary>
136+
/// <value><see langword="true"/> if the first element of this array is used as a field value; otherwise, <see langword="false"/>.</value>
137+
public bool SingleElementPlaceholder { get; set; } = false;
138+
139+
/// <summary>Gets or sets the size of the array as a constant value.</summary>
140+
/// <value>The size of the array as a constant value.</value>
141+
public int SizeConst { get; set; } = 0;
142+
143+
/// <summary>Gets or sets the name of the field that holds the size of the array.</summary>
144+
/// <value>The name of the field that holds the size of the array.</value>
145+
public string? SizeFieldName { get; set; } = null;
146+
147+
/// <summary>Gets or sets the character encoding.</summary>
148+
/// <value>The character encoding.</value>
149+
public StringEncoding StringEncoding { get; set; } = StringEncoding.Unicode;
150+
151+
internal Encoding Encoding => StringEncoding.ToEncoding();
152+
}
153+
154+
/// <summary>Attribute that can be applied to fields in a structure or class to indicate that the field is a bitfield.</summary>
155+
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
156+
public class BitFieldAttribute<T> : Attribute, IMarshalAsAttr where T : unmanaged, IConvertible
157+
{
158+
/// <summary>Initializes a new instance of the <see cref="BitFieldAttribute{T}"/> class.</summary>
159+
/// <param name="bitCount">The bit count.</param>
160+
/// <exception cref="System.ArgumentException">Generic type must be an integral. - T</exception>
161+
/// <exception cref="System.ArgumentOutOfRangeException">bitCount</exception>
162+
public BitFieldAttribute(int bitCount = 1)
163+
{
164+
if (!typeof(T).IsIntegral()) throw new ArgumentException("Generic type must be an integral.", nameof(T));
165+
BitCount = bitCount >= 1 && bitCount <= typeof(T).GetBitSize() ? bitCount : throw new ArgumentOutOfRangeException(nameof(bitCount));
166+
}
167+
168+
/// <summary>The number of bits in the field. The default is 1. The maximum value is bit size of <typeparamref name="T"/>.</summary>
169+
public int BitCount { get; }
170+
171+
/// <summary>If <see langword="true"/>, the field starts a new underlying field in the structure. The default is <see langword="false"/>.</summary>
172+
public bool StartNewField { get; set; } = false;
173+
}
174+
175+
/// <summary>
176+
/// Attribute that can be applied to fields in a structure or class to indicate that the field is a pointer to a string of a specified length.
177+
/// </summary>
178+
/// <param name="length">
179+
/// The length, in characters, of the string. Whether or not a NULL terminator is included is specified by <paramref name="nullTerm"/>..
180+
/// </param>
181+
/// <param name="nullTerm"><see langword="true"/> if the NULL terminator is included in <paramref name="length"/>; otherwise <see langword="false"/>.</param>
182+
/// <param name="stringEncoding">The character encoding of the string.</param>
183+
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
184+
public class FixedStringAttribute(int length, bool nullTerm = true, StringEncoding stringEncoding = StringEncoding.Unicode) : Attribute, IMarshalAsAttr
185+
{
186+
/// <summary>The length, in characters, of the string. Whether or not a NULL terminator is included is specified by <see cref="NullTerm"/>.</summary>
187+
public int Length { get; } = length;
188+
189+
/// <summary>Determines whether the NULL terminator is included in <see cref="Length"/>. The default is <see langword="true"/>.</summary>
190+
public bool NullTerm { get; } = nullTerm;
191+
192+
/// <summary>Gets or sets the character encoding.</summary>
193+
/// <value>The character encoding.</value>
194+
public StringEncoding StringEncoding { get; set; } = stringEncoding;
195+
196+
internal Encoding Encoding => StringEncoding.ToEncoding();
197+
}
198+
199+
/// <summary>
200+
/// Attribute that can be applied to fields in a structure or class to indicate that the field should be initialized with the native size
201+
/// of the parent structure or class or that indicates the size of the native structure or class on retrieval.
202+
/// </summary>
203+
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
204+
public class SizeOfAttribute : Attribute, IMarshalAsAttr
205+
{
206+
/// <summary>
207+
/// If <see langword="false"/>, the default, the field is initialized with the native size of the parent structure or class. When
208+
/// <see langword="true"/>, the size includes any additional bytes allocated for the last field, which must be a fixed array or fixed
209+
/// size string.
210+
/// </summary>
211+
public bool IncludeAnySizeAllocation { get; set; } = false;
212+
}
213+
214+
/// <summary>Attribute that can be applied to fields in a structure or class to indicate that the field is a pointer to a structure.</summary>
215+
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
216+
public class StructPtrAttribute : Attribute, IMarshalAsAttr
217+
{
218+
}
219+
220+
/// <summary>
221+
/// Attribute that can be applied to fields in a structure or class to indicate that the field is part of a union. If the <see
222+
/// cref="UnionId"/> is not set, then an id is generated for all union fields that also do not have a unionId specified.
223+
/// </summary>
224+
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
225+
public class UnionFieldAttribute(string unionId = "") : Attribute, IMarshalAsAttr
226+
{
227+
/// <summary>Gets or sets the union identifier.</summary>
228+
/// <value>The union identifier.</value>
229+
public string UnionId { get; set; } = unionId;
230+
}
231+
}

0 commit comments

Comments
 (0)