11

I have a IntPtr called rawbits, which points to a 10MB array of data, 16 bit values. I need to return a managed ushort array from that. The following code works but there is an extra BlockCopy I would like to get rid of. Marshal.Copy does not support ushort. What can I do? (FYI: the rawbits is filled in by a video framegrabber card into unmanaged memory)

    public const int width = 2056;
    public const int height = 2048;
    public const int depth = 2;
    public System.IntPtr rawbits;

public ushort[] bits()
{
    ushort[] output = new ushort[width * height];
    short[] temp = new short[width * height];
    Marshal.Copy(rawbits, temp, 0, width * height);
    System.Buffer.BlockCopy(temp, 0, output, 0, width * height * depth);
    return output;
}

The suggestions given in the following question did not help. (compiler error).

C# Marshal.Copy Intptr to 16 bit managed unsigned integer array

[BTW, the short array does have unsigned 16 bit data in it. The Marshal.Copy() does not respect the sign, and that is what I want. But I would rather not just pretend that the short[] is a ushort[] ]

Soleil
  • 6,404
  • 5
  • 41
  • 61
Dr.YSG
  • 7,171
  • 22
  • 81
  • 139

3 Answers3

13

Option 1 - call CopyMemory:

[DllImport("kernel32.dll", SetLastError = false)]
static extern void CopyMemory(IntPtr destination, IntPtr source, UIntPtr length);

public static void Copy<T>(IntPtr source, T[] destination, int startIndex, int length)
    where T : struct
{
    var gch = GCHandle.Alloc(destination, GCHandleType.Pinned);
    try
    {
        var targetPtr = Marshal.UnsafeAddrOfPinnedArrayElement(destination, startIndex);
        var bytesToCopy = Marshal.SizeOf(typeof(T)) * length;

        CopyMemory(targetPtr, source, (UIntPtr)bytesToCopy);
    }
    finally
    {
        gch.Free();
    }
}

Not portable, but has nice performance.


Option 2 - unsafe and pointers:

public static void Copy(IntPtr source, ushort[] destination, int startIndex, int length)
{
    unsafe
    {
        var sourcePtr = (ushort*)source;
        for(int i = startIndex; i < startIndex + length; ++i)
        {
            destination[i] = *sourcePtr++;
        }
    }
}

Requires unsafe option to be enabled in project build properties.


Option 3 - reflection (just for fun, don't use in production):

Marshal class internally uses CopyToManaged(IntPtr, object, int, int) method for all Copy(IntPtr, <array>, int, int) overloads (at least in .NET 4.5). Using reflection we can call that method directly:

private static readonly Action<IntPtr, object, int, int> _copyToManaged =
    GetCopyToManagedMethod();

private static Action<IntPtr, object, int, int> GetCopyToManagedMethod()
{
    var method = typeof(Marshal).GetMethod("CopyToManaged",
        System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
    return (Action<IntPtr, object, int, int>)method.CreateDelegate(
        typeof(Action<IntPtr, object, int, int>), null);
}

public static void Copy<T>(IntPtr source, T[] destination, int startIndex, int length)
    where T : struct
{
    _copyToManaged(source, destination, startIndex, length);
}

Since Marshal class internals can be changed, this method is unreliable and should not be used, though this implementation is probably the closest to other Marshal.Copy() method overloads.

max
  • 33,369
  • 7
  • 73
  • 84
  • You should used `fixed` when converting the array to a pointer – Cole Tobin Feb 19 '15 at 04:35
  • `fixed` cannot be used to create a generic `Copy()` method because `fixed(T* ptr = array)` would not compile. There is no generic constraint to allow that. Also `fixed` requires `unsafe` option, while using `GCHandle` does not. – max Feb 19 '15 at 05:02
  • Great answers. Any intuition as the the ranking in order of performance? [Not that I do this conversion to managed space in normal mode, but for debug it is useful to divert the 25 fps traffic to the host, instead of sending it to the GPU for processing). – Dr.YSG Feb 19 '15 at 15:04
  • Performance of all those options should be similar. My bet - `CopyMemory()` is the fastest, but you should definitely test that. – max Feb 19 '15 at 18:16
  • The `length` parameter of `CopyMemory` should be of type `uint`, not `UIntPtr`! Otherwise, the signature will be incorrect on 64-bit platforms – OronDF343 Dec 10 '17 at 08:31
0

Seems like you're stuck with either doing the extra conversion yourself (short[] to ushort[], the one you basically do already), or doing the mem copy yourself through unsafe keyword.

There's third option: create custom struct.

struct MyMagicalStruct
{
    // todo: set SizeConst correct
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=width*height)] 
    public ushort[] Test123;
}

You'd also have to use Marshal.PtrToStructure<MyMagicalStruct>(yourPtr)..

Erti-Chris Eelmaa
  • 25,338
  • 6
  • 61
  • 78
0

In modern .NET, you might be able to work in terms of spans instead of arrays, then things become interesting:

public unsafe Span<ushort> bits()
    => new Span<short>(rawbits.ToPointer(), width * height);

This is zero copy, but does not propagate the unsafe - consumers can access the span in managed / "safe" code. It works much like an array, include bounds elision etc - but can talk to unmanaged memory.

You can use ReadOnlySpan<T> if the caller doesn't need to (or shouldn't) change the values.

Note: if you construct a span using unsafe and get things wrong (the length, for example), then it can still explode on the caller, hence the "safe" rather than safe.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900