【问题标题】:Passing a struct containing an int array of unknown size from c# to c and back将包含未知大小的 int 数组的结构从 c# 传递给 c 并返回
【发布时间】:2013-06-13 10:01:27
【问题描述】:

我开始努力解决这个问题。我已经搜索并寻求帮助,但我尝试过的一切似乎都不起作用。我显然做错了什么。

无论如何 - 我有一个 c# 结构定义为:

public struct TestStruct
[StructLayout(LayoutKind.Sequential)]
{
   public int num;
   public IntPtr intArrayPtr;
}

在我的 c# 代码的主体中,我有:

public class Testing
{
   [DllImport("testing.dll")]
   static extern void Dll_TestArray(out IntPtr intArrayPtr);

   public GetArray()
   {
      IntPtr structPtr = IntPtr.Zero;
      TestStruct testStruct;
      structPtr = Marshal.AllocHGlobal(Marshal.SizeOf(testStruct));
      Marshal.StructureToPtr(testStruct, structPtr, false);

      Dll_TestArray(structPtr);

      testStruct = (TestStruct) Marshal.PtrToStructure(structPtr, typeof(TestStruct));
   }
}

现在是 c++ 部分。从结构开始:

struct TestStruct
{
   public:
     int  num;
     int* intArray;
}

现在是函数:

extern "C" __declspec(dllexport) void Dll_TestArray(TestStruct *&testStruct)
{
   int num = 15;
   testStruct->num = num;
   testStruct->intArray = new int[num];

   for (int i = 0; i < num; i++)
     testStruct->intArray[i] = i+1;  
}

所以 - 我遇到的问题是,当我将结构恢复到 c# 时,我的结构不是它应该的样子。我可以看到 num 字段已正确填写:它显示 15。但是,仍然设置为零的是 IntPtr。在c++中完成的数组创建并没有传递到c#中。

如果我尝试退后一步,并返回到 dll 函数,我可以看到数组创建正常并且仍然​​保留信息。

因此,结构中的 c# intptr 没有设置为在 c++ 中创建的指针(如果有意义的话)。

所以我的问题确实是 - 如何使这项工作正常工作?

我希望能够从 dll 中返回,该结构包含我需要的所有信息。也就是说,在这个例子中,元素的数量和指向数组的指针。这样,我就可以在 intptr 上执行 Marshal.Copy 以获取数组。

如果有其他方法可以做到这一点,我很乐意这样做。我已经尝试了几种方法,但无济于事。这包括在 c# 中尝试以下结构(其中包含 int 数组,而不是 intptr):

public struct TestStruct
{
   public int num;

   // i have tried various methods to marshal this- eg:
   // [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_I4]
   public int[] intArray;
}

我还尝试通过引用本身而不是 intptr 来传递结构。 对此问题的任何帮助将不胜感激。我无法更改 c++ 代码,但可以更改 c# 代码。

【问题讨论】:

  • 这是你的真实代码吗? structPtr. falseTestStruct *&amp;testStruct 是什么?另外,如果intArrayPtr 定义为out,为什么在调用Dll_TestArray 之前先填充结构?请发布真实代码(剪切和粘贴是个好主意)。
  • 这是非常真实的代码,是的。它不是特别有用,我只是想让一些非常基本的工作,所以我可以做一些更高级的工作。我已经编辑了一些错别字。我之前没有使用 out 参数,但是当我在某处读到这将是一件好事时,我开始这样做了。所以如果我们使用 out 参数,这就是为什么我使用 TestStruct *&testStruct 我已经重命名了其中的一些变量,以便它们对你更有意义。
  • 我完全没有c#经验,所以对于任何愚蠢的错误表示歉意。
  • 你有更大的问题,这段代码中有一个重要的内存管理问题。需要释放数组以防止失控的内存泄漏。你不能,你不能在 C# 中调用 delete[] 运算符。需要用 C++/CLI 编写的包装器。同样重要的是,此 C++ 代码使用完全相同的编译器编译并使用 /MD 选项以便共享 CRT。
  • @HansPassant 虽然您不能从 C# 调用 delete[],但您可以简单地从 C++ 代码中导出一个释放器。那么就不需要 C++/CLI 也不需要相同的 CRT。

标签: c# c++ pinvoke


【解决方案1】:

首先将 C++ 代码更改为仅使用一级间接:

extern "C" __declspec(dllexport) void Dll_TestArray(TestStruct &testStruct)
{
     const int num = 15;
     testStruct.num = num;
     testStruct.intArray = new int[num];
     for (int i=0; i<num; i++)
         testStruct.intArray[i] = i+1;  
}

在 C# 方面你想要这个:

public struct TestStruct
{
    public int num;
    public IntPtr intArray;
}

[DllImport("testing.dll", CallingConvention=CallingConvention.Cdecl)]
static extern void Dll_TestArray(out TestStruct testStruct);

public GetArray()
{
    TestStruct testStruct;
    Dll_TestArray(out testStruct);
    int[] arr = new int[testStruct.num];
    Marshal.Copy(testStruct.intArray, arr, 0, arr.Length);
    // need to call back to DLL to ask it to deallocate testStruct.intArray
}

您还需要导出一个函数,该函数将释放您使用 C++ 堆分配的数组。否则你会泄露它。

也许更简单的方法是更改​​设计以让调用者分配缓冲区。

【讨论】:

  • 非常感谢。至于释放内存等,我很高兴照顾这个。我只是在努力在数组中取回我想要的数据。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多