带有nim的interop返回包含string/char*成员的Struct数组

本文关键字:char 成员 Struct 数组 string nim interop 返回 包含 带有 | 更新日期: 2023-09-27 18:20:25

从c#i互操作nim dll可以调用并执行下面的代码

如果我将添加另一个调用GetPacks()的函数(proc),并尝试在每个元素的buffer上回显,我可以正确地在C#控制台中看到输出但我无法按原样传输数据,我尝试了一切,但我无法完成任务

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/c++中执行相同操作时,我使用的类型是IntPtrchar*很高兴包含返回的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代码的c#签名

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

C#结构

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代码

它不一定是相同的代码,但效果相同,在c#中,我可以读取字符串的内容。目前,int是可读的,但字符串不是

编辑:

这就是我试图让事情变得简单的原因int成员的结构数组

更新:

问题似乎与我在windows操作系统中对nim的设置有关。我会在发现问题后尽快更新。

带有nim的interop返回包含string/char*成员的Struct数组

Nim中的string类型与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位字符,因为Nim倾向于使用UTF8。

考虑到这一点,您有几个选择:

1) 您可以向C#教授这种布局,并在正确的位置访问字符串缓冲区(以上注意事项适用)。可以在此处找到此方法的示例实现:https://gist.github.com/zah/fe8f5956684abee6bec9

2) 您可以为Nim代码中的buffer字段使用不同的类型。可能的候选者是ptr char或固定大小的array[char]。第一个将要求您放弃自动垃圾收集,并维护一些用于手动内存管理的代码。第二个将放弃一点空间效率,并对这些缓冲区的大小进行严格限制。

编辑:使用cstring看起来也很诱人,但它最终是危险的。当您将一个常规字符串分配给cstring时,结果将是一个正常的char *值,指向上面描述的Nim字符串的数据缓冲区。由于Nim垃圾收集器正确地处理指向已分配值的内部指针,因此只要cstring值被放置在像堆栈一样的跟踪位置,这将是安全的。但是,当您将其放置在对象中时,cstring将不会被跟踪,并且没有任何东西可以阻止GC释放内存,这可能会在C#代码中创建一个悬空指针。

尝试将结构更改为:

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