4
\$\begingroup\$

I have attempted to write c# bindings for a Rust library. I have never worked with unmanned code / languages before. Superficially this code does work with no apparent bugs or memory leaks.

I want to know if I am correctly handling the pointers. Is it memory safe?

I am running Marshal.FreeHGlobal(optionsPtr); on line 75 because optionsPtr is the only case that allocates memory with the Marshal.AllocHGlobal() method on line 116

Do I need to free all the other IntPtr's as well?

The repo is here

The rust library is here

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;

namespace CavalierContours
{
    /// <summary>
    /// Wrapper for the CavalierContours FFI functions, should have no Unity dependencies.
    /// </summary>
    
    
    public class Interop
    {
        private const string DLL_NAME = "cavalier_contours_ffi";

        [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr test();

        [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
        private static extern int cavc_pline_create(
            CavcVertex[] vertexes,
            uint n_vertexes,
            byte is_closed,
            out IntPtr pline
        );

        [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
        private static extern int cavc_pline_get_vertex_count(
            IntPtr pline, 
            out uint count);

        [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
        private static extern int cavc_pline_parallel_offset(
            IntPtr pline, 
            double offset, 
            IntPtr options, 
            out IntPtr result);

        [DllImport("cavalier_contours_ffi.dll")]
        private static extern int cavc_pline_get_vertex(IntPtr 
            pline, 
            uint position, 
            out CavcVertex vertex);

        [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
        private static extern int cavc_pline_create_approx_aabbindex(
            IntPtr pline, 
            out IntPtr aabbindex);

        [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
        private static extern int cavc_plinelist_get_pline(
            IntPtr plinelist,
            uint position,
            out IntPtr pline);
        
        
        [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
        private static extern int cavc_plinelist_get_count(
            IntPtr plinelist,
            out uint count);
        
        
        public static List<CavcVertex[]> ParallelOffset(CavcVertex[] cavVerts, bool isClosed, double offsetDelta)
        {
            IntPtr plinePointer = GetPline(cavVerts, isClosed);
            return ParallelOffsetInernal(plinePointer, offsetDelta, IntPtr.Zero); // IntPtr.Zero yields default options
        }
        
        public static List<CavcVertex[]> ParallelOffset(CavcVertex[] cavVerts, bool isClosed, double offsetDelta, OffsetOptions options)
        {
            IntPtr plinePointer = GetPline(cavVerts, isClosed);
            IntPtr optionsPtr = GetOptions(plinePointer, options.PosEqualEps, options.SliceJoinEps, options.OffsetDistEps, options.HandleSelfIntersects);
            var result = ParallelOffsetInernal(plinePointer,  offsetDelta, optionsPtr);
            Marshal.FreeHGlobal(optionsPtr);
            return result;
        }

        private static List<CavcVertex[]> ParallelOffsetInernal(IntPtr plinePointer, double offsetDelta, IntPtr options)
        {
            IntPtr resultListPointer;
            int statusOutput1 = cavc_pline_parallel_offset(plinePointer, offsetDelta, options, out resultListPointer);
            if (statusOutput1 != 0) Debug.Log("Error: cavc_pline_parallel_offset: Rust: Pline is null"); //todo replace with non unity log

            uint offsetPlineCount;
            cavc_plinelist_get_count(resultListPointer, out offsetPlineCount);

            List<CavcVertex[]> result = new();
            
            for (uint i = 0; i < offsetPlineCount; i++)
            {
                uint position = i;
                IntPtr offsettedPlinePointer;
                int statusOutput2 = cavc_plinelist_get_pline(resultListPointer, position, out offsettedPlinePointer);
                if (statusOutput2 != 0) Debug.Log("Error: cavc_pline_parallel_offset: Rust: PlineList is null"); //todo replace with non unity log
                result.Add(VertsFromPline(offsettedPlinePointer));
            }
            return result;
        }
        
        private static IntPtr GetOptions(IntPtr pline, double posEqualEps, double sliceJoinEps, double offsetDistEps, bool HandleSelfIntersects)
        {
            IntPtr aabbindex;
            int result = cavc_pline_create_approx_aabbindex(pline, out aabbindex);
            if (result != 0) Debug.Log("Error: cavc_pline_create_approx_aabbindex: Rust: Pline is null"); //todo replace with non unity log
        
            CavcPlineParallelOffsetO cavcPlineParallelOffsetO = new CavcPlineParallelOffsetO
            {
                AabbIndex = aabbindex, //IntPtr
                PosEqualEps = posEqualEps, //double
                SliceJoinEps = sliceJoinEps, //double
                OffsetDistEps = offsetDistEps, //double
                HandleSelfIntersects = (byte)(HandleSelfIntersects ? 1 : 0) //byte
            };
        
            IntPtr cavcPlineParallelOffsetOPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CavcPlineParallelOffsetO)));
            Marshal.StructureToPtr(cavcPlineParallelOffsetO, cavcPlineParallelOffsetOPtr, false);
            
            return cavcPlineParallelOffsetOPtr;
        }
        
        private static IntPtr GetPline(CavcVertex[] cavVerts, bool isClosed)
        {
            IntPtr plinePointer;
            byte is_closed = isClosed ? (byte) 1 : (byte) 0;
            int errorCode = cavc_pline_create(cavVerts, (uint) cavVerts.Length, is_closed, out plinePointer);
            if (errorCode != 0) Debug.Log("Error: cavc_pline_create"); //todo replace with non unity log
            uint count;
            cavc_pline_get_vertex_count(plinePointer, out count);
            Debug.Assert(count == cavVerts.Length); //todo replace with non unity
            return plinePointer;
        }
        
        private static CavcVertex[] VertsFromPline(IntPtr pline)
        {
            uint count;
            cavc_pline_get_vertex_count(pline, out count);
            CavcVertex[] output = new CavcVertex[count];
            for (int i = 0; i < count; i++)
            {
                cavc_pline_get_vertex(pline, (uint) i, out output[i]);
            }
            return output;
        }
    }

    public class OffsetOptions
    {
        public double PosEqualEps = 0.0001;
        public double SliceJoinEps = 0.0001;
        public double OffsetDistEps = 0.0001;
        public bool HandleSelfIntersects = false;
    }
}

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Minor note: I'd use the EntryPoint field of the DllImport attribute to set the name of the external method, so you can stick to C# naming guidelines, so something like this:

(also, to stick with C# convention, use DllName instead of DLL_NAME)

[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "cavc_pline_create")]
private static extern int CavcPlineCreate(
    CavcVertex[] vertexes,
    uint n_vertexes,
    byte is_closed,
    out IntPtr pline
);

I can^t test it right now, but you should also be able to rename the parameters to be inline with C# naming conventions.

\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.