Маршалинг вложенных структур из C# в C++

У меня есть следующие типы в С++:

typedef void* keychain_handle;

typedef struct {
  const char* keyHolderName;
  unsigned int numKeys;
  key* keys;
} key_holder;

typedef struct {
  const char* keyName;
  unsigned int keySize;
} key;

И у меня есть следующие методы:

int createKeyChain(
  int id, 
  key_holder* keyHolders,
  keychain_handle* handle);

Мне нужен мой С# для создания держателей ключей с ключами, отправки его в код С++ и получения дескриптора.

Это мой код С#:

    /* Structs */
    [StructLayout(LayoutKind.Sequential)]
    public struct Key
    {
          public string key;
          public uint size;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct KeyHolder
    {
          public string name;
          public uint keys;
          public IntPtr keys;
    }


    /* Sync API */
    [DllImport("keys.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
    public static extern uint createKeyChain(uint id, KeyHolder[] keyHolders, ref IntPtr handle);


        Key[] myKeys = new Key[1];
        myKeys[0] = new Key { key = "tst", size = 5 };

        KeyHolder keyHolder = new DllWrapper.KeyHolder
        {
            name = "tst123",
            items = 1,
            keys = Marshal.AllocHGlobal(Marshal.SizeOf(typeof (Key))*myKeys.Length)
        };

        IntPtr c = new IntPtr(keyHolder.keys.ToInt32());

        for (int i = 0; i < myKeys.Length; i++)
        {
            Marshal.StructureToPtr(myKeys[i], c, false);
            c = new IntPtr(c.ToInt32() + Marshal.SizeOf(typeof(Key)));
        }

        Marshal.StructureToPtr(c, keyHolder.keys, false);

        IntPtr handle = IntPtr.Zero;

        var ret = createKeyChain(111, new []{keyHolder}, ref handle);

Все работает хорошо, за исключением внутренней строки внутри объекта Key, которая повреждена. Я подозреваю, что это StructureToPtr портит его. Как я могу показать строку на стороне С++?

Спасибо.


person Tsury    schedule 18.03.2015    source источник
comment
не могли бы вы показать, что вы пробовали на С#?   -  person nikis    schedule 18.03.2015
comment
пожалуйста, добавьте определения Key и KeyHolder   -  person nikis    schedule 18.03.2015
comment
@nikis, извините, я забыл добавить, добавил сейчас.   -  person Tsury    schedule 18.03.2015
comment
Это довольно противно. Потребуется совсем немного ручной сортировки. Я думаю, вам нужно маршалировать массив ключей как IntPtr, а затем маршалировать каждый ключ по отдельности. Потребуется один вызов Marshal.StringToHGlobalAnsi для каждого элемента массива. Является ли C++/CLI вариантом?   -  person David Heffernan    schedule 18.03.2015
comment
Да, я думаю, это вариант.   -  person Tsury    schedule 18.03.2015
comment
Атрибут [MarshalAs] в IntPtr недействителен, его необходимо удалить. И вы должны избавиться от m_Keys, ключевое слово private не влияет на маршалинг.   -  person Hans Passant    schedule 18.03.2015
comment
@Tsury, вы дважды объявили keys в своей структуре KeyHolder, m_keys избыточно   -  person nikis    schedule 18.03.2015
comment
Ага убрал проблемы в KeyHolder и теперь работает, только строки испорчены   -  person Tsury    schedule 18.03.2015
comment
Строка keyHolder в порядке, но строки внутренних ключей повреждены.   -  person Tsury    schedule 18.03.2015
comment
Я не думаю, что маршаллер сделает это за вас........   -  person David Heffernan    schedule 18.03.2015


Ответы (2)


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

[StructLayout(LayoutKind.Sequential)]
public struct _Key
{
    public IntPtr keyName;
    public uint keySize;
}

[StructLayout(LayoutKind.Sequential)]
public struct _KeyHolder
{
    public string name;
    public uint numKeys;
    public IntPtr keys;
}

public struct Key
{
    public string keyName;
    public uint keySize;
}

public static _KeyHolder CreateKeyHolder(string name, Key[] keys)
{
    _KeyHolder result;
    result.name = name;
    result.numKeys = (uint)keys.Length;
    result.keys = Marshal.AllocHGlobal(keys.Length * Marshal.SizeOf(typeof(_Key)));
    IntPtr ptr = result.keys;
    for (int i = 0; i < result.numKeys; i++)
    {
        _Key key;
        key.keyName = Marshal.StringToHGlobalAnsi(keys[i].keyName);
        key.keySize = keys[i].keySize;
        Marshal.StructureToPtr(key, ptr, false);
        ptr += Marshal.SizeOf(typeof(_Key));
    }
    return result;
}

public static void DestroyKeyHolder(_KeyHolder keyHolder)
{
    IntPtr ptr = keyHolder.keys;
    for (int i = 0; i < keyHolder.numKeys; i++)
    {
        _Key key = (_Key)Marshal.PtrToStructure(ptr, typeof(_Key));
        Marshal.FreeHGlobal(key.keyName);
        ptr += Marshal.SizeOf(typeof(_Key));
    }
    Marshal.FreeHGlobal(keyHolder.keys);
}
person David Heffernan    schedule 18.03.2015
comment
На самом деле функция принимает массив держателей ключей, но на самом деле проблема не в этом. Единственная проблема, которая у меня осталась, - это ключевая строка, не доходящая до стороны С++. Можете ли вы привести пример того, что вы только что объяснили? - person Tsury; 18.03.2015
comment
Я обновил свой пост, чтобы он содержал мой последний код, в котором я перебираю все ключи и упорядочиваю их. - person Tsury; 18.03.2015
comment
То, что вы предложили в отношении массива ключей, не работает: невозможно маршалировать «ключи» поля типа «KeyHolder»: недопустимая комбинация управляемого/неуправляемого типа (поля массивов должны быть соединены с ByValArray или SafeArray). - person Tsury; 18.03.2015
comment
Ok. Я удалил это. Я больше не могу писать код прямо сейчас. Если вы все еще застряли позже, у меня будет время. - person David Heffernan; 18.03.2015
comment
Спасибо за ваше время! - person Tsury; 18.03.2015
comment
Ну, я официально застрял. Я даже пытался изменить строку на один (!) символ, и она вышла поврежденной. - person Tsury; 18.03.2015
comment
Видимо, я не могу получить доступ к своему компьютеру удаленно. Утром первым делом попробую и отчитаюсь. Спасибо! - person Tsury; 19.03.2015

Вам все еще нужно указать .NET маршалировать строки как PChars:

[StructLayout(LayoutKind.Sequential)]
public struct Key
{
      [MarshalAs(UnmanagedType.LPStr)]
      public string key;
      public uint size;
}

[StructLayout(LayoutKind.Sequential)]
public struct KeyHolder
{
      [MarshalAs(UnmanagedType.LPStr)]
      public string name;
      public uint keyCount;

      public Key[] keys;
}
person Luaan    schedule 18.03.2015
comment
Даже если я добавлю этот атрибут, ключевая строка будет повреждена. Я думаю, что Marshal.StructureToPtr для всего объекта Key искажает его. - person Tsury; 18.03.2015
comment
@Tsury Ааа, конечно, вы не можете этого сделать, когда используете Marshal.StructureToPtr. Либо заставьте вызов работать полностью (например, Key[] вместо IntPtr), либо обработайте строки вручную - IntPtr key и Marshal.PtrToStringAnsi. - person Luaan; 18.03.2015
comment
Разве PtrToStringAnsi не наоборот? Мне нужно преобразовать строку в IntPtr, верно? - person Tsury; 18.03.2015
comment
@Tsury: О, я думал, ты хочешь их прочитать. Если вы хотите передать некоторые, Marshal.StringToHGlobalAnsi ваш человек. Однако убедитесь, что либо вы (обычно вы, сразу после вызова метода), либо библиотека освобождают память. - person Luaan; 20.03.2015