簡體   English   中英

C# Marshal.Copy Intptr 到 16 位托管無符號整數數組

[英]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 合規性的一個原則是整數類型SByteUInt16UInt32UInt64不會暴露給消費者,因為它們不受更廣泛的 CLR 兼容語言的支持——而且這種情況並不罕見:即使在 2022 年, Java 仍然沒有無符號類型

對於編組整數數組,重要的是元素的大小,而不是它們的符號性——這也適用於Array.BlockCopy所以我要么使用ByteInt16重載,然后再進行自己的轉換,或者我會使用如果允許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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM