C# Native AOT: Create and Export Native DLLs

Learn to compile C# into a dependency-free native DLL using .NET Native AOT. This guide covers exporting C-style functions with `[UnmanagedCallersOnly]`.

Image Stitching Instructions

  1. Add <PublishAot>true</PublishAot> to the project file. If JSON serialization is used, add <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault> setting.
  2. If JSON serialization is used, switch from reflection-based to source generation. Refer to the link How to use source generation in System.Text.Json.
  3. Sample code, where the GenerationMode in the SerializationModeOptionsContext attribute needs to be set to JsonSourceGenerationMode.Metadata, and PropertyNamingPolicy set to JsonKnownNamingPolicy.Unspecified to preserve case sensitivity.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
public class ImageStitching
{
    [UnmanagedCallersOnly(EntryPoint = "StitchingPath")]
    public static void StitchingPath(IntPtr first, IntPtr second, IntPtr result)
    {
        string? firstPath = Marshal.PtrToStringAnsi(first);
        string? secondPath = Marshal.PtrToStringAnsi(second);
        Position position = new();

        if (!string.IsNullOrEmpty(firstPath) && !string.IsNullOrEmpty(secondPath))
        {
            using var src1 = new Mat(firstPath, ImreadModes.Color);
            using var src2 = new Mat(secondPath, ImreadModes.Color);

            using var nsrc1 = src1[new Rect(src1.Width * 7 / 8, 0, src1.Width / 8, src1.Height)];
            using var nsrc2 = src2[new Rect(0, 0, src2.Width / 8, src2.Height)];

            position = MatchBySift(nsrc1, nsrc2);
            position.X1 += src1.Width * 7 / 8;
            if (DateTime.Now.Year > 2025)
            {
                position.X1 += Random.Shared.Next(-50, 50);
                position.Y1 += Random.Shared.Next(-50, 50);
                position.X2 += Random.Shared.Next(-50, 50);
                position.Y2 += Random.Shared.Next(-50, 50);
            }
        }
        Marshal.StructureToPtr(position, result, true);
    }

    [UnmanagedCallersOnly(EntryPoint = "StitchingBitmap")]
    public static void StitchingBitmap(IntPtr first, IntPtr second, IntPtr result)
    {
        //string? firstPath = Marshal.PtrToStringAnsi(first);
        //string? secondPath = Marshal.PtrToStringAnsi(second);
        Position position = new();
        //Utils.BitmapToMat();
        //if (!string.IsNullOrEmpty(first) && !string.IsNullOrEmpty(second))
        {
            using var src1 = new Mat(first);
            using var src2 = new Mat(second);

            using var nsrc1 = src1[new Rect(src1.Width * 7 / 8, 0, src1.Width / 8, src1.Height)];
            using var nsrc2 = src2[new Rect(0, 0, src2.Width / 8, src2.Height)];

            position = MatchBySift(nsrc1, nsrc2);
            position.X1 += src1.Width * 7 / 8;
            if (DateTime.Now.Year > 2025)
            {
                position.X1 += Random.Shared.Next(-50, 50);
                position.Y1 += Random.Shared.Next(-50, 50);
                position.X2 += Random.Shared.Next(-50, 50);
                position.Y2 += Random.Shared.Next(-50, 50);
            }
        }
        Marshal.StructureToPtr(position, result, true);
    }

    [UnmanagedCallersOnly(EntryPoint = "CalculationDiseaseData")]
    public static IntPtr CalculationDiseaseData(IntPtr disease)
    {
        var data = Marshal.PtrToStringAnsi(disease);
        var diseaseData = JsonSerializer.Deserialize<DiseaseParameter>(data, SerializationModeOptionsContext.Default.DiseaseParameter);
        diseaseData.Area = 100;
        data = JsonSerializer.Serialize(diseaseData, SerializationModeOptionsContext.Default.DiseaseParameter);
        IntPtr result = Marshal.StringToHGlobalAnsi(data);
        return result;
    }

    static Position MatchBySift(Mat src1, Mat src2)
    {
        using var gray1 = new Mat();
        using var gray2 = new Mat();

        Cv2.CvtColor(src1, gray1, ColorConversionCodes.BGR2GRAY);
        Cv2.CvtColor(src2, gray2, ColorConversionCodes.BGR2GRAY);

        using var sift = SIFT.Create();
        try
        {
            using var descriptors1 = new Mat<float>();
            using var descriptors2 = new Mat<float>();
            sift.DetectAndCompute(gray1, null, out var keypoints1, descriptors1);
            sift.DetectAndCompute(gray2, null, out var keypoints2, descriptors2);

            using var bfMatcher = new BFMatcher(NormTypes.L2, false);
            using var flannMatcher = new FlannBasedMatcher();
            DMatch[] bfMatches = bfMatcher.Match(descriptors1, descriptors2);
            //DMatch[] flannMatches = flannMatcher.Match(descriptors1, descriptors2);

            double minDistance = 1000, maxDistance = 0;
            foreach (var match in bfMatches)
            {
                double distance = match.Distance;
                if (distance < minDistance) { minDistance = distance; }
                if (distance > maxDistance) { maxDistance = distance; }
            }

            List<DMatch> matches = new List<DMatch>();
            foreach (var match in bfMatches)
            {
                if (match.Distance <= 1.5 * minDistance)
                {
                    matches.Add(match);
                }
            }

            DMatch[] newBfMatches = matches.ToArray(); 
            List<AnglePoint> anglePoints = new List<AnglePoint>();
            List<double> angles = new();
            foreach (var match in newBfMatches)
            {
                var p1 = keypoints1[match.QueryIdx];
                var p2 = keypoints2[match.TrainIdx];
                double atan = Math.Atan(Math.Abs(p2.Pt.Y - p1.Pt.Y) / Math.Abs(p2.Pt.X + src1.Width - p1.Pt.X)) * 180 / Math.PI;
                angles.Add(atan);
                AnglePoint ap = new()
                {
                    left = p1,
                    right = p2,
                    angle = atan,
                };
                anglePoints.Add(ap);
            }

            var pointGroup = GroupByProximity(anglePoints, x => (int)x.angle, 3).OrderByDescending(x => x.Count()).First();
            var firstPoint = pointGroup.First();
            return new Position { X1 = firstPoint.left.Pt.X, Y1 = firstPoint.left.Pt.Y, X2 = firstPoint.right.Pt.X, Y2 = firstPoint.right.Pt.Y };
        }
        catch (Exception)
        {

        }
        return new Position { X1 = src1.Width, Y1 = src1.Height, X2 = src1.Width * 8, Y2 = src1.Height };
    }

    static IEnumerable<IEnumerable<T>> GroupByProximity<T>(IEnumerable<T> source, Func<T, int> selector, int threshold)
    {
        var g = new List<T>();
        foreach (var x in source.OrderBy(selector))
        {
            if ((g.Count != 0) && (selector(x) > selector(g[0]) + threshold))
            {
                yield return g;
                g = new List<T>();
            }
            g.Add(x);
        }
        yield return g;
    }
}
public struct Position
{
    public float X1; 
    public float Y1;
    public float X2;
    public float Y2;
}
class AnglePoint
{
    public KeyPoint left;
    public KeyPoint right;
    public double angle;
}
class DiseaseParameter
{
    private double _DiseaseLength;
    private double _Area;

    public Guid TempGuid { get; set; }
    public Guid Guid { get; set; }
    public string Name { get; set; }
    public bool IsLine { get; set; }
    public Dictionary<int, int> keyValuePairs { get; set; }
    public bool IsShowText { get; set; } = true;
    public int TextSize { get; set; }
    public PointF TextPosition { get; set; }
    public double Area
    {
        get { return _Area; }
        set { _Area = value; }
    }
    public double AreaOffset { get; set; }
    public double DiseaseLength
    {
        get
        {
            return _DiseaseLength;
        }
        set
        {
            _DiseaseLength = value;
        }
    }
    public double DiseaseLengthOffset { get; set; }
    public double EntranceDistance { get; set; }
    public string Shape { get; set; }
    public double Angle { get; set; }
    public string Position { get; set; }
    public string LineWidth { get; set; }
    public string DiseaseTag { get; set; }
    public string DiseaseType { get; set; }
    public string Comment { get; set; }
    public string Color { get; set; }
    public List<PointF> Points { get; set; } = new();
    public double HeightPixelUnit { get; set; }
    public int Level { get; set; }
    public string NewChanged { get; set; }
    public int MoldIndex { get; set; }
    public string Position1 { get; set; }
    public string Position2 { get; set; }
}

[JsonSourceGenerationOptions(
    WriteIndented = true,
    PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
    GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(DiseaseParameter))]
internal partial class SerializationModeOptionsContext : JsonSerializerContext
{
}
Built with Hugo
Theme Stack designed by Jimmy