3

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 Answers 2

5

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Deleted my previous comment as I was not thinking clearly. Thanks a lot, this code works perfectly and your advice is very useful.
1

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.

6 Comments

CyberHand::Hand* is not a pointer to a double, it's a pointer to an object that the C function will extract data from. The function's return value is the doublepointer.
And how is the memory for the returned pointer allocated? Who is responsible for freeing it? How do you know how big it is?
Poll's code looks like: hand->updateData(); double* rv = hand->getJoints(); return rv;. Freeing the memory is an issue I'll solve after this; it's currently not being freed so it should remain there.
@ArnoSluismans How does the C# code instantiate the CyberHand::Hand object? It's hard and inconvenient to instantiate an unmanaged C++ class in C#. If you haven't dealt with that you're a long way from getting this to work. The natural solution here is to turn Hand into a COM class, which is the way to expose unmanaged C++ classes to .NET.
@user1610015 I do not instantiate it in C# -- I only keep a pointer to that object stored as an IntPtr. Then for each method I need to call (I deliberately keep this number of methods as low as possible), I call a C++ method that takes a CyberHand::Hand* as argument. That method will then call the correct method in CyberHand::Hand. Sadly, this means that I need to build such a bridge method for every required method call. It's clunky but I read that this is the best way to do it.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.