6

Why does C# Marshal.Copy routine does not have any overload for copying from unmanaged memory pointer to 16 bit managed unsigned integer array?

ex:

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.

If there is no marshalling alternative, how do I copy unmanaged ushort array to managed ushort array?

Dwight Schrute
  • 359
  • 1
  • 5
  • 17

3 Answers3

3

Using unsafe code:

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);

Can be easily adapted to copy uint[] and ulong[] (adjust the number of bytes per element)

  • Just a note that `CopyMemory()` is basically a macro alias for `memcpy()` (AFAICT) so it one is using C++/CLI then using `memcpy` (or `memcpy_s`) would be quicker and easier. I know the question didn't specify C++/CLI but that how I got here. In either case, mem-copying into a managed array (using `pin_ptr` if in C++/CLI) is the only thing that seems to work and I guess is relatively efficient. – Maxim Paperno Jun 29 '22 at 18:29
1

I think Marshal doesn't have a Copy(IntPtr, UInt16[], Int32, Int32) overload because of CLS compliance (a tenet of CLS compliance is that the integer types SByte, UInt16, UInt32, and UInt64 are not exposed to consumers because they aren't supported by the broader gamut of CLR-compatible languages - and it's hardly rare: even in 2022 Java still doesn't have unsigned types.

With marshalling integer arrays, all that matters is the size of the elements, not their signed-ness - this also goes for Array.BlockCopy - so I would either use the Byte or Int16 overloads and do my own casting afterwards, or I would use pointers if unsafe is allowed.

Update for .NET Core and later:

In .NET 6 (and likely .NET Core too) the Marshal.Copy method's implementation is just this:

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);
}

So you could call-into this method using reflection, or just copy+paste it yourself.

If you can't use unsafe or .NET Core... try type-punning:

I wrote and tested this just now and it runs without issues on .NET Framework 4.8, .NET Core 3.1, and .NET 6, phew:

...but don't go using type-punning everywhere, it's only safe or workable in certain situations like these:

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;
}

Slow, but safe, way:

If you want something that just works and assuming you're not bothered about having a temporary second copy of the data then this would work:

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;
}
Dai
  • 141,631
  • 28
  • 261
  • 374
  • This removes all data, since it is all and not see my question: http://stackoverflow.com/questions/28595903/copy-from-intptr-16-bit-array-to-managed-ushort – Dr.YSG Feb 18 '15 at 23:22
  • Linq, really? It would be much faster to just loop over the unmanaged array and copy the data piecemeal. – Maxim Paperno Jun 29 '22 at 17:57
  • @MaximPaperno That's outside the scope of the OP's question. It's in my answer's code just for illustrative effect - and if anyone is going to copy-and-paste this answer then they've got bigger things to worry about than just a call to `.ToArray`. (Also, I realise my answer is incorrect w.r.t. `OfType`, I'll fix that now). – Dai Jun 29 '22 at 18:01
  • Removed, since you removed any Linq reference... :) Linq is like a slow old dog with a lot of tricks. `Span` looks promising, as others have suggested. But really this needs to be benchmarked to see if any of it is worth doing vs. just a loop copy (which, essentially is what any memcopy-like operation does also, though _maybe_ some implementations are optimized for certain situations). The `CopyMemory` hack is likely still most performant (or just `memcpy` from CLI). Cheers. – Maxim Paperno Jun 30 '22 at 04:13
-2

Probably because not all managed languages have unsigned types. I don't think that is a good reason, but it's a reasonable explanation.

A cast should work before the unsigned array parameter.

Copy(addr, (Int16[])someUnsignedArray, 0, len);
doug65536
  • 6,562
  • 3
  • 43
  • 53
  • VB.NET does have unsigned type support, you just need to specify the type-name rather than the language alias, e.g. `Dim foo As UInt16`. – Dai Jan 07 '14 at 20:57
  • @dai Ok, I'll remove that from my answer. – doug65536 Jan 07 '14 at 20:59
  • (Int16[]) gives a type conversion error. Here is what I am trying to do: http://stackoverflow.com/questions/28595903/copy-from-intptr-16-bit-array-to-managed-ushort – Dr.YSG Feb 18 '15 at 23:23
  • That should have worked.... https://github.com/dotnet/docs/issues/16176#issuecomment-594742104 – Ben Voigt Jun 29 '22 at 19:14