PInvoke: Issue with returned array of doubles?

2020-04-10 06:47发布

I am using PInvoke to call a C++ function from my C# program. The code looks like this:

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

With Poll()'s signature looking like this:

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

The C-function Poll's signature:

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

Unless I'm having a huge brain failure (admittedly, fairly common for me), this looks to me like it should be working.

However, the double values I am getting are completely incorrect, and I think this is because of incorrect memory usage. I have looked it up, and I think doubles in C# and C++ are identical in size, but maybe there is some other issue playing here. One thing that rubs me the wrong way is that Marshal.Copy is never told what type of data it should expect, but I read that it is supposed to be used this way.

Any clues, anyone? If needed, I can post the correct results and the returned results.

2条回答
我想做一个坏孩纸
2楼-- · 2020-04-10 07:30

You are missing the CallingConvention property, it is Cdecl.

You really want to favor a better function signature, the one you have is extremely brittle due to the memory management problem, the required manual marshaling, the uncertainty of getting the right size array and the requirement to copy the data. Always favor the caller passing a buffer that your native code fills in:

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)

Use the int return value to report a status code. Like a negative value to report an error code, a positive value to return the number of elements actually copied into the buffer.

查看更多
不美不萌又怎样
3楼-- · 2020-04-10 07:52

You shouldn't even need to marshal the data like that, as long as you declare the P/Invoke correctly.

If your CyberHand::Hand* is in reality a pointer to a double, then you should declare your P/Invoke as

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

And then just call it with your array of doubles.

If it isn't really an array of doubles, then you certainly can't do what you're doing.

Also, how does your 'C' function know how big the array will be? Is it a fixed size?

The IntPtr return value will be a problem. What is the double* pointing to? An array or a single item?

You could find that it's easier (if you can) to write a simpler more friendly 'C' wrapper for the function you're calling, and call the wrapper function itself. You can of course only do that if you can change the source code of the 'C' DLL. But without knowing exactly what your function does, I can't give you specific advice.

[EDIT]

Ok, your code should theoretically work if the memory being passed back isn't being messed around with (e.g. freed up). If it's not working, then I suspect something like that is happening. You'd definitely be better writing a wrapper 'C' function that fills in an array allocated by the C# and passed to the function, rather than passing back a pointer to some internal memory.

BTW: I don't like code which passes around pointers to blocks of memory without also passing the size of that block. Seems a bit prone to nasty things.

查看更多
登录 后发表回答