C#从C++ DLL封送行程double*?

8ehkhllq  于 2022-11-27  发布在  C#
关注(0)|答案(3)|浏览(236)

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

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

此函数计算两个双数组(真实的和虚数)的FFT,并返回实数和虚数分量交错的单个双数组:“雷、林、雷、林......”
我不知道如何在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异常:
无法封送行程'return value':无效的托管/非托管类型组合。
我想这是因为C++ double*和C#double[]不太一样,但是我不知道如何修复它。有什么想法吗?
编辑:我更改了签名,以便传递一些额外的信息:

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

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

nhhxz33t

nhhxz33t1#

你的例子有几个问题:

  1. C++代码不知道这些数组有多大。封送处理器会给它们传递一个有效的指针,但是如果没有相应的长度参数,就没有办法知道它们有多大。在大多数平台上,sizeof(dataReal)和sizeof(dataImag)可能是4或8(即sizeof(void*))。可能不是您想要的。
    1.虽然可以将指针作为返回值封送回去(您可以使用它来填充托管数组),返回值的内存没有隐含的所有者,这就有可能导致内存泄漏。是否使用new在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被描述要做的事情进行更新。

e37o9pze

e37o9pze2#

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

[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

ef1yzkbh

ef1yzkbh3#

这不是很好的Interop原型。您可以使用两种方法来解决这个问题:
1.更改C函数原型并通过参数返回所有双精度值(希望您有C函数的源代码)
1.使用Rest Wing描述的IntPtr
只有在使用CoTaskMemAlloc函数分配内存的情况下,返回内存引用才能正常工作。否则,您将在内存释放期间发生运行时异常(CLR始终尝试使用CoTaskMemFree释放返回内存)。
我认为最好的方法是第一种,这在彼得·休恩的回答中有所描述。

相关问题