P/Invoke使用C#和C++之间的双编组数据数组
P/Invoke with arrays of double - marshalling data between C# and C++
我已经阅读了C++Interop上使用p/Invoke的各种MSDN页面,但我仍然感到困惑。
我有一些大型的double数组,需要将其放入本机代码中,还有一些生成的数组需要返回。我事先不知道输出数组的大小。为了简单起见,我将在示例中只使用一个数组。平台为x64;我读到32位和64位环境之间的编组内部结构非常不同,所以这可能很重要。
C#
[DllImport("NativeLib.dll")]
public static extern void ComputeSomething(double[] inputs, int inlen,
[Out] out IntPtr outputs, [Out] out int outlen);
[DllImport("NativeLib.dll")]
public static extern void FreeArray(IntPtr outputs);
public void Compute(double[] inputs, out double[] outputs)
{
IntPtr output_ptr;
int outlen;
ComputeSomething(inputs, inputs.Length, out output_ptr, out outlen);
outputs = new double[outlen];
Marshal.Copy(output_ptr, outputs, 0, outlen);
FreeArray(output_ptr);
}
C++
extern "C"
{
void ComputeSomething(double* inputs, int input_length,
double** outputs, int* output_length)
{
//...
*output_length = ...;
*outputs = new double[output_length];
//...
}
void FreeArray(double* outputs)
{
delete[] outputs;
}
}
它是有效的,也就是说,我可以在C++侧读取我写入数组的双打。然而,我想知道:
- 这真的是使用P/Invoke的正确方法吗
- 我的签名不是不必要的复杂吗
- 是否可以更有效地使用P/Invoke来解决此问题
- 我相信我读到过,可以避免对内置类型的一维数组进行编组。有办法绕过元帅吗。收到了吗
请注意,我们有一个可工作的C++/Cli版本,但在第三方库代码中存在一些与本地静态相关的问题,这些问题会导致崩溃。微软将这个问题标记为WONTFIX,这就是我寻找替代品的原因。
这是可以的。完全缺乏返回错误代码的方法是非常糟糕的,当数组很大并且程序内存不足时,这会造成伤害。你遇到的严重车祸很难诊断。
复制数组并明确发布它们的需要当然不会赢得任何奖励。通过让调用者将指针传递给它自己的数组,然后只编写元素,就可以解决这个问题。然而,您需要一个协议来让调用者计算出数组需要有多大,这将需要调用该方法两次。第一个调用返回所需的大小,第二个调用完成任务。
一个样板示例是:
[DllImport("foo.dll")]
private static int ReturnData(double[] data, ref int dataLength);
还有一个示例用法:
int len = 0;
double[] data = null;
int err = ReturnData(data, ref len);
if (err == ERROR_MORE_DATA) { // NOTE: expected
data = new double[len];
err = ReturnData(data, len);
}
不需要复制,不需要释放内存,这是件好事。如果本机代码不注意传递的len,它可能会损坏GC堆,这不是一件好事。但当然很容易避免。
如果将确定输出长度的代码与填充输出的代码分离是可行的,那么您可以:
- 导出一个返回输出长度的函数
- 从C#代码中调用它,然后分配输出缓冲区
- 再次调用非托管代码,这一次要求它填充输出缓冲区
但我认为您拒绝了这个选项,因为它不切实际。在这种情况下,您的代码是解决问题的一种非常合理的方式。事实上,我想说你做得很好。
一旦修复了调用约定的不匹配,代码在x86中的工作方式将完全相同。在C++端,调用约定是cdecl
,但在C#端,它是stdcall
。这在x64上并不重要,因为只有一个调用约定。但在x86下,这将是一个问题。
一些评论:
- 您不需要使用
[Out]
和out
。后者意味着前者 - 您可以通过分配共享堆来避免导出解除定位器。例如,C++侧的
CoTaskMemAlloc
,然后与C#侧的Mashal.FreeCoTaskMem
解除分配
如果事先知道数组大小,可以编写一个以托管数组为参数的C++/CLI DLL,对其进行固定,并在其获得的固定指针上调用本机C++DLL。
但如果它只是输出,我看不到任何没有副本的版本。您可以使用SAFEARRAY,让P/Invoke代替您进行复制,但仅此而已。
- C++:__aligned(__alignof__) 导致字符数组数据出现问题?
- 很好的语法来获取对向量/数组数据的大小引用?
- 数组数据以错误的方式遍历 Python/Matlab
- 访问 Arduino 中的结构数组数据
- 在能够从 Web 浏览器访问大型数组数据的同时存储它的最可行方法是什么?
- 在C 类中声明数组数据
- 如何在 c++ 中将字符串数组数据写入输出文件
- 使用 stl::map 和 stl::unordered_map 对包含大量重复元素的数组数据进行排序
- 检索字符数组数据时出现问题
- 共享库的C 访问数组数据
- 将包含数组数据成员的结构保存到文件
- C++中的LinkedList字符数组数据
- 正在尝试使用if语句检查数组数据
- 在单独的数据中显示数组数据
- 如何在 c++ 中将字节类型的字符数组数据保存到文件中
- 从数组数据中缺少第一个元素的 DLL 返回C++结构
- 如何在C++中表示二进制字节数组数据
- 实现动态数组数据结构时出现"double free or corruption"错误
- 多维数组数据成员的运行时错误
- C++ :使用 * 显示数组数据的条形图