diff --git a/PInvoke/Shared/InteropServices/SafeAnysizeStruct.cs b/PInvoke/Shared/InteropServices/SafeAnysizeStruct.cs
index 149475d8..31b9efe0 100644
--- a/PInvoke/Shared/InteropServices/SafeAnysizeStruct.cs
+++ b/PInvoke/Shared/InteropServices/SafeAnysizeStruct.cs
@@ -20,7 +20,7 @@ namespace Vanara.InteropServices
/// The initial value of the structure, if provided.
///
/// The name of the field in that holds the length of the array. If , 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.
///
/// This class can only manange sequential layout structures.
public SafeAnysizeStruct(in T value, string sizeFieldName = null) : base(baseSz)
@@ -34,7 +34,7 @@ namespace Vanara.InteropServices
/// The size of the allocated memory in in bytes.
///
/// The name of the field in that holds the length of the array. If , 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.
///
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
/// The type of the structure.
public abstract class SafeAnysizeStructBase : SafeMemoryHandle
{
+ internal const BindingFlags binds = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
+
/// The base, unextended size of T.
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;
}
}
+
+ ///
+ /// A marshaler implementation of 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.
+ ///
+ /// Use the cookie paramter of to specify the name of the field in that
+ /// specifies the length of the string in the last field of along with use indicators.
+ ///
+ ///
+ /// 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).
+ ///
+ ///
+ /// 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).
+ ///
+ /// If the field name is "*", then the string length will be determined by the amount of allocated memory.
+ ///
+ /// The structure type to be marshaled.
+ public class AnySizeStringMarshaler : 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.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;
+ }
+
+ /// Initializes a new instance of the class.
+ ///
+ /// The name of the field in that specifies the number of elements in the last field of .
+ ///
+ /// 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).
+ ///
+ ///
+ /// 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).
+ ///
+ ///
+ public AnySizeStringMarshaler(string cookie)
+ {
+ if (string.IsNullOrEmpty(cookie))
+ fiCount = typeof(T).GetOrderedFields(SafeAnysizeStructBase.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.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;
+ }
+ }
}
\ No newline at end of file
diff --git a/UnitTests/PInvoke/Shared/InteropServices/AnysizeStructTests.cs b/UnitTests/PInvoke/Shared/InteropServices/AnysizeStructTests.cs
index f1443626..c3f34c0b 100644
--- a/UnitTests/PInvoke/Shared/InteropServices/AnysizeStructTests.cs
+++ b/UnitTests/PInvoke/Shared/InteropServices/AnysizeStructTests.cs
@@ -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(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(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(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(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(nameof(TestStrStructU.array)).ToInt64();
+
+ IVanaraMarshaler m = new AnySizeStringMarshaler(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(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(nameof(TestStrStructA.array)).ToInt64();
+
+ IVanaraMarshaler m = new AnySizeStringMarshaler(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(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(nameof(TestStrStructU.array)).ToInt64();
+
+ IVanaraMarshaler m = new AnySizeStringMarshaler("*");
+ 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));
+ }
}
}
\ No newline at end of file