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?的主要内容,如果未能解决你的问题,请参考以下文章