当 C# 调用 c++ 函数时,是不是可以在被调用的 c++ 函数中初始化数组并返回到 C#?

Posted

技术标签:

【中文标题】当 C# 调用 c++ 函数时,是不是可以在被调用的 c++ 函数中初始化数组并返回到 C#?【英文标题】:when C# call a c++ function, is it possible to initialize array in called c++ function and back to C#?当 C# 调用 c++ 函数时,是否可以在被调用的 c++ 函数中初始化数组并返回到 C#? 【发布时间】:2016-11-03 10:17:47 【问题描述】:

c++ 函数知道数组的大小并且必须创建它。

c++ 代码:

__declspec(dllexport) int getlog(int* &data)

    data = new int[5];

    for (int i = 0; i < 5; i++)
    
        data[i] = i;
    

    return 0;

和c#:

    [DllImport(@"myDll.dll", CallingConvention=CallingConvention.Cdecl)]
    public static extern int getlog(int[] data);

    int[] arr = null;
    getlog(arr);

我看到了this,但没有帮助

【问题讨论】:

Returning an int array in C++/CLI to c# .NET的可能重复 @StoryTeller 提到了与问题相关的链接,但不是重复的。在链接下,通过gcnew 在 C++/CLI 端分配的内存。这不是 OP 要求的。 感谢 StoryTeller 和 Nikita。 win32project 和 CLI 有区别吗?我需要一些参数中的数组而不是返回值 【参考方案1】:

在 C# 方面:

[DllImport(@"myDll.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int getlog([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] out int[] data, 
out int length);

在 C++ 方面:

#include <Objbase.h>

extern "C" __declspec(dllexport) int getlog(int** data, int * length)

  *length = 5;
  int sizeInBytes = sizeof(int) * (*length);
  *data = static_cast<int*>(CoTaskMemAlloc(sizeInBytes ));

  for (int i = 0; i < 5; i++)
  
    (*data)[i] = i;
  

  return 0;

CoTaskMemAlloc 将使 CLR 能够在内存不再可访问时负责释放内存。更多信息请关注"Memory Management with the Interop Marshaler":

运行时总是使用CoTaskMemFree 方法来释放内存。如果您正在使用的内存不是通过CoTaskMemAlloc 方法分配的,您必须使用IntPtr 并使用适当的方法手动释放内存。

上述方法适用于 .NET 4.0 及更高版本。对于 .NET 3.5,由于 unmanaged arrays 的支持有限,它会失败:

被控制的数组不能作为 ref 或 out 参数传递。同样,包含数组大小的参数必须通过值传递(SizeParamIndex 字段不能引用 ref 或 out 参数)

要克服这个问题并在 C# 端获取数组,请使用以下签名:

[DllImport(@"MyDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int getlog(out IntPtr data, out int length);

当您有IntPtr 到数据时,可以将内容复制到托管数组:

int length;
IntPtr data;
getlog(out data, out length);
int[] managedArr = new int[length];
Marshal.Copy(data, managedArr, 0, length);

您也可以处理此类数组的内容而无需应对。要使用此方法,请在项目设置中启用“允许不安全代码”:

int length;
IntPtr data;
getlog(out data, out length);

unsafe

  int* intArray = (int*)data.ToPointer();
  for (int i = 0; i < length; i++)
  
    Console.WriteLine(intArray[i]);  
  

【讨论】:

@mohsen 解决了您的问题还是需要更多帮助? 谢谢@Nikita,抱歉。我尝试此代码并得到 Cannot marshal 'parameter #1': Cannot use SizeParamIndex for ByRef array parameters 错误。 @mohsen 您是否尝试过答案中的 C# 和 C++ 代码?我刚刚运行它并成功获得了 C# 端的缓冲区。我使用以下代码在 C# 中调用该方法:int len; int[] data; getlog(out data, out len); 谢谢@Nikita。这很好!不幸的是,我不能投票给答案 @user1000247 是的,您应该手动释放内存。使用Marshal.FreeCoTaskMem(data);

以上是关于当 C# 调用 c++ 函数时,是不是可以在被调用的 c++ 函数中初始化数组并返回到 C#?的主要内容,如果未能解决你的问题,请参考以下文章

C# 调用C++ DLL,而c++函数的有一个参数需要是null,该怎么传递?

当参数之一是指针数组时,如何从 C# 调用 C++ DLL

C++一天来几问

C#如何静态调用C++中的方法(静态调用dll)

如何使用 p/invoke 终止从 C# 调用的 C++ 函数

C++ 库在 vb6 中有效,但在 c# 中无效