【问题标题】:C# calling deviceIOControl with complex structuresC# 调用具有复杂结构的 deviceIOControl
【发布时间】:2014-08-15 07:12:42
【问题描述】:

所以我正在尝试编写一个 C# 包装器来与我们的设备驱动程序之一对话。 (创建单元测试)驱动程序是新的,但针对旧的 c++ 标头进行编码,因此定义了结构布局,并且不能真正改变。

所以我已经复制了设备期望 DeviceIOControl 传入的 c++ 结构。

更新 #3 - 将代码更改为具有相同问题的演示代码。还要清理问题以对其他人更有用,请参阅下面的答案

[StructLayout(LayoutKind.Sequential, Pack=1)]
public class Points
{
    public int id;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst=10)]  
    public int[] x = new int[10];
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=10)]
    public int[] y = new int[10];
};

[StructLayout(LayoutKind.Sequential, Pack=1)]
public class Shape
{
    public int name;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=10)]
    public Points[] p = new Points[10];
};

[StructLayout(LayoutKind.Sequential,Pack1)]
public class GeoShape:Shape
{
    public int top;
    public int left;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=10)]
    public int[] v = new int[10];
};

我对@9​​87654323@ 的调用失败,因为在驱动程序端它检查传入缓冲区的大小。在C# 端,我的对象太小,因为Marshal.SizeOf() 返回52 作为大小,当它应该是852,如果我将Size= 添加到StructLayout 属性,该函数将“通过”,但我相当确定数据没有被正确传递。

我很确定问题出在public Points[] p = new Points[10]; 我认为 Marshal.StructToPtr() 没有正确编组它,因为它本质上是一个多维数组。

所以我想我的问题是这甚至可能吗? 似乎 C# 可能足够聪明,知道如何在内存中为该结构数组创建适量的空间.. 但也许不是?

我认为“可行”的替代方案。

  1. 编写自定义 serailizer,将对象转换为 byte[] 并返回,元数据为零。 - 不理想。

  2. 是否可以编写一个混合的 clr c++ dll 并尝试将其用作楔子。但是我担心的是,我是否会遇到同样的问题,但只是在托管 c++ 中?或者即使在混合模式下,我也必须编写一个托管类来包装非托管对象以在 c# 中使用它。但是问题变成了如何将它传递给 deviceIOcontrol,如果我从 c# 中执行它,那么当前的问题是否会尝试正确编组?或者,如果我将它传递给调用 DeviceIOControl 的 C++ 调用,那么我需要知道如何获取传入的每个管理对象的未管理类型。

  3. 只需编写创建对象并调用 deviceIOControl 的 c++ 函数,更不用说参数可能会失控?

  4. 放弃,全部用 C++ 完成,我实际上正在尝试为我的硬件编写单元测试,而 VS 中较新的 cpp 单元测试确实可以很好地集成...

我也看到了这个较早的问题,并尝试了一下,但我认为我的场景有点不同。 Un-/Marshalling nested structures containing arrays of structures

 struct Points
{
    int id;
    int x[10];
    int y[10];
};


struct Shape
{
    int name;
    Points p[10];
};

struct GeoShape :Shape
{
    int top;
    int left;
    int v[10];
};

更新 2 我应该澄清一下,我正在尝试向驱动程序发送一个对象,而不是收到一个返回(至少还没有)

这就是我所说的。

public static bool SetObject(SafeFileHandle device, DeviceControlCode ioctlCode, Object obj)
        {
            int size = Marshal.SizeOf(obj.GetType());
            IntPtr ptr = Marshal.AllocHGlobal(size);
            Marshal.StructureToPtr(obj, ptr, false);

            // call the dviceIOControl method
            return Control(device, ref ioctlCode, ptr, size, IntPtr.Zero, 0);
        }

【问题讨论】:

  • 鉴于您没有发布 C++ 声明,因此无法判断您哪里出错了。当然你应该考虑C++/CLI,它可以直接读取.h文件。以非零的几率,您了解它足以编写正确的 C# 声明。
  • 好吧,我很确定问题是我的 C# 结构大小不一样,当我用我的结构调用 C# marshal.Sizeof() 时,我得到的大小是 156,而它应该是 7776。基本上 c# 不知道如何正确编组结构。
  • @Han Passant 我玩弄了这个无限位,但我不认为 C# 可以看到那些非托管结构,如果它看到了,我将如何调用 DeviceIOControl 而无需再次执行某种 Marshal .
  • 想通了!很快就会发布答案。

标签: c# c++ pinvoke marshalling deviceiocontrol


【解决方案1】:

我真的不知道标题上的内容,但作为起点,请考虑阅读 this 关于何时使用 structclass

作为提醒...您确定 Pack = 1 吗?你有#pragma 将它设置为 1 吗?

如果您提供相关的 .h 代码,则更容易检查可能出现的问题。无论如何,有了可用的信息,我会这样做:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct VENUS_FORMAT4
{
    public uint Top;    
    public uint Left;                                                           
    public uint Rows;                                                           
    public uint Columns;                                                        
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = Constants.MAX_CD_ROWS)]
    public uint[] V65Rows;                    
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = Constants.MAX_CD_COLS_DD2)]
    public uint[] CDCols;                 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = Constants.MAX_DD_SECTIONS)]
    public uint[] DDSections;             
}

其余部分与上述基本相同,除了在 VENUS_VM4_DEVICE_FORMAT4IL 中,您必须“复制”字段,因为在使用结构时无法继承(在 C# 中(类型值))。

另外,如果在 C++ 端有 unions,这将不起作用,您应该使用 LayoutKind.ExplicitFieldOffset

【讨论】:

  • 我可以从类更改为结构。我不认为这是一个问题?我还应该注意,我收到了另一个 IOCTL 调用,但它的结构要简单得多。我是否 100% 确定 Pack,不,但它以前对我有用。我想我是无工会的。我将不得不考虑发布标题内容,它相当冗长和混乱。
  • @NateGates,您只能发布这些结构相关的代码作为开始。它有任何#pragma 包吗?因为它之前可能已经奏效了,但它取决于 c++ 端的打包对齐方式。
  • 您是在问 c++ 是否有编译指示包?我仍然认为问题在于我的 Marshal.SizeOf 返回(156 vs. 7776)与 c++ 代码不同的结构大小。我的驱动程序使用该大小来验证对象是结构。我相当确定 C# 只是不知道如何正确编组我的结构。
  • 据我所知,我们没有在头文件中做任何编译指示包。
  • @NateGates,除了我写的内容之外,我真的没有看到任何问题。您可能想像 Hans Passant 建议的那样考虑 c++\cli。
【解决方案2】:

因为您将结构嵌入到结构中,所以您将需要使用 C# 结构而不是类,以便您的结构是值而不是引用。这意味着您将不得不放弃使用继承。你可以像这样翻译你的结构:

public struct Points
{
    int id;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=10)]
    int[] x;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=10)]
    int[] y;
};

public struct Shape
{
    int name;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=10)]
    Points[] p;
};

public struct GeoShape
{
    int name;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=10)]
    Points[] p;
    int top;
    int left;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=10)]
    int[] v;
};

使用这些定义,Marshal.SizeOf(typeof(GeoShape)) 的计算结果为 892。请注意,尽管您声称正确的值是 852,但事实并非如此。你的 C++ 结构的大小是 892。

如果你想避免在GeoShape 中重复Shape 的定义,你可以嵌入它:

public struct GeoShape
{
    Shape shape;
    int top;
    int left;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=10)]
    int[] v;
};

【讨论】:

    【解决方案3】:

    我终于能够通过以下两件事来修复它。

    1. 我将我的第一个类更改为结构并使用fixed 关键字。
    2. 我删除了StructLayoutAttribute 上的pack=1 值。

      [StructLayout(LayoutKind.Sequential)] public struct Points { public int id; public unsafe fixed int x[10]; public unsafe fixed int y[10]; //[MarshalAs(UnmanagedType.ByValArray, SizeConst=10)] //public int[] x = new int[10]; //[MarshalAs(UnmanagedType.ByValArray, SizeConst=10)] //public int[] y = new int[10]; };

    【讨论】:

    • 这里不需要固定。没有它很容易做到,让您的生活更轻松。
    • 修复是我让它工作的唯一方法。你还有什么建议?
    • 好吧,以这种态度我放弃了。帮助这样开始的人没有乐趣。按照你的方式去做。
    • 摆脱 Pack,使用 struct 和 UnmanagedType.ByValArray。布局与固定相同。
    • 是的。你需要的是价值观而不是参考。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-03-31
    • 1970-01-01
    • 2018-01-04
    • 2017-03-14
    • 2019-09-17
    • 2023-03-28
    • 1970-01-01
    相关资源
    最近更新 更多