Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copying byte array with Marshal.Copy from IntPtr using C# doesn't work

I'm using an unmanaged library, which generates grayscale images (about 100x200 pixels, more or less). An image is contained within a struct, which looks like this in C:

typedef struct abs_image {
    ABS_DWORD Width;
    ABS_DWORD Height;
    ABS_DWORD ColorCount;
    ABS_DWORD HorizontalDPI;
    ABS_DWORD VerticalDPI;
    ABS_BYTE ImageData[ABS_VARLEN];
} ABS_IMAGE
typedef unsigned int     ABS_DWORD;
typedef unsigned char     ABS_BYTE;

And here my C# wrapper struct:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct ABS_IMAGE {
    public uint Width;
    public uint Height;
    public uint ColorCount;
    public uint HorizontalDPI;
    public uint VerticalDPI;
    public IntPtr ImageData;
}

Grabbing the image and marshallign the ABS_IMAGE struct works just fine. In a previous version, I tried using a byte array with fixed length for ImageData, which crashed sometimes. This happened, I think, because the image size is not fix. Now I try to read the image byte array at later time, when I can calulate the real array length before. Here the relevant code:

ABS_Type_Defs.ABS_IMAGE img =
    (ABS_Type_Defs.ABS_IMAGE)Marshal.PtrToStructure(
    pImage,
    typeof(ABS_Type_Defs.ABS_IMAGE));

int length = ((int)img.Height - 1) * ((int)img.Width - 1);
byte[] data = new byte[length];

Marshal.Copy(img.ImageData, data, 0, length);

Now my problem: Every time I want to execute Marshal.Copy to read the image bytes, I get an AccessViolationException.

Does anyone have an idea?

like image 883
mrsubwoof Avatar asked May 25 '12 10:05

mrsubwoof


1 Answers

This is what is happening. Your struct is what is known as a variable length struct. The pixel data is containined inline in the struct, starting at the offset to ImageData.

typedef struct abs_image {
    ABS_DWORD Width;
    ABS_DWORD Height;
    ABS_DWORD ColorCount;
    ABS_DWORD HorizontalDPI;
    ABS_DWORD VerticalDPI;
    ABS_BYTE ImageData[ABS_VARLEN];
} ABS_IMAGE

Your API returns pImage which is an IntPtr that points to unmanaged data of type ABS_IMAGE. However, if you look at the native code then you will see that ABS_VARLEN is equal to 1. This is because the struct has to be defined statically at compile time. In reality the pixel data will have length determined by the height, width and colour count fields.

You can carry on using Marshal.PtrToStructure to get at most of the fields. But you can't get at the ImageData field that way. That's going to need a little more work.

Declare the struct like this instead:

[StructLayout(LayoutKind.Sequential)]
public struct ABS_IMAGE {
    public uint Width;
    public uint Height;
    public uint ColorCount;
    public uint HorizontalDPI;
    public uint VerticalDPI;
    public byte ImageData;
}

When you need to get the image data do this:

IntPtr ImageData = pImage + Marshal.OffsetOf(typeof(ABS_IMAGE), "ImageData");
Marshal.Copy(ImageData, data, 0, length);

If you are not yet on .net 4 then you need casting to make the arithmetic compile:

IntPtr ImageData = (IntPtr) (pImage.ToInt64() + 
    Marshal.OffsetOf(typeof(ABS_IMAGE), "ImageData").ToInt64());

Finally, I think you are calculating length incorrectly. Surely you need to use Height*Width. Also you have not accounted for the color depth. For example, 32 bit color will be 4 bytes per pixel.

like image 106
David Heffernan Avatar answered Oct 19 '22 09:10

David Heffernan