[Connect Update 16 C# + C++] Calling methods in DLL (p/invoke)

Ok, so i have some questions regarding the p/invoking of methods out of a dll:

Does this look right for dllimport if both DLLs exist in the same folder? I am using an example project to test with. Both projects work separately, now I just want to understand how i can call a method from managed to native.

 class UstnNative
    {
        [DllImport("TextUpdateCpp.dll", CharSet = CharSet.Unicode)]
        private static extern int createALine(DPoint3d dPoint);

        public static void CreateLine()
        {
            createALine(new DPoint3d(0, 0, 0));
        }
    }

Here's the cpp file method:

void createALine(DPoint3dCR basePt)

{

	EditElementHandle eeh;

	DSegment3d seg;

	seg.Init(basePt, DPoint3d::From(basePt.x + g_1mu * 2, basePt.y + g_1mu));

	ICurvePrimitivePtr pCurve = ICurvePrimitive::CreateLine(seg);

	DraftingElementSchema::ToElement(eeh, *pCurve, nullptr, ACTIVEMODEL->Is3d(), *ACTIVEMODEL);

	eeh.AddToModel();

}

Can I pass in the DPoint3d from GeometryNET.DPoint3d to native Bentley.DPoint3D? Or do i need to use doubles and create a new point in native?

Parents
  • Hi Viktor,

    Yes you can pass the DPoint directly as you are doing, it will be marshalled by the System.Runtime.InteropServices.Marshal, which will make a copy of the data to pass through.

    You can also pass pointers to the data which will not create copies. (Without having to enable the unsafe compile flag on the C# side)

    For instance if you had a C function to extract a Z value from a surface (receiving a point pointer and a surface Id):

    int FnSurfaceZ(Dpoint3d* Pt, int I);

    You can call it like this:

    [DllImport("xxxx.dll", EntryPoint = "FnSurfaceZ",
                CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
    private static extern int FnSurfaceZ(IntPtr point3d, int surfaceId);
    
    public bool SurfaceZ(ref BCOM.Point3d point, int surfaceId)
    {
        //Allocate new unmanaged memory
        IntPtr pointer = Marshal.AllocHGlobal(Marshal.SizeOf(point));
        try
        {
            //copy the value of the reference point to the unmanaged memory
            Marshal.StructureToPtr(point, pointer, false);
            //Pass the pointer to MicroStation to pass to Terra Model
            int answer = FnSurfaceZ(pointer, surfaceId);
            //Copy the answer in unmanaged memory back to the reference point
            point = (BCOM.Point3d)Marshal.PtrToStructure(pointer, typeof(BCOM.Point3d));
            //return true or false indicating the success of the operation
            return (answer == SUCCESS);
        }
        finally
        {
            //Free the unmanaged memory
            Marshal.FreeHGlobal(pointer);
        }
    }

    This is explicitly creating the pointers.

    You can also implicitly pass the pointers using:

    [DllImport("xxxx.dll", EntryPoint = "FnSurfaceZ",
                CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
    private static extern int FnSurfaceZ(ref DPoint3d point3d, int surfaceId);
    
    public bool SurfaceZ(ref BCOM.Point3d point, int surfaceId)
    {
        int answer = FnSurfaceZ(ref point, surfaceId);
        //return true or false indicating the success of the operation
        return (answer == SUCCESS);
    
    }

    This can however not be used for struct arrays.

    I prefer the first mechanism because then passing points or arrays of points follows the same logic, and I have control over how it happens.

    HTH,

    Francois

Reply
  • Hi Viktor,

    Yes you can pass the DPoint directly as you are doing, it will be marshalled by the System.Runtime.InteropServices.Marshal, which will make a copy of the data to pass through.

    You can also pass pointers to the data which will not create copies. (Without having to enable the unsafe compile flag on the C# side)

    For instance if you had a C function to extract a Z value from a surface (receiving a point pointer and a surface Id):

    int FnSurfaceZ(Dpoint3d* Pt, int I);

    You can call it like this:

    [DllImport("xxxx.dll", EntryPoint = "FnSurfaceZ",
                CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
    private static extern int FnSurfaceZ(IntPtr point3d, int surfaceId);
    
    public bool SurfaceZ(ref BCOM.Point3d point, int surfaceId)
    {
        //Allocate new unmanaged memory
        IntPtr pointer = Marshal.AllocHGlobal(Marshal.SizeOf(point));
        try
        {
            //copy the value of the reference point to the unmanaged memory
            Marshal.StructureToPtr(point, pointer, false);
            //Pass the pointer to MicroStation to pass to Terra Model
            int answer = FnSurfaceZ(pointer, surfaceId);
            //Copy the answer in unmanaged memory back to the reference point
            point = (BCOM.Point3d)Marshal.PtrToStructure(pointer, typeof(BCOM.Point3d));
            //return true or false indicating the success of the operation
            return (answer == SUCCESS);
        }
        finally
        {
            //Free the unmanaged memory
            Marshal.FreeHGlobal(pointer);
        }
    }

    This is explicitly creating the pointers.

    You can also implicitly pass the pointers using:

    [DllImport("xxxx.dll", EntryPoint = "FnSurfaceZ",
                CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
    private static extern int FnSurfaceZ(ref DPoint3d point3d, int surfaceId);
    
    public bool SurfaceZ(ref BCOM.Point3d point, int surfaceId)
    {
        int answer = FnSurfaceZ(ref point, surfaceId);
        //return true or false indicating the success of the operation
        return (answer == SUCCESS);
    
    }

    This can however not be used for struct arrays.

    I prefer the first mechanism because then passing points or arrays of points follows the same logic, and I have control over how it happens.

    HTH,

    Francois

Children
No Data