2

I have to call an unmanaged function from C# and must provide an array of coordinates (doubles) to it. How does the marshalling work for this case correctly?

On the unmanaged side:

typedef struct dPoint3dTag
{
  double x, y, z;
} dPoint3d;

void UnmanagedModifyGeometry(char *strFeaId, dPoint3d *pnts, int iNumPnts);

I defined a managed Structure for DPoint3d on the managed side:

[StructLayout(LayoutKind.Sequential)]
public struct DPoint3d
{
// Constructor
public DPoint3d(double x, double y, double z)
{
this.x = x;
this.y = y;
this.z = z;
}

public double x, y, z;
}

I'm trying to call the unmanaged function from C# in this way:

// Import of the unmanaged function
[DllImport("Unmanaged.dll")]
public static extern void UnmanagedModifyGeometry([MarshalAs(UnmanagedType.LPStr)] string strFeaId, DPoint3d[] pnts, int iNumPnts);

// Using the unmanaged function from C#
// Allocating points
DPoint3d[] pnts = new DPoint3d[iPntCnt];
String strFeaId = "4711";

// After filling in the points call the unmanaged function
UnmanagedModifyGeometry(strFeaId, pnts, iPntCnt);

Is this workflow correct?

Regards tomtorell

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Tom Torell
  • 51
  • 5

1 Answers1

1

First of all on the unmanaged side, char* is a modifiable string. You should use const here to indicate that the data flows from caller to callee. And it makes sense to do the same for the other parameters:

void UnmanagedModifyGeometry(
    const char *strFeaId, 
    const dPoint3d *pnts, 
    const int iNumPnts
);

Now it is clear to all how the data flows.

On the managed side, there is one obvious problem with the declaration which is that you don't specify the calling convention. The default is stdcall, but your unmanaged code will be cdecl, assuming that the declaration in the question is accurate.

The struct declarations that you show match perfectly. There is nothing more to say on that subject.

You can also make use of the default marshalling to simplify the p/invoke. I'd write it like this:

[DllImport("Unmanaged.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void UnmanagedModifyGeometry(
    string strFeaId, 
    [In] DPoint3d[] pnts, 
    int iNumPnts
);

And call it like this:

DPoint3d[] pnts = new DPoint3d[...]; // supply appropriate value for array length
// populate pnts
UnmanagedModifyGeometry("4711", pnts, pnts.Length);
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • +1 True problem here is only wrong calling convention (but I'd still add attributes to make explicit that `iNumPnts` is number of elements in `pnts`, frankly speaking I don't know if marshaler will ever use it but it's an extra-check - if used - or documentation - if unused). – Adriano Repetti Jul 23 '14 at 09:40
  • @AdrianoRepetti `UnmanagedType.LPArray` is the default here. The `SizeParamIndex` can be used to allow the marshaller to marshal only part of the array. However, `DPoint3d[]` is blittable so it is pinned by the marshaller anyway. It wouldn't hurt to include `SizeParamIndex` but I also don't think it really hurts to omit it. – David Heffernan Jul 23 '14 at 09:53
  • I agree it doesn't hurt to omit it, I just wonder if there are (or there will) benefits to include it. `SizeParamIndex` shouldn't be used to tell marshaler that one parameter carries the size of the array? Something that - in theory if used - will generate an error (managed side, not access violation on unmanaged code) if I do `unmanagedFunction(myArray, myArray.Length + 1)`? – Adriano Repetti Jul 23 '14 at 09:59
  • @AdrianoRepetti All it does it control how the marshaller marshals the array. If it is not specified, then the length of the supplied array is used. If it is specified, then the value of the other param is used. In this case the array is blittable and it has no impact at all because the marshaller pins rather than marshals. – David Heffernan Jul 23 '14 at 10:03
  • Thanks! I thought it was a run-time check! – Adriano Repetti Jul 23 '14 at 10:20
  • @AdrianoRepetti There may well be runtime checking also. Your example of `unmanagedFunction(myArray, myArray.Length + 1)` would be worth checking out. – David Heffernan Jul 23 '14 at 10:22
  • @David thanks a lot for clarfying this, you put me on the right way! One little more question: Using a single DPoint3d as a parameter, not an array, I have to use: "[In] ref DPoint3d pntCenter", is this right? – Tom Torell Jul 23 '14 at 11:22
  • @David Does this really matches "const dPoint3d*", witch means that the memory to witch the ptr points to is const (the ptr itself is not const), or does it matches "dPoint3d* const" witch means that the ptr itself is const and the memory is writable? – Tom Torell Jul 23 '14 at 12:51
  • What I said. Of course C# and C++ are different so the match up is not enforceable. – David Heffernan Jul 23 '14 at 13:24