[英]C# Marshal.Copy Intptr to 16 bit managed unsigned integer array
为什么 C# Marshal.Copy 例程对于从非托管内存指针复制到 16 位托管无符号整数数组没有任何重载?
前任:
Copy(IntPtr, Byte[], Int32, Int32) Copies data from an unmanaged memory pointer to a managed 8-bit unsigned integer array.
Copy(IntPtr, Char[], Int32, Int32) Copies data from an unmanaged memory pointer to a managed character array.
Copy(IntPtr, Double[], Int32, Int32) Copies data from an unmanaged memory pointer to a managed double-precision floating-point number array.
Copy(IntPtr, Int16[], Int32, Int32) Copies data from an unmanaged memory pointer to a managed 16-bit signed integer array.
Copy(IntPtr, Int32[], Int32, Int32) Copies data from an unmanaged memory pointer to a managed 32-bit signed integer array.
Copy(IntPtr, Int64[], Int32, Int32) Copies data from an unmanaged memory pointer to a managed 64-bit signed integer array.
Copy(IntPtr, IntPtr[], Int32, Int32) Copies data from an unmanaged memory pointer to a managed IntPtr array.
Copy(IntPtr, Single[], Int32, Int32). Copies data from an unmanaged memory pointer to a managed single-precision floating-point number array.
如果没有编组替代方案,如何将非托管 ushort 数组复制到托管 ushort 数组?
使用不安全的代码:
public static unsafe void Copy(IntPtr ptrSource, ushort[] dest, uint elements) {
fixed(ushort* ptrDest = &dest[0]) {
CopyMemory((IntPtr)ptrDest, ptrSource, elements * 2); // 2 bytes per element
}
}
public static unsafe void Copy(ushort[] source, Intptr ptrDest, uint elements) {
fixed(ushort* ptrSource = &source[0]) {
CopyMemory(ptrDest, (Intptr)ptrSource, elements * 2); // 2 bytes per element
}
}
[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
static extern void CopyMemory(IntPtr Destination, IntPtr Source, uint Length);
可以很容易地适应复制 uint[] 和 ulong[] (调整每个元素的字节数)
我认为由于 CLS 合规性, Marshal
没有Copy(IntPtr, UInt16[], Int32, Int32)
重载(CLS 合规性的一个原则是整数类型SByte
、 UInt16
、 UInt32
和UInt64
不会暴露给消费者,因为它们不受更广泛的 CLR 兼容语言的支持——而且这种情况并不罕见:即使在 2022 年, Java 仍然没有无符号类型。
对于编组整数数组,重要的是元素的大小,而不是它们的符号性——这也适用于Array.BlockCopy
所以我要么使用Byte
或Int16
重载,然后再进行自己的转换,或者我会使用如果允许unsafe
,则指针。
.NET Core 及更高版本的更新:
在 .NET 6(也可能是 .NET Core) 中, Marshal.Copy
方法的实现是这样的:
private static unsafe void CopyToManaged<T>(IntPtr source, T[] destination, int startIndex, int length)
{
if (source == IntPtr.Zero) throw new ArgumentNullException(nameof(source));
if (destination is null) throw new ArgumentNullException(nameof(destination));
if (startIndex < 0) throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
if (length < 0) throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
void* sourcePtr = (void*)source;
Span<T> srcSpan = new Span<T>(sourcePtr, length);
Span<T> destSpan = new Span<T>(destination, startIndex, length);
srcSpan.CopyTo(destSpan);
}
所以你可以使用反射调用这个方法,或者只是自己复制+粘贴。
如果您不能使用unsafe
或 .NET Core... 尝试类型双关:
我刚刚编写并测试了它,它在 .NET Framework 4.8、.NET Core 3.1 和 .NET 6 上运行没有问题,唷:
...但不要到处使用类型双关语,它仅在某些情况下是安全或可行的,例如:
void Main()
{
// i.e. `calloc( 0x1000, 2 )`
IntPtr nativeArray = Marshal.AllocHGlobal( cb: 0x1000 * sizeof(UInt16) ); // 2KiB == 2048 bytes == 1024 shorts.
{
// TODO: Zero-out `nativeArray`, and all memory returned from `AllocHGlobal`, but it's fiddly, see here: https://stackoverflow.com/questions/1486999/how-to-zero-out-memory-allocated-by-marshal-allochglobal
}
UInt16[] u16Array = new UInt16[ 0x1000 ];
// Fill the array with incrementing numbers so we can ensure it's copied correctly:
for( Int32 i = 0; i < u16Array.Length; i++ ) u16Array[i] = (UInt16)(( Int16.MaxValue - 500 ) + i); // `( 32767 - 500 ) + n` ==> [ 32267, 32267, ..., 33267 ] which is between Int16.MaxValue and UInt16.MaxValue.
// Very-self documenting code:
ThisStructPunsTypes s = new ThisStructPunsTypes( u16Array );
Int16[] staticTypeInt16RealTypeUInt16 = s.AsInt16;
// Proof of Magic:
Debug.Assert( Object.ReferenceEquals( u16Array, staticTypeInt16RealTypeUInt16 ) == true, message: "These two array references, of different types, reference the same single underlying UInt16[]." );
// Copy values from `u16Array` into `nativeArray` using type-punning:
{
Marshal.Copy( source: s.AsInt16, startIndex: 0, destination: nativeArray, length: 0x1000 );
}
// Get the values back out directly into a new (separate) Int16[] buffer:
Int16[] outputInt16Array;
{
outputInt16Array = new Int16[ 0x1000 ];
Marshal.Copy( source: nativeArray, destination: outputInt16Array, startIndex: 0, length: 0x1000 );
}
// Get the values back out directly into a new (separate) UInt16[] buffer via type-punning:
UInt16[] outputUInt16Array;
{
outputUInt16Array = new UInt16[ 0x1000 ];
ThisStructPunsTypes again = new ThisStructPunsTypes( outputUInt16Array );
Int16[] typePunAgain = again.AsInt16;
Marshal.Copy( source: nativeArray, destination: typePunAgain, startIndex: 0, length: 0x1000 );
}
Debug.Assert( Object.ReferenceEquals( u16Array, outputInt16Array ) != true, message: "These are two separate array objects." );
Debug.Assert( Object.ReferenceEquals( outputInt16Array, outputUInt16Array ) != true, message: "These are two separate array objects." );
// Observe the values are copied fine from a UInt16 array into native memory, then into a separate and new Int16 array:
Debug.Assert( outputInt16Array[ 0] == 32267 );
Debug.Assert( outputInt16Array[ 1] == 32268 );
Debug.Assert( outputInt16Array[ 2] == 32269 );
Debug.Assert( outputInt16Array[ 499] == 32766 );
Debug.Assert( outputInt16Array[ 500] == 32767 );
Debug.Assert( outputInt16Array[ 501] == -32768 );
Debug.Assert( outputInt16Array[ 502] == -32767 );
Debug.Assert( outputInt16Array[4095] == -29174 );
Debug.Assert( outputInt16Array.Length == 4096 );
// Observe the values are copied fine from a UInt16 array into native memory, then into a separate and new UInt16 array:
Debug.Assert( outputUInt16Array[ 0] == 32267 );
Debug.Assert( outputUInt16Array[ 1] == 32268 );
Debug.Assert( outputUInt16Array[ 2] == 32269 );
Debug.Assert( outputUInt16Array[ 499] == 32766 );
Debug.Assert( outputUInt16Array[ 500] == 32767 );
Debug.Assert( outputUInt16Array[ 501] == 32768 );
Debug.Assert( outputUInt16Array[ 502] == 32769 );
Debug.Assert( outputUInt16Array[4095] == 36362 );
Debug.Assert( outputUInt16Array.Length == 4096 );
}
[StructLayout( LayoutKind.Explicit )]
readonly struct ThisStructPunsTypes
{
public ThisStructPunsTypes( UInt16[] uint16Array )
{
this.AsInt16 = default!;
this.AsUInt16 = uint16Array ?? throw new ArgumentNullException(nameof(uint16Array));
}
[FieldOffset( 0 )]
public readonly Int16[] AsInt16;
[FieldOffset( 0 )]
public readonly UInt16[] AsUInt16;
}
缓慢但安全的方式:
如果您想要一些可以正常工作的东西,并且假设您不介意拥有临时的第二份数据副本,那么这将起作用:
static UInt16[] GetUInt16Array( IntPtr unmanagedArray, Int32 count )
{
if( unmanagedArray == default ) throw new ArgumentException( ... );
if( count < 0 ) throw new ArgumentOutOfRangeException( ... );
//
Int16[] signedInt16Array = new Int16[ count ];
Marshal.Copy( source: unmanagedArray, destination: signedInt16Array, startIndex: 0, length: count );
Int16[] outputUnsignedInt16Array = new UInt16[ count ];
Array.Copy( sourceArray: signedInt16Array, destinationArray: outputUnsignedInt16Array, length: count );
return outputUnsignedInt16Array;
}
可能是因为并非所有托管语言都有无符号类型。 我不认为这是一个很好的理由,但这是一个合理的解释。
强制转换应该在无符号数组参数之前工作。
Copy(addr, (Int16[])someUnsignedArray, 0, len);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.