3

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-functionPoll的签名:

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

除非我有严重的脑力衰竭(诚然,这对我来说相当普遍),否则这在我看来应该是可行的。

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

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

4

2 回答 2

4

您缺少 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 返回值报告状态码。就像一个负值报告错误代码,一个正值返回实际复制到缓冲区中的元素数量。

于 2013-04-04T09:24:17.773 回答
1

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

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

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

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

如果它不是真正的双打数组,那么你当然不能做你正在做的事情。

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

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

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

[编辑]

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

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

于 2013-04-04T08:48:43.803 回答