C#编组来自C ++ DLL的double *?

我有一个带有导出函数的C ++ DLL:

extern "C" __declspec(dllexport) double* fft(double* dataReal, double* dataImag) { [...] } 

该函数计算两个双数组(实数和虚数)的FFT,返回单个双数组,实数为虚数组件交错:{Re,Im,Re,Im,…}

我不知道如何在C#中调用此函数。 我在做的是:

 [DllImport("fft.dll")] static extern double[] fft(double[] dataReal, double[] dataImag); 

当我像这样测试它:

 double[] foo = fft(new double[] { 1, 2, 3, 4 }, new double[] { 0, 0, 0, 0 }); 

我收到MarshalDirectiveExceptionexception:

无法封送“返回值”:无效的托管/非托管类型组合。

我假设这是因为C ++ double*与C# double[]不完全相同,但我不确定如何修复它。 有任何想法吗?

编辑:我已经更改了签名,以便我现在传递一些额外的信息:

 extern "C" __declspec(dllexport) void fft(double* dataReal, double* dataImag, int length, double* output); 

我们总是知道output length是2倍length

 [DllImport("fft.dll")] static extern void fft(double[] dataReal, double[] dataImag, int length, out double[] output); 

测试如下:

 double[] foo = new double[8]; fft(new double[] { 1, 2, 3, 4 }, new double[] { 0, 0, 0, 0 }, 4, out foo); 

现在我得到一个AccessViolationException而不是MarshalDirectiveException。

您的示例存在一些问题:

  1. C ++代码不知道这些数组有多大。 编组器会向它们传递一个有效的指针,但没有相应的长度参数,就无法分辨它们有多大。 sizeof(dataReal)和sizeof(dataImag)在大多数平台上可能是4或8(即sizeof(void *))。 可能不是你想要的。
  2. 虽然可以将指针作为返回值进行编组(然后可以使用它来填充托管数组),但是没有隐含的返回值内存的所有者,从而打开了内存泄漏的可能性。 缓冲区是否在fft中分配了新的? 如果是这样,那么您需要另一个导出,托管代码可以调用以释放内存或使用LocalAlloc(然后在托管端使用Marshal.FreeHGlobal)。 这充其量是个问题。

相反,我的建议是以这种方式定义fft:

 extern "C" __declspec(dllexport) void __stdcall fft(double const* dataReal, int dataRealLength, double const* dataImag, int dataImagLength, double* result, int resultLength) { // Check that dataRealLength == dataImagLength // Check that resultLength is twice dataRealLength } 

相应的P / Invoke签名将是:

 [DllImport("fft.dll")] static extern void fft(double[] dataReal, int dataRealLength, double[] dataImag, int dataImagLength, double[] result, int resultLength); 

然后是一个电话示例:

 double[] dataReal = new double[] { 1.0, 2.0, 3.0, 4.0 }; double[] dataImag = new double[] { 5.0, 6.0, 7.0, 8.0 }; double[] result = new double[8]; fft(dataReal, dataReal.Length, dataImag, dataImag.Length, result, result.Length); 

编辑:根据描述的fft进行更新。

无需更改签名。 您可以使用以下内容:

 [DllImport( "fft.dll", EntryPoint = "fft" )] public static extern IntPtr fft( double[] dataReal, double[] dataImag ); 

然后,您将需要从返回的IntPtr复制字节。 由于您知道输出的大小是输入的两倍,因此您可以通过以下方式执行此操作:

 double[] result = new double[ doubleSize ]; Marshal.Copy( pointer, result, 0, doubleSize ); 

result将包含fft函数返回的字节。

编辑:我相信你会发现P/Invoke Interop Assistant工具很有帮助: Managed,Native和COM Interop

这不是互操作的好原型。 您可以通过两种方式解决此问题:

  1. 更改C ++函数原型并通过参数返回所有双精度数(我希望你有C ++函数的源代码)
  2. 使用Rest Wing描述的IntPtr

只有在使用CoTaskMemAlloc函数分配内存的情况下,返回内存引用才能正常工作。 Othrwise你将在内存释放期间获取运行时exception(CLR总是尝试使用CoTaskMemFree释放返回内存)。

我认为最好的是第一种方式,它在Peter Huene的答案中描述。