C# Marshalling double* 从 C++ DLL?

Posted

技术标签:

【中文标题】C# Marshalling double* 从 C++ DLL?【英文标题】:C# Marshalling double* from C++ DLL? 【发布时间】:2011-07-01 15:15:42 【问题描述】:

我有一个带有导出函数的 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 );

我得到一个 MarshalDirectiveException 异常:

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

我假设这是因为 C++ double* 与 C# double[] 不太一样,但我不确定如何解决它。有什么想法吗?

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

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

我们总是知道output 的长度是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。

【问题讨论】:

它无法将 C++ double* 转换为 C# double[],因为封送代码不知道数组的长度。根据您的描述,我假设它是输入数组的两倍,但我不知道 C++ 函数如何知道输入数组的长度。 C++ 函数像这样确定数组长度: int lengthReal = sizeof(dataReal) / sizeof(double); int lengthImag = sizeof(dataImag) / sizeof(double); 不,它没有。 dataReal 只是一个指向 double 的指针,它的大小与数组中元素的数量无关,这就是数组在 C(++) 中作为参数传递的方式。另外:我们不知道谁的工作是释放结果数组(以及如何)。 好的,我明白了。 (对不起,我在 C# 与 C/C++ 方面的经验要多得多)。那么我还需要传递数组的长度吗?至于解除分配,它们不是在堆栈中吗?这意味着当函数结束时,内存会自动释放?如果不是,你如何建议我自己释放它? 【参考方案1】:

您的示例存在一些问题:

    C++ 代码不知道这些数组有多大。编组器将向它们传递一个有效的指针,但没有相应的长度参数,就无法判断它们有多大。 sizeof(dataReal) 和 sizeof(dataImag) 在大多数平台上可能是 4 或 8(即 sizeof(void*))。可能不是您想要的。 虽然可以将指针作为返回值封送回(然后您可以使用它来填充托管数组),但返回值的内存没有隐含的所有者,因此存在内存泄漏的可能性。缓冲区是否在 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 的描述进行更新。

【讨论】:

澄清一下:你的意思实际上是 double[] result = double[8],对吧? 是的。我没有密切注意你的 fft 实际做了什么。 根据您的要求,您可以将 resultLength 按值传递。【参考方案2】:

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

[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, and COM Interop

【讨论】:

【参考方案3】:

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

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

只有在使用 CoTaskMemAlloc 函数分配内存的情况下,返回内存引用才能正常工作。否则,您将在内存释放期间出现运行时异常(CLR 总是尝试使用 CoTaskMemFree 释放返回的内存)。

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

【讨论】:

以上是关于C# Marshalling double* 从 C++ DLL?的主要内容,如果未能解决你的问题,请参考以下文章

在C#中如何将int类型强制转换为double类型

使用从 int 到字符串 double 的字符串格式并保持小数 C# [重复]

netty系列之:netty对marshalling的支持

c#使用double时的大数字表示

编解码-marshalling

C# double 的尾数规范化