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.

Lee White
  • 3,649
  • 8
  • 37
  • 62

2 Answers2

4

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 1
    Deleted my previous comment as I was not thinking clearly. Thanks a lot, this code works perfectly and your advice is very useful. – Lee White Apr 04 '13 at 10:33
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.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • 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. – Lee White Apr 04 '13 at 08:52
  • 1
    And how is the memory for the returned pointer allocated? Who is responsible for freeing it? How do you know how big it is? – Matthew Watson Apr 04 '13 at 08:54
  • 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. – Lee White Apr 04 '13 at 08:56
  • @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 Apr 04 '13 at 10:20
  • @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. – Lee White Apr 04 '13 at 10:26
  • @ArnoSluismans That's definitely not the best way to do it. Use a COM class, as I said before. Then you can instantiate it directly in C# and call methods on it just as if it was a C# class. Marshaling will also be handled automatically for you. – user1610015 Apr 05 '13 at 02:19