【问题标题】:Managed and unmanaged struct are not the same size托管和非托管结构的大小不同
【发布时间】:2010-01-09 00:56:57
【问题描述】:

我正在通过 P/Invoke 使用非托管库,它使用三个结构(尽管它们都有相同的基本布局,所以我只发布一个):

struct Agraph_t {
    int tag:4;
    int kind:4;
    int handle:24;
    char **attr;
    char *didset;
    char *name;
    Agdata_t *univ;
    Dict_t *nodes, *inedges, *outedges;
    Agraph_t *root;
    Agnode_t *meta_node;
    Agproto_t *proto;
    Agraphinfo_t u;
};

由于我的包装器使用这些对象的方式,我必须将Agraph_t 中的结构称为IntPtrs。我添加了一些属性,使访问位域的值更容易。

public struct Agraph_t {
    public uint tag_kind_handle;
    public IntPtr attr;
    public string didset;
    public string name;
    public IntPtr univ;
    public IntPtr nodes, inedges, outedges;
    public IntPtr root;
    public IntPtr meta_node;
    public IntPtr proto;
    public IntPtr u;

    public uint Tag {
        get { return (tag_kind_handle & 15u); }
    }

    public uint Kind {
        get { return (tag_kind_handle & 240u) / 16; }
    }

    public uint Handle {
        get { return (tag_kind_handle & 4294967040u) / 256; }
    }
}

在做任何事情之前,我必须通过给它三个结构中每个结构的大小来初始化非托管库。

aginitlib(Marshal.SizeOf(typeof(Agraph_t)), ..., ...);

这样做时我没有收到错误,我可以很好地使用该库。但是,库的一部分使用非托管结构的大小自己调用 aginitlib(我无法控制)。那时,库警告我它已经用两种不同的大小进行了初始化,这使得它变得不稳定(在某些操作后抛出 AccessViolationExceptions)。

添加的属性是否已计入结构的大小并使其大于非托管版本?我会删除它们,看看会发生什么,但我的代码在很大程度上依赖于它们,这很困难。

我是否需要将StructLayoutAttributeSize 属性一起使用?唯一让我感到困惑的是IntPtrs。该库是严格 32 位的,所以我可以继续假设这些字段始终是 32 位吗?

【问题讨论】:

    标签: c# .net struct pinvoke marshalling


    【解决方案1】:

    区别在于u 的声明。非托管声明有这样的:

    Agraphinfo_t u;
    

    这意味着 Agraphinfo_t 在 Agraph_t 结构中内联分配。如果 Agraphinfo_t 的大小是 16 个字节,那么它会为 sizeof(Agraph_t) 贡献 16 个字节。

    但是,在您的托管声明中,您可以这样声明 u:

    public IntPtr u;
    

    这意味着在 Agraph_t 结构中分配了一个 指针。在 32 位系统上,这将为 sizeof(Agraph_t) 贡献 4 个字节。因此,为 Agraph_t 计算的两个大小不同步。

    要解决此问题,请声明与 Agraphinfo_t 的托管等效项并在 Agraph_t 中创建它的实例:

    public struct Agraphinfo_t
    {
      // fields go here as per unmanaged definition
    }
    
    public struct Agraph_t
    {
      // ....
      public Agraphinfo_t u;
    }
    

    【讨论】:

    • 哦,我明白了。我没有注意到缺少的*。这个解决方案的问题是 Agraphinfo_t 结构是 MASSIVE(就字段数量而言)并且根据库的编译方式使用不同的字段。我不知道使用了哪些编译器标志,所以我不确定要包含哪些字段。有什么建议吗?
    • 很遗憾没有。要使大小匹配,您需要在该插槽中提供与非托管 Agraphinfo_t 结构占用相同大小(在结构中)的 something。您可能可以使用字节数组(使用合适的编组标志以确保将其视为内联空间而不是引用),但这仍然需要您计算大小......尽管我想您可以通过编写一个用于打印sizeof(Agraphinfo_t) 的小 C 程序。没有承诺,但 - 对不起!
    • 这听起来可行。与此同时,我已经添加了这样的要求,即在调用aginitlib 时,应该先调用库的一部分,并删除从托管包装器中手动调用它的能力。它有效,但绝对不是我想保留的要求。
    【解决方案2】:

    IntPtr 在 32 位代码中为 4 字节宽,在 64 位模式下为 8 字节长。

    您可以使用 IntPtr.Size 属性确定大小,该属性报告当前运行时值的大小。

    至于你的其他问题,你真的应该使用StructLayoutAttribute 来确保托管和非托管结构以相同的方式在内存中布局,具有相同的填充和字段大小。

    您还需要从结构中删除属性,因为它们会影响结构在内存中的大小。

    【讨论】:

    • "您还需要从结构中删除属性,因为它们会影响内存中结构的大小。"这是不正确的。属性是元数据,占用每个类型的空间,而不是每个实例。 (如果属性有自己的支持字段,您的评论将是正确的,但它们没有:它们只是 tag_kind_handle 字段上的 getter 方法。)
    猜你喜欢
    • 1970-01-01
    • 2011-02-24
    • 1970-01-01
    • 2011-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-04
    相关资源
    最近更新 更多