Interop с nim return Struct Array, содержащий строку /char * member

Взаимодействие nim dll с С# я мог вызвать и выполнить код ниже

если я добавлю еще одну функцию (proc), которая вызывает GetPacks(), и попытаться эхо на каждом элементе buffer, я мог бы правильно видеть вывод в консоли С# но я не мог передавать данные так, как есть, я пробовал все, но я не мог выполнить задачу.

proc GetPacksPtrNim(parSze: int, PackArrINOUT: var DataPackArr){.stdcall,exportc,dynlib.} =
  PackArrINOUT.newSeq(parSze)
  var dummyStr = "abcdefghij"
  for i, curDataPack in PackArrINOUT.mpairs:
    dummyStr[9] = char(i + int8'0')
    curDataPack = DataPack(buffer:dummyStr, intVal: uint32 i)

type
  DataPackArr = seq[DataPack]
  DataPack = object
    buffer: string
    intVal: uint32

когда я делаю то же самое в c/С++, я использую тип IntPtr или char* который с удовольствием будет содержать возвращаемый элемент buffer

EXPORT_API void __cdecl c_returnDataPack(unsigned int size, dataPack** DpArr)
{
    unsigned int dumln, Index;dataPack* CurDp = {NULL};
    char dummy[STRMAX];
    *DpArr = (dataPack*)malloc( size * sizeof( dataPack ));
    CurDp = *DpArr;
    strncpy(dummy, "abcdefgHij", STRMAX);

    dumln = sizeof(dummy);

    for ( Index = 0; Index < size; Index++,CurDp++)
    {
        CurDp->IVal = Index;
        dummy[dumln-1] = '0' + Index % (126 - '0');
        CurDp->Sval = (char*) calloc (dumln,sizeof(dummy));
        strcpy(CurDp->Sval, dummy);
    }

}

С# подпись для кода c выше

    [DllImport(@"cdllI.dll", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
    private static extern uint c_returnDataPack(uint x, DataPackg.TestC** tcdparr);

С# Struct

public unsafe static class DataPackg
{

   [StructLayout(LayoutKind.Sequential)]
    public struct TestC
    {
        public uint Id;
        public IntPtr StrVal;
    }

}

наконец, вызывает функцию следующим образом:

    public static unsafe List<DataPackg.TestC> PopulateLstPackC(int ArrL)
    {
        DataPackg.TestC* PackUArrOut;
        List<DataPackg.TestC> RtLstPackU = new List<DataPackg.TestC>(ArrL);
        c_returnDataPack((uint)ArrL, &PackUArrOut);
        DataPackg.TestC* CurrentPack = PackUArrOut;
        for (int i = 0; i < ArrL; i++, CurrentPack++)
        {

            RtLstPackU.Add(new DataPackg.TestC() { StrVal = CurrentPack->StrVal, Id = CurrentPack->Id });
        }
        //Console.WriteLine("Res={0}", Marshal.PtrToStringAnsi((IntPtr)RtLstPackU[1].StrVal));//new string(RtLstPackU[0].StrVal));
        return RtLstPackU;
    }

Как я могу создать аналогичный код c, как указано выше, из Nim?

не обязательно должен быть тот же самый код, но тот же эффект, который в С# я мог бы читать содержимое строки. на данный момент int читается, но строка не

Изменить:

Это то, что я пытался сделать простым struct array of int members

Update:

Кажется, что проблема связана с моими настройками nim в ОС Windows. я буду обновлять, как только я узнаю, что именно не так.

Ответ 1

Тип string в Nim не эквивалентен типу C const char*. Строки в Nim представлены в виде указателей, указывающих на кучу памяти, выделенной кучей, которая имеет следующий макет:

NI length;   # the length of the stored string
NI capacity; # how much room do we have for growth
NIM_CHAR data[capacity]; # the actual string, zero-terminated

Обратите внимание, что эти типы являются специфичными для архитектуры, и они действительно являются деталями реализации компилятора, которые могут быть изменены в будущем. NI - тип зацепления по умолчанию архитектуры и NIM_CHAR обычно эквивалентен 8-разрядному char, поскольку Nim склоняется к использованию UTF8.

Имея это в виду, у вас есть несколько вариантов:

1) Вы можете научить С# об этом макете и получить доступ к строковым буферам в правильном месте (применимы вышеописанные предостережения). Пример реализации этого подхода можно найти здесь: https://gist.github.com/zah/fe8f5956684abee6bec9

2) Вы можете использовать другой тип для поля buffer в коде Nim. Возможными кандидатами являются ptr char или фиксированный размер array[char]. Первый требует, чтобы вы отказались от автоматической сборки мусора и сохранили немного кода для ручного управления памятью. Второй из них немного снизит эффективность пространства, и он будет устанавливать жесткие ограничения на размер этих буферов.

EDIT: Использование cstring также может выглядеть заманчивым, но в конечном итоге это опасно. Когда вы присваиваете правильную строку cstring, результат будет нормальным значением char *, указывая на буфер данных строки Nim, описанной выше. Поскольку сборщик мусора Nim правильно управляет внутренними указателями на выделенные значения, это будет безопасно, если значение cstring помещено в отслеживаемое местоположение, такое как стек. Но когда вы помещаете его внутри объекта, cstring не будет прослеживаться, и ничто не мешает GC освобождать память, которая может создать обвисший указатель в вашем коде С#.

Ответ 2

Попробуйте изменить структуру:

public unsafe static class DataPackg
{
   [StructLayout(LayoutKind.Sequential)]
   public struct TestC
   {
      public uint Id;
      [MarshalAs(UnmanagedType.LPStr)]
      public String StrVal;
   }
}