Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C# Api #25

Closed
AiwendilsCode opened this issue Mar 28, 2024 · 2 comments
Closed

C# Api #25

AiwendilsCode opened this issue Mar 28, 2024 · 2 comments

Comments

@AiwendilsCode
Copy link

AiwendilsCode commented Mar 28, 2024

I am trying to implement this Rust implementation into c# so far I created FFIArray which holds data to simplify as well as simplified data returned from Rust.

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct FfiArray
{
  public required nint data;
  public required nuint len;
}

I am converting float[][] array into nint and vice versa using these methods.

public static nint CopyDataFromFloatJaggedArrayToIntPtr(float[][] jaggedArray)
{
    int length = jaggedArray.Length;

    double[] joinedArray = jaggedArray.Select(coords =>
    {
        var x = BitConverter.GetBytes(coords[0]);
        var y = BitConverter.GetBytes(coords[1]);
        return BitConverter.ToDouble([..x, ..y], 0);
    }).ToArray();

    nint arrayPtr = Marshal.AllocHGlobal(jaggedArray.Length * sizeof(double));

    Marshal.Copy(joinedArray, 0, arrayPtr, joinedArray.Length);

    return arrayPtr;
}

public static float[][] CopyDataFromIntPtrToFloatJaggedArray(nint ptr, int length, int innerLength = 2)
{
    float[][] jaggedArray = new float[length][];

    for (int i = 0; i < length; i++)
    {
        byte[] x = new byte[4];
        byte[] y = new byte[4];

        Marshal.Copy(ptr + (i * 8), x, 0, 4);
        Marshal.Copy(ptr + (i * 8 + 4), y, 0, 4);

        jaggedArray[i] = [BitConverter.ToSingle(x), BitConverter.ToSingle(y)];
    }

    return jaggedArray;
}

public static void FreeIntPtr(nint ptr, int length)
{
    for (int i = 0; i < length; i++)
    {
        nint innerPtr = Marshal.ReadIntPtr(ptr, i * nint.Size);
        Marshal.FreeHGlobal(innerPtr);
    }

    Marshal.FreeHGlobal(ptr);
}

Rust bindings from DLL:

internal class RustBindings
{
    [DllImport("RustSimplificationAlgorithms.dll")]
    public static extern FfiArray simplify_rdp_ffi(FfiArray array, double precision);
    [DllImport("RustSimplificationAlgorithms.dll")]
    public static extern FfiArray simplify_rdp_idx_ffi(FfiArray array, double precision);
    [DllImport("RustSimplificationAlgorithms.dll")]
    public static extern FfiArray simplify_visvalingam_ffi(FfiArray array, double precision);
    [DllImport("RustSimplificationAlgorithms.dll")]
    public static extern FfiArray simplify_visvalingam_idx_ffi(FfiArray array, double precision);
    [DllImport("RustSimplificationAlgorithms.dll")]
    public static extern FfiArray simplify_visvalingamp_ffi(FfiArray array, double precision);
    [DllImport("RustSimplificationAlgorithms.dll")]
    public static extern void drop_float_array(FfiArray ptr);
    [DllImport("RustSimplificationAlgorithms.dll")]
    public static extern void drop_usize_array(FfiArray ptr);
}

And Public Api looks like this (for testing purposes I am trying to implement only RDP algorithm).

public static float[][] Simplify(float[][] data, double tolerance)
{
    FfiArray? dataInArray = null;
    FfiArray? simplified = null;

    try
    {
        nint dataForSimplification = MarshalOperations.CopyDataFromFloatJaggedArrayToIntPtr(data);

        dataInArray = new FfiArray()
        {
            data = dataForSimplification,
            len = (nuint)data.Length
        };

        simplified = RustBindings.simplify_rdp_ffi((FfiArray)dataInArray, tolerance);

        float[][] result = MarshalOperations.CopyDataFromIntPtrToFloatJaggedArray(((FfiArray)simplified).data, (int)((FfiArray)simplified).len);

        return result;
    }
    finally
    {

        if (dataInArray is not null)
            MarshalOperations.FreeIntPtr(((FfiArray)dataInArray).data, (int)((FfiArray)dataInArray).len);

        if (simplified is not null)
            RustBindings.drop_float_array((FfiArray)simplified!);

        Console.WriteLine("memory freed");
    }
}

My problem is that the simplification algorithm always returns different values despite the same values being provided. For checking I am using the Python library which is part of this repository. According to that, I am expecting 26 values but I got only 15 at best. Also when I check the returned values only the first two are valid and the others are junk.

I expect this problem is in bad memory handling.

string[] lines = File.ReadAllLines("generatedValuesSin.csv");

float[][] data = new float[lines.Length - 1][];

for (int i = 1; i < lines.Length; i++) // skip first line
{
    data[i - 1] = [(i - 1) * 3600, float.Parse(lines[i], NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture)];
}

float[][] simplified = RdpAlgorithm.Simplify(data, 0.15);

Source code repository: https://github.com/AiwendilsCode/Simplification.NET
Python testing code: https://github.com/AiwendilsCode/Simplification.NET/tree/master/Python

As a testing data I am using generated Sin values.

@AiwendilsCode AiwendilsCode changed the title Interop into C#. C# Api Mar 28, 2024
@urschrei
Copy link
Owner

I'm not a C# user, so I can't help you with this.

@AiwendilsCode
Copy link
Author

I successfully implemented a wrapper for C# here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants