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;
}
}