/// <summary> /// Marshals data from a managed list of strings to an unmanaged block of memory allocated by the <paramref name="memAlloc"/> method. /// </summary> /// <param name="values">The enumerated list of strings to marshal.</param> /// <param name="packing">The packing type for the strings.</param> /// <param name="memAlloc"> /// The function that allocates the memory for the block of strings (typically <see cref="Marshal.AllocCoTaskMem(int)"/> or <see cref="Marshal.AllocHGlobal(int)"/>. /// </param> /// <param name="bytesAllocated">The bytes allocated by the <paramref name="memAlloc"/> method.</param> /// <param name="charSet">The character set to use for the strings.</param> /// <param name="prefixBytes">Number of bytes preceding the trailing strings.</param> /// <returns> /// Pointer to the allocated native (unmanaged) array of strings stored using the <paramref name="packing"/> model and the character /// set defined by <paramref name="charSet"/>. /// </returns> public static IntPtr MarshalToPtr(this IEnumerable <string> values, StringListPackMethod packing, Func <int, IntPtr> memAlloc, out int bytesAllocated, CharSet charSet = CharSet.Auto, int prefixBytes = 0) { // Convert to list to avoid multiple iterations var list = values as IList <string> ?? (values != null ? new List <string>(values) : null); // Look at count and bail early if 0 var count = values?.Count() ?? 0; var chSz = StringHelper.GetCharSize(charSet); bytesAllocated = prefixBytes + (packing == StringListPackMethod.Concatenated ? chSz : IntPtr.Size); if (count == 0) { var ret = memAlloc(bytesAllocated); Marshal.Copy(new byte[bytesAllocated], 0, ret, bytesAllocated); return(ret); } // Check for empty and/or null strings if (packing == StringListPackMethod.Concatenated && list.Any(s => string.IsNullOrEmpty(s))) { throw new ArgumentException("Concatenated string arrays cannot contain empty or null strings."); } // Get size of output var sumStrLen = list.Sum(s => s == null ? 0 : s.Length + 1); bytesAllocated += sumStrLen * chSz; if (packing == StringListPackMethod.Packed) { bytesAllocated += (IntPtr.Size * count); } using (var ms = new MarshalingStream(memAlloc(bytesAllocated), bytesAllocated) { Position = prefixBytes, CharSet = charSet }) { if (packing == StringListPackMethod.Packed) { ms.Position += (count + 1) * IntPtr.Size; for (var i = 0; i < list.Count; i++) { ms.Poke(list[i] == null ? IntPtr.Zero : ms.Pointer.Offset(ms.Position), prefixBytes + (i * IntPtr.Size)); ms.Write(list[i]); } ms.Poke(IntPtr.Zero, prefixBytes + (count * IntPtr.Size)); } else { foreach (var s in list) { ms.Write(s); } ms.Write(""); } return(ms.Pointer); } }
/// <summary> /// Marshals data from a managed list of strings to an unmanaged block of memory allocated by the <paramref name="memAlloc"/> method. /// </summary> /// <param name="values">The enumerated list of strings to marshal.</param> /// <param name="packing">The packing type for the strings.</param> /// <param name="memAlloc"> /// The function that allocates the memory for the block of strings (typically <see cref="Marshal.AllocCoTaskMem(int)"/> or <see cref="Marshal.AllocHGlobal(int)"/>. /// </param> /// <param name="bytesAllocated">The bytes allocated by the <paramref name="memAlloc"/> method.</param> /// <param name="charSet">The character set to use for the strings.</param> /// <param name="prefixBytes">Number of bytes preceding the trailing strings.</param> /// <returns> /// Pointer to the allocated native (unmanaged) array of strings stored using the <paramref name="packing"/> model and the character /// set defined by <paramref name="charSet"/>. /// </returns> public static IntPtr MarshalToPtr(this IEnumerable <string> values, StringListPackMethod packing, Func <int, IntPtr> memAlloc, out int bytesAllocated, CharSet charSet = CharSet.Auto, int prefixBytes = 0) { // Bail early if empty if (values is null || !values.Any()) { bytesAllocated = prefixBytes + (packing == StringListPackMethod.Concatenated ? StringHelper.GetCharSize(charSet) : IntPtr.Size); var ret = memAlloc(bytesAllocated); ret.FillMemory(0, bytesAllocated); return(ret); } // Write to memory stream using (var ms = new NativeMemoryStream(1024, 1024) { CharSet = charSet }) { ms.SetLength(ms.Position = prefixBytes); if (packing == StringListPackMethod.Packed) { foreach (var s in values) { ms.WriteReference(s); } ms.WriteReference(null); } else { foreach (var s in values) { if (string.IsNullOrEmpty(s)) { throw new ArgumentException("Concatenated string arrays cannot contain empty or null strings."); } ms.Write(s); } ms.Write(""); } ms.Flush(); // Copy to newly allocated memory using memAlloc bytesAllocated = (int)ms.Length; var ret = memAlloc(bytesAllocated); ms.Pointer.CopyTo(ret, bytesAllocated); return(ret); } }
/// <summary> /// Marshals data from a managed array of strings to an unmanaged block of memory allocated by the <paramref name="memAlloc"/> method. /// </summary> /// <param name="values">The array of strings to marshal.</param> /// <param name="packing">The packing type for the strings.</param> /// <param name="memAlloc"> /// The function that allocates the memory for the block of strings (typically <see cref="Marshal.AllocCoTaskMem(int)"/> or <see cref="Marshal.AllocHGlobal(int)"/>. /// </param> /// <param name="bytesAllocated">The bytes allocated by the <paramref name="memAlloc"/> method.</param> /// <param name="charSet">The character set to use for the strings.</param> /// <param name="prefixBytes">Number of bytes preceding the trailing strings.</param> /// <returns> /// Pointer to the allocated native (unmanaged) array of strings stored using the <paramref name="packing"/> model and the character /// set defined by <paramref name="charSet"/>. /// </returns> public static IntPtr MarshalToPtr(this string[] values, StringListPackMethod packing, Func <int, IntPtr> memAlloc, out int bytesAllocated, CharSet charSet = CharSet.Auto, int prefixBytes = 0) => MarshalToPtr((IEnumerable <string>)values, packing, memAlloc, out bytesAllocated, charSet, prefixBytes);
/// <summary>Allocates from unmanaged memory sufficient memory to hold an array of strings.</summary> /// <param name="values">The list of strings.</param> /// <param name="packing">The packing type for the strings.</param> /// <param name="charSet">The character set to use for the strings.</param> /// <param name="prefixBytes">Number of bytes preceding the trailing strings.</param> /// <returns> /// <see cref="SafeMoveableHGlobalHandle"/> object to an native (unmanaged) array of strings stored using the <paramref /// name="packing"/> model and the character set defined by <paramref name="charSet"/>. /// </returns> public static SafeMoveableHGlobalHandle CreateFromStringList(IEnumerable <string> values, StringListPackMethod packing = StringListPackMethod.Concatenated, CharSet charSet = CharSet.Auto, int prefixBytes = 0) => new(InteropExtensions.MarshalToPtr(values, packing, mm.AllocMem, out _, charSet, prefixBytes, mm.LockMem, mm.UnlockMem), true);