PInvoke:返回的双精度数组有问题吗?

Posted

技术标签:

【中文标题】PInvoke:返回的双精度数组有问题吗?【英文标题】:PInvoke: Issue with returned array of doubles? 【发布时间】:2013-04-04 08:42:28 【问题描述】:

我正在使用PInvoke 从我的 C# 程序中调用 C++ 函数。代码如下所示:

IntPtr data = Poll(this.vhtHand);
double[] arr = new double[NR_FINGERS /* = 5 */ * NR_JOINTS /* = 3*/];
Marshal.Copy(data, arr, 0, arr.Length);

Poll() 的签名如下所示:

[DllImport("VirtualHandBridge.dll")]
static public extern IntPtr Poll(IntPtr hand);

C 函数Poll 的签名:

extern "C" __declspec(dllexport) double* Poll(CyberHand::Hand* hand)

除非我有严重的脑力衰竭(诚然,这对我来说相当常见),否则在我看来它应该可以工作。

但是,我得到的双精度值完全不正确,我认为这是因为内存使用不正确。我查了一下,我认为 C# 和 C++ 中的双打在大小上是相同的,但也许这里还有其他问题。让我感到不适的一件事是 Marshal.Copy 从未被告知它应该期望什么类型的数据,但我读到它应该以这种方式使用。

任何线索,有人吗?如果需要,我可以发布正确的结果和返回的结果。

【问题讨论】:

【参考方案1】:

您缺少 CallingConvention 属性,它是 Cdecl。

您真的想要一个更好的函数签名,由于内存管理问题、所需的手动编组、获取正确大小数组的不确定性以及复制数据的要求,您拥有的函数签名非常脆弱。始终支持调用者传递一个由您的本机代码填充的缓冲区:

extern "C" __declspec(dllexport)
int __stdcall Poll(CyberHand::Hand* hand, double* buffer, size_t bufferSize)

[DllImport("foo.dll")]
private static extern int Poll(IntPtr hand, double[] buffer, int bufferSize)

使用 int 返回值报告状态码。就像一个负值报告错误代码,一个正值返回实际复制到缓冲区中的元素数量。

【讨论】:

删除了我之前的评论,因为我没有想清楚。非常感谢,这段代码运行良好,您的建议非常有用。【参考方案2】:

只要您正确声明 P/Invoke,您甚至不需要像那样编组数据。

如果您的 CyberHand::Hand* 实际上是一个指向双精度的指针,那么您应该将您的 P/Invoke 声明为

[DllImport("VirtualHandBridge.dll")]
static public extern IntPtr Poll(double[] data);

然后用你的双打数组调用它。

如果它不是真的是一个双精度数组,那么你肯定不能做你正在做的事情。

另外,你的“C”函数如何知道数组有多大?是固定大小吗?

IntPtr 返回值会有问题。 double* 指向什么?数组还是单个项目?

您会发现为您正在调用的函数编写一个更简单、更友好的“C”包装器并调用包装器函数本身会更容易(如果可以的话)。当然,只有当您可以更改“C”DLL 的源代码时,您才能这样做。但是在不知道你的函数具体做什么的情况下,我无法给你具体的建议。

[编辑]

好的,您的代码理论上应该可以工作如果传回的内存没有被弄乱(例如释放)。如果它不起作用,那么我怀疑正在发生类似的事情。您肯定会更好地编写一个包装器“C”函数来填充由 C# 分配的数组并传递给函数,而不是传回指向某个内部存储器的指针。

顺便说一句:我不喜欢传递指向内存块的指针而不传递该块的大小的代码。似乎有点容易发生讨厌的事情。

【讨论】:

Cyber​​Hand::Hand* 不是指向双精度的指针,而是指向 C 函数将从中提取数据的对象的指针。函数的返回值是双指针。 返回指针的内存是如何分配的?谁负责释放它?你怎么知道它有多大? 投票代码如下:hand->updateData(); double* rv = hand->getJoints(); return rv;。释放内存是我将在此之后解决的问题;它目前没有被释放,所以它应该保留在那里。 @ArnoSluismans C# 代码如何实例化 Cyber​​Hand::Hand 对象?在 C# 中实例化非托管 C++ 类既困难又不方便。如果你还没有处理过这个问题,那么你离让它发挥作用还有很长的路要走。这里自然的解决方案是将 Hand 变成一个 COM 类,这是将非托管 C++ 类暴露给 .NET 的方式。 @user1610015 我没有在 C# 中实例化它——我只保留一个指向该对象的指针,该指针存储为IntPtr。然后对于我需要调用的每个方法(我故意使方法的数量尽可能少),我调用一个以CyberHand::Hand* 作为参数的 C++ 方法。然后该方法将调用CyberHand::Hand 中的正确方法。可悲的是,这意味着我需要为每个必需的方法调用构建这样的桥接方法。这很笨拙,但我读到这是最好的方法。

以上是关于PInvoke:返回的双精度数组有问题吗?的主要内容,如果未能解决你的问题,请参考以下文章

返回没有尾随零的双精度类型

Pyspark 在返回 0 的双精度数上转换整数

尝试从文本字段中读取格式化的双精度

Java - Decimal Format.parse 返回具有指定小数位数的双精度值

代码指定的双精度和编译器选项双精度之间的差异

在元组列表(SCALA)中将所有具有相同日期的双精度加起来