mirror of https://github.com/dahall/Vanara.git
Added AnySizeStringMarshaler<T> which is an IVanaraMarshaler implementation to handle structures where the last field is a single character string array. Also added ability to use "*" as field name to indicate that string or array length should be determined by the amount of allocated memory.
parent
f13930bd63
commit
87664c5f97
|
@ -20,7 +20,7 @@ namespace Vanara.InteropServices
|
|||
/// <param name="value">The initial value of the structure, if provided.</param>
|
||||
/// <param name="sizeFieldName">
|
||||
/// The name of the field in <typeparamref name="T"/> that holds the length of the array. If <see langword="null"/>, the first
|
||||
/// public field will be selected.
|
||||
/// public field will be selected. If "*", then the array size will be determined by the amount of allocated memory.
|
||||
/// </param>
|
||||
/// <exception cref="InvalidOperationException">This class can only manange sequential layout structures.</exception>
|
||||
public SafeAnysizeStruct(in T value, string sizeFieldName = null) : base(baseSz)
|
||||
|
@ -34,7 +34,7 @@ namespace Vanara.InteropServices
|
|||
/// <param name="size">The size of the allocated memory in <paramref name="allocatedMemory"/> in bytes.</param>
|
||||
/// <param name="sizeFieldName">
|
||||
/// The name of the field in <typeparamref name="T"/> that holds the length of the array. If <see langword="null"/>, the first
|
||||
/// public field will be selected.
|
||||
/// public field will be selected. If "*", then the array size will be determined by the amount of allocated memory.
|
||||
/// </param>
|
||||
public SafeAnysizeStruct(IntPtr allocatedMemory, SizeT size, string sizeFieldName = null) : base(allocatedMemory, size, false)
|
||||
{
|
||||
|
@ -65,12 +65,14 @@ namespace Vanara.InteropServices
|
|||
#if !(NET20 || NET35 || NET40)
|
||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
protected override int GetArrayLength(in T local) => Convert.ToInt32(fiCount.GetValue(local));
|
||||
protected override int GetArrayLength(in T local) => fiCount is null ? GetArrLenFromSz() : Convert.ToInt32(fiCount.GetValue(local));
|
||||
|
||||
private int GetArrLenFromSz() => 1 + (Size - baseSz) / Marshal.SizeOf(elemType);
|
||||
|
||||
private void InitCountField(string sizeFieldName)
|
||||
{
|
||||
fiCount = string.IsNullOrEmpty(sizeFieldName) ? structType.GetOrderedFields().First() : structType.GetField(sizeFieldName, BindingFlags.Public | BindingFlags.Instance);
|
||||
if (fiCount is null) throw new ArgumentException("Invalid size field name.", nameof(sizeFieldName));
|
||||
fiCount = string.IsNullOrEmpty(sizeFieldName) ? structType.GetOrderedFields(binds).First() : structType.GetField(sizeFieldName, binds);
|
||||
if (fiCount is null && sizeFieldName != "*") throw new ArgumentException("Invalid size field name.", nameof(sizeFieldName));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,6 +83,8 @@ namespace Vanara.InteropServices
|
|||
/// <typeparam name="T">The type of the structure.</typeparam>
|
||||
public abstract class SafeAnysizeStructBase<T> : SafeMemoryHandle<CoTaskMemoryMethods>
|
||||
{
|
||||
internal const BindingFlags binds = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
|
||||
/// <summary>The base, unextended size of T.</summary>
|
||||
protected static readonly int baseSz;
|
||||
|
||||
|
@ -96,7 +100,7 @@ namespace Vanara.InteropServices
|
|||
if (!structType.IsLayoutSequential)
|
||||
throw new InvalidOperationException("This class can only manange sequential layout structures.");
|
||||
baseSz = Marshal.SizeOf(structType);
|
||||
fiArray = structType.GetOrderedFields().Last();
|
||||
fiArray = structType.GetOrderedFields(binds).Last();
|
||||
if (!fiArray.FieldType.IsArray)
|
||||
throw new ArgumentException("The field information must be for an array.", nameof(fiArray));
|
||||
elemType = fiArray.FieldType.FindElementType();
|
||||
|
@ -202,4 +206,112 @@ namespace Vanara.InteropServices
|
|||
return s.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A marshaler implementation of <see cref="IVanaraMarshaler"/> to marshal structures whose last field is a character array of length
|
||||
/// (1) and that uses a field to determine the length of the full string.
|
||||
/// <para>
|
||||
/// Use the cookie paramter of <see cref="AnySizeStringMarshaler{T}"/> to specify the name of the field in <typeparamref name="T"/> that
|
||||
/// specifies the length of the string in the last field of <typeparamref name="T"/> along with use indicators.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If the field specifies byte, rather than character length, follow the field name with a colon (:) followed by 'b' (for bytes) or 'c'
|
||||
/// (for characters).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If the field specifies a length that does NOT include the NULL terminator, follow the field name, colon (:), and type specifier by
|
||||
/// 'r' (for raw) or 'n' (for null-terminated).
|
||||
/// </para>
|
||||
/// <para>If the field name is "*", then the string length will be determined by the amount of allocated memory.</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The structure type to be marshaled.</typeparam>
|
||||
public class AnySizeStringMarshaler<T> : IVanaraMarshaler
|
||||
{
|
||||
private static readonly int charSz;
|
||||
private static readonly int baseSz;
|
||||
private readonly FieldInfo fiCount;
|
||||
private static readonly FieldInfo fiArray;
|
||||
private static int strOffset;
|
||||
private readonly bool cntIsBytes = false;
|
||||
private readonly bool cntInclNull = true;
|
||||
private readonly bool allMem = false;
|
||||
|
||||
static AnySizeStringMarshaler()
|
||||
{
|
||||
var structType = typeof(T);
|
||||
if (!structType.IsLayoutSequential)
|
||||
throw new InvalidOperationException("This class can only manange sequential layout structures.");
|
||||
baseSz = Marshal.SizeOf(structType);
|
||||
fiArray = structType.GetOrderedFields(SafeAnysizeStructBase<T>.binds).Last();
|
||||
if (fiArray.FieldType != typeof(string))
|
||||
throw new ArgumentException("The field information must be for a string.", nameof(fiArray));
|
||||
charSz = StringHelper.GetCharSize(typeof(T).StructLayoutAttribute.CharSet);
|
||||
strOffset = Marshal.OffsetOf(structType, fiArray.Name).ToInt32();// unchecked((uint)Marshal.ReadInt32(fiArray.FieldHandle.Value.Offset(4 + IntPtr.Size))) & 0x7FFFFFF;
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="AnySizeStringMarshaler{T}"/> class.</summary>
|
||||
/// <param name="cookie">
|
||||
/// The name of the field in <typeparamref name="T"/> that specifies the number of elements in the last field of <typeparamref name="T"/>.
|
||||
/// <para>
|
||||
/// If the field specifies byte, rather than character length, follow the field name with a colon (:) followed by 'b' (for bytes) or
|
||||
/// 'c' (for characters).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If the field specifies a length that does NOT include the NULL terminator, follow the field name, colon (:), and type specifier
|
||||
/// by 'r' (for raw) or 'n' (for null-terminated).
|
||||
/// </para>
|
||||
/// </param>
|
||||
public AnySizeStringMarshaler(string cookie)
|
||||
{
|
||||
if (string.IsNullOrEmpty(cookie))
|
||||
fiCount = typeof(T).GetOrderedFields(SafeAnysizeStructBase<T>.binds).First();
|
||||
else if (cookie == "*")
|
||||
allMem = true;
|
||||
else
|
||||
{
|
||||
var parts = cookie.Split(':');
|
||||
if (parts.Length == 2 && parts[1].Length >= 2)
|
||||
{
|
||||
cntIsBytes = parts[1][0] == 'b';
|
||||
cntInclNull = parts[1].Length > 1 && parts[1][1] == 'n';
|
||||
}
|
||||
fiCount = typeof(T).GetField(parts[0], SafeAnysizeStructBase<T>.binds);
|
||||
}
|
||||
if (fiCount is null && !allMem) throw new ArgumentException("Invalid string length field name.", nameof(cookie));
|
||||
}
|
||||
|
||||
SizeT IVanaraMarshaler.GetNativeSize() => baseSz;
|
||||
|
||||
SafeAllocatedMemoryHandle IVanaraMarshaler.MarshalManagedToNative(object managedObject)
|
||||
{
|
||||
var h = managedObject is null ? SafeCoTaskMemHandle.Null : new SafeCoTaskMemHandle(baseSz);
|
||||
var str = fiArray.GetValue(managedObject) as string;
|
||||
var len = allMem ? StringHelper.GetByteCount(str, true, typeof(T).StructLayoutAttribute.CharSet) : Convert.ToInt32(fiCount.GetValue(managedObject));
|
||||
var clen = cntIsBytes ? len / charSz : len;
|
||||
if (cntInclNull && !allMem) --clen;
|
||||
if (str is not null && str.Length > clen)
|
||||
str = str.Substring(0, clen);
|
||||
h.Size += StringHelper.GetByteCount(str, cntInclNull, typeof(T).StructLayoutAttribute.CharSet) - charSz;
|
||||
Marshal.StructureToPtr(managedObject, h, false);
|
||||
StringHelper.Write(str, ((IntPtr)h).Offset(strOffset), out _, cntInclNull, typeof(T).StructLayoutAttribute.CharSet);
|
||||
return h;
|
||||
}
|
||||
|
||||
object IVanaraMarshaler.MarshalNativeToManaged(IntPtr pNativeData, SizeT allocatedBytes)
|
||||
{
|
||||
if (pNativeData == IntPtr.Zero) return null;
|
||||
var o = Marshal.PtrToStructure(pNativeData, typeof(T));
|
||||
int len;
|
||||
if (allMem)
|
||||
len = (allocatedBytes - strOffset) / charSz;
|
||||
else
|
||||
{
|
||||
len = Convert.ToInt32(fiCount.GetValue(o));
|
||||
if (cntIsBytes) len /= charSz;
|
||||
if (cntInclNull) --len;
|
||||
}
|
||||
fiArray.SetValue(o, StringHelper.GetString(pNativeData.Offset(strOffset), len, typeof(T).StructLayoutAttribute.CharSet));
|
||||
return o;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,6 +16,22 @@ namespace Vanara.InteropServices.Tests
|
|||
[TestFixture()]
|
||||
public class AnysizeStructTests
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct TestStrStructU
|
||||
{
|
||||
public int iVal;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1)]
|
||||
public string array;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
||||
public struct TestStrStructA
|
||||
{
|
||||
public int iVal;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1)]
|
||||
public string array;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct TestStruct
|
||||
{
|
||||
|
@ -27,7 +43,7 @@ namespace Vanara.InteropServices.Tests
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void ConvertTest()
|
||||
public void StructNamedFieldTest()
|
||||
{
|
||||
var array = new[] { long.MinValue, 1L, long.MaxValue };
|
||||
var ts = new TestStruct { iVal = array.Length, array = array };
|
||||
|
@ -41,5 +57,116 @@ namespace Vanara.InteropServices.Tests
|
|||
Assert.That(tsout.Value.iVal, Is.EqualTo(3));
|
||||
Assert.That(((TestStruct)tsout).array, Is.EquivalentTo(array));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void StructAssumedFieldTest()
|
||||
{
|
||||
var array = new[] { long.MinValue, 1L, long.MaxValue };
|
||||
var ts = new TestStruct { iVal = array.Length, array = array };
|
||||
|
||||
using var mem = new SafeAnysizeStruct<TestStruct>(ts);
|
||||
Assert.That((int)mem.Size, Is.EqualTo(32));
|
||||
Assert.That(mem.Value.iVal, Is.EqualTo(3));
|
||||
Assert.That(mem.Value.array, Is.EquivalentTo(array));
|
||||
|
||||
var tsout = new SafeAnysizeStruct<TestStruct>(mem, mem.Size, null);
|
||||
Assert.That(tsout.Value.iVal, Is.EqualTo(3));
|
||||
Assert.That(((TestStruct)tsout).array, Is.EquivalentTo(array));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void StructNoFieldTest()
|
||||
{
|
||||
var array = new[] { long.MinValue, 1L, long.MaxValue };
|
||||
var ts = new TestStruct { iVal = array.Length, array = array };
|
||||
|
||||
using var mem = new SafeAnysizeStruct<TestStruct>(ts);
|
||||
Assert.That((int)mem.Size, Is.EqualTo(32));
|
||||
Assert.That(mem.Value.iVal, Is.EqualTo(3));
|
||||
Assert.That(mem.Value.array, Is.EquivalentTo(array));
|
||||
|
||||
var tsout = new SafeAnysizeStruct<TestStruct>(mem, mem.Size, "*");
|
||||
Assert.That(tsout.Value.iVal, Is.EqualTo(3));
|
||||
Assert.That(((TestStruct)tsout).array, Is.EquivalentTo(array));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void StringNamedFieldTest()
|
||||
{
|
||||
var ts = new TestStrStructU { iVal = str.Length + 1, array = str };
|
||||
var strOffset = Marshal.OffsetOf<TestStrStructU>(nameof(TestStrStructU.array)).ToInt64();
|
||||
|
||||
IVanaraMarshaler m = new AnySizeStringMarshaler<TestStrStructU>(nameof(TestStrStructU.iVal));
|
||||
using (var mem = m.MarshalManagedToNative(ts))
|
||||
{
|
||||
Assert.That((long)mem.Size, Is.GreaterThanOrEqualTo(strOffset + (str.Length + 1) * StringHelper.GetCharSize(CharSet.Unicode)));
|
||||
Assert.That(StringHelper.GetString(((IntPtr)mem).Offset(strOffset), CharSet.Unicode), Is.EqualTo(str));
|
||||
|
||||
var tsout = (TestStrStructU)m.MarshalNativeToManaged(mem, mem.Size);
|
||||
Assert.That(tsout.iVal, Is.EqualTo(ts.iVal));
|
||||
Assert.That(tsout.array, Is.EquivalentTo(str));
|
||||
}
|
||||
|
||||
m = new AnySizeStringMarshaler<TestStrStructU>(nameof(TestStrStructU.iVal) + ":br");
|
||||
using (var mem = m.MarshalManagedToNative(ts))
|
||||
{
|
||||
var newStr = str.Substring(0, ts.iVal / StringHelper.GetCharSize(CharSet.Unicode));
|
||||
Assert.That((long)mem.Size, Is.GreaterThanOrEqualTo(strOffset + ts.iVal));
|
||||
Assert.That(StringHelper.GetString(((IntPtr)mem).Offset(strOffset), CharSet.Unicode), Is.EqualTo(newStr));
|
||||
|
||||
var tsout = (TestStrStructU)m.MarshalNativeToManaged(mem, mem.Size);
|
||||
Assert.That(tsout.iVal, Is.EqualTo(ts.iVal));
|
||||
Assert.That(tsout.array, Is.EquivalentTo(newStr));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void StringNamedFieldATest()
|
||||
{
|
||||
var ts = new TestStrStructA { iVal = str.Length + 1, array = str };
|
||||
var strOffset = Marshal.OffsetOf<TestStrStructA>(nameof(TestStrStructA.array)).ToInt64();
|
||||
|
||||
IVanaraMarshaler m = new AnySizeStringMarshaler<TestStrStructA>(nameof(TestStrStructA.iVal));
|
||||
using (var mem = m.MarshalManagedToNative(ts))
|
||||
{
|
||||
Assert.That((long)mem.Size, Is.GreaterThanOrEqualTo(strOffset + (str.Length + 1)));
|
||||
Assert.That(StringHelper.GetString(((IntPtr)mem).Offset(strOffset), CharSet.Ansi), Is.EqualTo(str));
|
||||
|
||||
var tsout = (TestStrStructA)m.MarshalNativeToManaged(mem, mem.Size);
|
||||
Assert.That(tsout.iVal, Is.EqualTo(ts.iVal));
|
||||
Assert.That(tsout.array, Is.EquivalentTo(str));
|
||||
}
|
||||
|
||||
m = new AnySizeStringMarshaler<TestStrStructA>(nameof(TestStrStructA.iVal) + ":br");
|
||||
using (var mem = m.MarshalManagedToNative(ts))
|
||||
{
|
||||
var newStr = str.Substring(0, Math.Min(ts.iVal, str.Length));
|
||||
Assert.That((long)mem.Size, Is.GreaterThanOrEqualTo(strOffset + ts.iVal));
|
||||
Assert.That(StringHelper.GetString(((IntPtr)mem).Offset(strOffset), CharSet.Ansi), Is.EqualTo(newStr));
|
||||
|
||||
var tsout = (TestStrStructA)m.MarshalNativeToManaged(mem, mem.Size);
|
||||
Assert.That(tsout.iVal, Is.EqualTo(ts.iVal));
|
||||
Assert.That(tsout.array, Is.EquivalentTo(newStr));
|
||||
}
|
||||
}
|
||||
|
||||
const string str = "l;kajsdfl;kajsdl;fkj";
|
||||
|
||||
[Test]
|
||||
public void StringNoFieldTest()
|
||||
{
|
||||
var ts = new TestStrStructU { iVal = str.Length + 1, array = str };
|
||||
var strOffset = Marshal.OffsetOf<TestStrStructU>(nameof(TestStrStructU.array)).ToInt64();
|
||||
|
||||
IVanaraMarshaler m = new AnySizeStringMarshaler<TestStrStructU>("*");
|
||||
using var mem = m.MarshalManagedToNative(ts);
|
||||
|
||||
Assert.That((long)mem.Size, Is.GreaterThanOrEqualTo(strOffset + (str.Length + 1) * StringHelper.GetCharSize(CharSet.Unicode)));
|
||||
Assert.That(StringHelper.GetString(((IntPtr)mem).Offset(strOffset), CharSet.Unicode), Is.EqualTo(str));
|
||||
|
||||
var tsout = (TestStrStructU)m.MarshalNativeToManaged(mem, mem.Size);
|
||||
Assert.That(tsout.iVal, Is.EqualTo(ts.iVal));
|
||||
Assert.That(tsout.array, Is.EquivalentTo(str));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue