Управляемая и неуправляемая структуры имеют разный размер

Я работаю с неуправляемой библиотекой через 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 (у меня нет контроля над этим), используя размер неуправляемых структур. В этот момент библиотека предупреждает меня, что она была инициализирована с двумя разными размерами, что делает ее нестабильной (бросание AccessViolationException после определенных операций).

Учитываются ли добавленные свойства в размере структуры и делают ли ее больше, чем неуправляемая версия? Я бы удалил их и посмотрел, что произойдет, но мой код сильно зависит от них, что усложняет задачу.

Нужно ли использовать StructLayoutAttribute со свойством Size? Единственное, что меня смущает, это IntPtrs. Библиотека строго 32-битная, поэтому могу ли я смело предположить, что эти поля всегда будут 32-битными?


person David Brown    schedule 09.01.2010    source источник


Ответы (2)


Разница заключается в объявлении u. Неуправляемое объявление имеет следующее:

Agraphinfo_t u;

Это означает, что Agraphinfo_t размещается внутри структуры Agraph_t. Если размер Agraphinfo_t, скажем, 16 байт, то он добавляет 16 байт к sizeof(Agraph_t).

Однако в вашей управляемой декларации вы объявляете u следующим образом:

public IntPtr u;

Это означает, что в структуре Agraph_t выделяется указатель. В 32-битной системе это добавит 4 байта к sizeof(Agraph_t). Следовательно, два размера, вычисленные для 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;
}
person itowlson    schedule 09.01.2010
comment
Ага, понятно. Я не заметил пропавшего *. Проблема с этим решением заключается в том, что структура Agraphinfo_t является МАССИВНОЙ (с точки зрения количества полей) и использует разные поля в зависимости от того, как была скомпилирована библиотека. Я понятия не имею, какие флаги компилятора использовались, поэтому я не уверен, какие поля включать. Какие-нибудь советы? - person David Brown; 09.01.2010
comment
К сожалению нет. Чтобы размеры совпадали, вам потребуется предоставить в этом слоте что-то, занимающее тот же размер (в структуре), что и неуправляемая структура Agraphinfo_t. Возможно, вы сможете использовать массив байтов (с подходящими флагами сортировки, чтобы гарантировать, что он обрабатывается как встроенное пространство, а не как ссылка), но это все равно потребует от вас вычисления размера... хотя я думаю, вы могли бы сделать это, написав небольшая программа на C для печати файла sizeof(Agraphinfo_t). Но никаких обещаний — извините! - person itowlson; 09.01.2010
comment
Это звучит выполнимо. Тем временем я добавил требование, чтобы та часть библиотеки, которая выполняет собственный вызов aginitlib, вызывалась раньше всего, и удалил возможность вызывать ее вручную из управляемой оболочки. Это работает, но определенно не является требованием, которое я хотел бы сохранить. - person David Brown; 09.01.2010

IntPtr имеет ширину 4 байта в 32-битном коде и будет иметь длину 8 байтов в 64-битном режиме.

Размер можно определить с помощью свойства IntPtr.Size, которое сообщает размер значения для текущей среды выполнения.

Что касается вашего другого вопроса, вам действительно следует использовать StructLayoutAttribute, чтобы убедиться, что управляемые и неуправляемые структуры расположены в памяти одинаково, с одинаковыми размерами полей и отступов.

Вам также нужно будет удалить свойства из структуры, так как они будут влиять на размер структуры в памяти.

person LBushkin    schedule 09.01.2010
comment
Вам также нужно будет удалить свойства из структуры, так как они будут влиять на размер структуры в памяти. Это неправильно. Свойства являются метаданными и занимают место для каждого типа, а не для экземпляра. (Ваш комментарий был бы верным, если бы у свойств были свои собственные резервные поля, но это не так: это просто методы получения для поля tag_kind_handle.) - person itowlson; 09.01.2010