C# 联合结构编组

Posted

技术标签:

【中文标题】C# 联合结构编组【英文标题】:C# Union Structure Marshalling 【发布时间】:2012-06-10 22:12:19 【问题描述】:

我正在尝试将 Video4Linux 集成到我的托管应用程序中。事实上,我已经声明了所有必需的结构和相关的ioctl。在这个问题中,我提出了两个ioctlSetFormatGetFormat;虽然前者运行良好(就像我实际使用的其他十几个一样),但后者给我带来了糟糕的记忆行为。

GetFormat ioctl 实际上正在执行,但是一旦应用程序访问 ioctl 参数(调试器或我的应用程序本身),它总是会因以下堆栈而崩溃:

System.NullReferenceException: ...
at System.String.mempy4(...) in <filename unknown>:0
at System.String.memcpy(...) in <filename unknown>:0
at Derm.Platform.Video4Linux.ControlGetFormat(...) in <...>
...

我对@9​​87654321@ 进行了一些调查,但我再次无法理解为什么我的应用程序崩溃了。我怀疑这是因为结构内存布局,但我无法解释为什么 SetFormat 正在工作而 GetFormat 不工作(因为它们使用相同的参数)。

我必须 P/Invoke 一个接收/返回结构 v4l2_formatioctl 例程:

struct v4l2_format 
        enum v4l2_buf_type type;
        union 
                struct v4l2_pix_format          pix;     /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
                struct v4l2_pix_format_mplane   pix_mp;  /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
                struct v4l2_window              win;     /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
                struct v4l2_vbi_format          vbi;     /* V4L2_BUF_TYPE_VBI_CAPTURE */
                struct v4l2_sliced_vbi_format   sliced;  /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
                __u8    raw_data[200];                   /* user-defined */
         fmt;
;

struct v4l2_pix_format 
    __u32                   width;
    __u32                   height;
    __u32                   pixelformat;
    enum v4l2_field         field;
    __u32                   bytesperline;   /* for padding, zero if unused */
    __u32                   sizeimage;
    enum v4l2_colorspace    colorspace;
    __u32                   priv;           /* private data, depends on pixelformat */
;

我已经使用下面的表格引用了v4l2_format 的结构

[StructLayout(LayoutKind.Explicit, Pack = 4, CharSet = CharSet.Ansi)]
public struct Format

    public Format(BufferType bufferType)
    
        Type = bufferType;
        Pixmap = new PixmapFormat();
        RawData = new byte[RawDataLength];
    

    public Format(BufferType bufferType, PixmapFormat pixmapFormat)
    
        Type = bufferType;
        Pixmap = pixmapFormat;
        RawData = new byte[RawDataLength];
    

    [FieldOffset(0)]
    public BufferType Type;

    [FieldOffset(4)]
    public PixmapFormat Pixmap;

    [FieldOffset(4)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=RawDataLength)]
    public byte[] RawData;

    public const int RawDataLength = 200;


[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct PixmapFormat

    public PixmapFormat(uint width, uint height, PixelFormatCode pixelFormat)
    
        Width = width;
        Height = height;
        PixelFormat = pixelFormat;
        Field = Video4Linux.BufferField.Any;
        BytesPerLine = 0;
        SizeImage = 0;
        Colorspace = Video4Linux.PixelColorspace.None;
        Private = 0;
    

    public UInt32 Width;

    public UInt32 Height;

    public PixelFormatCode PixelFormat;

    public BufferField Field;

    public UInt32 BytesPerLine;

    public UInt32 SizeImage;

    public PixelColorspace Colorspace;

    public UInt32 Private;

最后,这里是调用 ioctl 的方法:

public static void ControlGetFormat(IntPtr fd, BufferType pixmapType, out PixmapFormat pixmapFormat)

    if (fd == IntPtr.Zero)
        throw new ArgumentException("invalid file descriptor", "fd");

    Format format = new Format(pixmapType);

    int result = IoCtrlGetFormat(fd, GetFormat.ControlCode, ref format);

    if (result < 0)
        ThrowExceptionOnError();

    pixmapFormat = format.Pixmap;


private static readonly IoCtrlRequest GetFormat = new IoCtrlRequest(IoCtrlDirection.Read | IoCtrlDirection.Write, 'V', 4, typeof(Format));

[DllImport("libc", EntryPoint = "ioctl", SetLastError = true)]
private static extern int IoCtrlGetFormat(IntPtr fd, Int32 code, ref Format argument);

public static void ControlSetFormat(IntPtr fd, BufferType pixmapType, ref PixmapFormat pixmapFormat)

    if (fd == IntPtr.Zero)
        throw new ArgumentException("invalid file descriptor", "fd");

    PixmapFormat pixmapFormatCopy = pixmapFormat;
    Format format = new Format(pixmapType, pixmapFormatCopy);

    int result = IoCtrlSetFormat(fd, SetFormat.ControlCode, ref format);

    if (result < 0)
        ThrowExceptionOnError();

    pixmapFormat = format.Pixmap;


private static readonly IoCtrlRequest SetFormat = new IoCtrlRequest(IoCtrlDirection.Read | IoCtrlDirection.Write, 'V', 5, typeof(Format));

[DllImport("libc", EntryPoint = "ioctl", SetLastError = true)]
private static extern int IoCtrlSetFormat(IntPtr fd, Int32 code, ref Format argument);

【问题讨论】:

当其中一个字段是对象时,我不认为与 Explicit 的联合可以工作。我会尝试使用顺序布局并声明数组字段。然后使用 Marshal 类读取/写入其他字段。 除了数组,一切都是结构。是否可以强制结构大小,不包括数组字段? 我很想将 v4l 结构包装成更易于 pinvoke 并具有在它们之间转换的非托管函数 @IanNorton 我已经尝试过,但我放弃了:V4L 层实际上是一个集成为插件的视频解复用器插件,我的插件系统不允许简单的非托管插件。事实上,我决定回到托管解决方案。 "是否可以强制结构大小,不包括数组字段?"对 StructLayout 属性使用 Size 参数 【参考方案1】:

通过将结构大小强制为实际大小(在我的情况下为204)并删除数组字段RawData(如David Heffernan 所建议的那样),问题已得到解决。

确实:

[StructLayout(LayoutKind.Explicit, Pack = 4, CharSet = CharSet.Ansi, Size = 204)]
public struct Format

    public Format(BufferType bufferType)
    
        Type = bufferType;
        Pixmap = new PixmapFormat();
    

    public Format(BufferType bufferType, PixmapFormat pixmapFormat)
    
        Type = bufferType;
        Pixmap = pixmapFormat;
    

    [FieldOffset(0)]
    public BufferType Type;

    [FieldOffset(4)]
    public PixmapFormat Pixmap;

当然,Marshal.SizeOf(typeof(Format)) == 204

【讨论】:

以上是关于C# 联合结构编组的主要内容,如果未能解决你的问题,请参考以下文章

创建要在 C# 中编组的 C++ Dll 的最佳实践 [关闭]

Marshal Union(C) 与包含 C# 中的数组的结构

将复杂结构编组到 C#

如何在 C# 中编组结构数组?

C++ 到 C# 回调结构编组

如何将 C 指针编组到 C# 结构数组