<?xml version="1.0" encoding="iso-8859-1" ?><rss version="2.0"><channel><title>Eric Sink</title><link>https://ericsink.com/</link><description>SourceGear Founder</description><copyright>Copyright 2001-2023 Eric Sink. All Rights Reserved</copyright><generator>mine</generator><item><title>Native AOT libraries with TypeScript</title><guid>https://ericsink.com/native_aot/typescript.html</guid><link>https://ericsink.com/native_aot/typescript.html</link><pubDate>Wed, 03 May 2023 13:00:00 CST</pubDate><description><![CDATA[
<p style="text-align: center; font-style: italic">
This is part of a series on Native AOT.<br/>
<a href="sgbridge_050.html">Previous</a> -- <a href="index.html">Top</a>
</p>
<hr/>

<p>A few weeks ago I published some samples showing the use of
Native AOT libraries from Rust.  As I mentioned, the projection
of .NET APIs to Rust requires quite a few ergonomic compromises.
For example, the following line in C# is a call to the QuestPDF
method to set the size of a page:</p>

<pre class="screen">
    page.Size(PageSizes.Letter);
</pre>

<p>But the Rust projection is messier:</p>

<pre class="screen">
    page.<span style="color: red">Size_PageSize</span>(&amp;PageSizes::get_Letter()<span style="color: red">?</span>)<span style="color: red">?</span>;
</pre>

<ul>
    <li><p>Rust doesn't have method overloading by name, and the <code>Size()</code> method
        has multiple overloads, so we have to give each one its own name, like <code>Size_PageSize()</code>.</p></li>

    <li><p>Rust doesn't have exceptions, and every .NET method might throw, but the metadata doesn't tell us
        anything about which kinds of errors we might want to propagate or not.  So for the
        moment, the binding generator outputs every method with a return type of <code>Result<></code>,
        which means we need to use <code>?</code> after every call.</p></li>
</ul>

<p>Rust also lacks other things that .NET wants, including 
optional parameters, property getters/setters, and inheritance.
Because of all these differences, Rust for general .NET development 
would typically involve too much pain to be practical.</p>

<p>But the nice thing about Native AOT libraries, is that
they can be used with anything that can call C.</p>

<h3>Hello TypeScript</h3>

<p>TypeScript has method overloads, exceptions, property getters/setters,
and inheritance.  Hmmm.</p>

<p>For the last few weeks, I've been adding
TypeScript as another output language for my 
binding generator.  Things are in a rough state,
but the results look positive, so I have published
a nuget package and a working port of the QuestPDF sample.
That sample is short, but it involves things like generic delegates and
extension methods, so I consider it non-trivial.
</p>

<p>Interop between any two languages will always involve trouble
spots, but TypeScript can look awfully similar to C#.
Here's a visual diff from the Quest PDF sample, with TypeScript on
the left, and C# on the right:</p>

<p><a href="questpdf_diff_ts_cs.png"><img width="750" src="questpdf_diff_ts_cs.png"/></a></p>

<p>All of the differences here are because
I am projecting nullability more strictly 
than it is in C#.  I currently think this is
a feature rather than a bug, but I'm not completely settled
on that, and if I stopped doing it this way, these two
snippets would actually be identical.</p>

<p>To be fair, let's acknowledge that this is just one sample.
TypeScript lacks several things that require extra effort
for .NET interop, including:</p>

<ul>
    <li><p>byref parameters</p></li>
    <li><p>extension methods</p></li>
    <li><p>runtime type information</p></li>
</ul>

<p>I mean, if TypeScript were exactly like C#, it would
be C#.</p>

<p>Still, I see potential here.</p>

<h3>Details</h3>

<p>The binding generator itself is contained in a nuget package:
</p>

<p><a href="https://www.nuget.org/packages/SourceGear.Bridge.NativeAOT.TypeScript/0.6.0">https://www.nuget.org/packages/SourceGear.Bridge.NativeAOT.TypeScript/0.6.0</a></p>

<p>The QuestPDF sample is in the <code>samples/typescript</code> directory of this repo:</p>

<p><a href="https://github.com/sourcegear/bridge-info">https://github.com/sourcegear/bridge-info</a></p>

<p>For this prototype, all the FFI-level stuff is setup for Deno (apologies to fans of node).</>

<h3>Related</h3>

<p>There's something going on in the following repo:</p>

<p><a href="https://github.com/microsoft/node-api-dotnet">https://github.com/microsoft/node-api-dotnet</a></p>

<p>I'm not super-clear on where that project is headed.
And there are significant differences relative
to what I'm doing, but also some common ground.  I think it looks interesting.</p>

<h3>Next steps</h3>

<p>I'm hoping that I will soon have an AvaloniaUI sample (it's a fair bit more complicated).  Down the road a bit, I'd love to get this working with [at least the desktop versions of] MAUI, but I'm not sure yet what will be feasible.</p>

<p>If you have any questions or feedback, please feel free to post in the Discussions or Issues area of the <a href="https://github.com/sourcegear/bridge-info">sourcegear/bridge-info</a> repo linked above.</p>

]]></description></item><item><title>Binding Generator Preview Release</title><guid>https://ericsink.com/native_aot/sgbridge_050.html</guid><link>https://ericsink.com/native_aot/sgbridge_050.html</link><pubDate>Mon, 10 Apr 2023 13:00:00 CST</pubDate><description><![CDATA[
<p style="text-align: center; font-style: italic">
This is part of a series on Native AOT.<br/>
<a href="delegate_i32.html">Previous</a> -- <a href="index.html">Top</a> -- <a href="typescript.html">Next</a>
</p>
<hr/>

<p>I have finally published a preview release of the
Native AOT binding generator I've been working on.
I wouldn't call it "production-ready" yet, but 
having the tool publicly available makes it more tangible and
real.  Folks can give it a try, and give
feedback if they wish.</p>

<p>There's a GitHub repo for samples and other information:</p>

<p><a href="https://github.com/sourcegear/bridge-info">https://github.com/sourcegear/bridge-info</a></p>

<p>The content there is fairly bare-bones right now, but 
there are a few Rust samples, including the QuestPDF demo I
discussed a few weeks ago, plus two little apps that use Avalonia.
One of the Avalonia samples is written against the very basic
controls.  The other uses NXUI, which is a pretty cool library
offering a different way of working with Avalonia:</p>

<p><a href="https://github.com/wieslawsoltes/NXUI">https://github.com/wieslawsoltes/NXUI</a></p>

<p>These "Work on My Machine", and should
hopefully work for you as well.</p>

<h3>A word about ugliness</h3>

<p>Some folks have contributed various aesthetic opinions
about the nature of .NET APIs projected into Rust.
<code>:-)</code></p>

<p>The simple fact is that Rust lacks a number of
key things that C# uses a lot:</p>

<ul>
    <li>Method overloading</li>
    <li>Exceptions</li>
    <li>Default parameter values</li>
    <li>Inheritance</li>
    <li>Properties</li>
    <li>etc</li>
</ul>

<p>This impedance mismatch causes the Rust projection
of a .NET API to be far less ergonomic than things are in C#.</p>

<p>So I am certainly not suggesting that Rust could become a 
preferred way of using .NET.  I am exploring the boundaries
of what is possible in interop situations.
Some .NET libraries turn out better than others.  For
Rust, Avalonia hits almost all of the difficulties at one
time.  That makes it a nice test case, but not necessarily
a practical use case.</p>

<h3>What got published</h3>

<p>The binding generator itself is contained in a nuget package:
</p>

<p><a href="https://www.nuget.org/packages/SourceGear.Bridge.NativeAOT.Rust/0.5.0">https://www.nuget.org/packages/SourceGear.Bridge.NativeAOT.Rust/0.5.0</a></p>

<p>But that package is not intended to be used directly.  Rather,
a Cargo build script will deal with all that.  The dependency
for that build script is this crate:</p>

<p><a href="https://crates.io/crates/sourcegear-bridge-build">https://crates.io/crates/sourcegear-bridge-build</a></p>

<p>Several things need to be done to integrate that build script into a Cargo project.
Take a look at the samples I mentioned above.  Also, there is a Cargo subcommand designed
to help make this easier:</p>

<p><a href="https://crates.io/crates/sourcegear-bridge-cargo">https://crates.io/crates/sourcegear-bridge-cargo</a></p>

<p>If you have any questions or feedback, please feel free to post in the Discussions or Issues area of the <a href="https://github.com/sourcegear/bridge-info">sourcegear/bridge-info</a> repo linked above.</p>

<p>Enjoy!</p>

]]></description></item><item><title>Delegates</title><guid>https://ericsink.com/native_aot/delegate_i32.html</guid><link>https://ericsink.com/native_aot/delegate_i32.html</link><pubDate>Thu, 02 Mar 2023 13:00:00 CST</pubDate><description><![CDATA[
<p style="text-align: center; font-style: italic">
This is part of a series on Native AOT.<br/>
<a href="looking_ahead_questpdf_rust.html">Previous</a> -- <a href="index.html">Top</a> -- <a href="sgbridge_050.html">Next</a>
</p>
<hr/>

<p>Developing with .NET often involves delegates,
which we can think of as objects that represent things that are callable.
For example:</p>

<pre class="screen">
public static int count_files_with_e(string path)
{
    return System.IO.Directory.GetFiles(path)
        .<span style="color: red">Where</span>(x =&gt; x.Contains("e"))
        .Count()
        ;
}
</pre>

<p>The extension method <code>Where</code>
accepts a delegate.  We're calling
it with a lambda expression, which the compiler
converts into the correct delegate type.</p>

<p>How do we deal with delegates in a Native AOT
library?</p>

<p>The signature for <code>System.Linq.Enumerable.Where()</code>
is fairly hard on the eyes:</p>

<pre class="screen">
public static IEnumerable&lt;TSource&gt; Where&lt;TSource&gt;(
    this IEnumerable&lt;TSource&gt; source, 
    Func&lt;TSource,bool&gt; predicate
    );
</pre>

<p>All those generics!  Yikes.  We need to start with a 
simpler example:</p>

<pre class="screen">
public delegate int MapOne(int x);

public static int MapSum(
    int n, 
    <span style="color: red">MapOne</span> f
    )
{
    var sofar = 0;
    for (var i=0; i&lt;n; i++)
    {
        sofar += f(i);
    }
    return sofar;
}
</pre>

<ul>
    <li><p>The delegate type
<code>MapOne</code> is a simple mapping of one integer value
to another.</p></li>
<li><p>The <code>MapSum</code> function loops over the first <code>n</code>
integers and applies a delegate to each one, returning the
sum of the results.</p></li>
</ul>

<p>Calling <code>MapSum</code> from C#
with a lambda expression might look like this:</p>

<pre class="screen">
public static int CountDivisibleBy42(int n)
{
    return MapSum(
        n, 
        x =&gt; ((x % 42) == 0) ? 1 : 0
        );
}
</pre>

<p>But functions exposed by a Native AOT library must
follow the rules of C, and C doesn't have delegates --
it has function pointers:</p>

<pre class="screen">
typedef int (*MapOne)(int);
</pre>

<p>For the sake of illustration, let's observe that
C# 9 added support for function pointers, so one option
here is to 
just rewrite <code>MapSum</code> to use them:</p>

<pre class="screen">
[UnmanagedCallersOnly(EntryPoint = "map_sum_with_funcptr")]
public static unsafe int MapSumWithFuncPtr(
    int n, 
    <span style="color: red">delegate* unmanaged&lt;int,int&gt;</span> f
    )
{
    var sofar = 0;
    for (var i=0; i&lt;n; i++)
    {
        sofar += f(i);
    }
    return sofar;
}
</pre>

<p>This results in a function signature which is
compatible with Native AOT, so we could call it from
C.  First, since C doesn't have lambdas, we need
to define the map function:</p>

<pre class="screen">
int divisible_by_42(int x)
{
    return ((x % 42) == 0) ? 1 : 0;
}
</pre>

<p>And the call from C to the Native AOT function
looks like this:</p>

<pre class="screen">
    int32_t total = map_sum_with_funcptr(
        1000, 
        divisible_by_42
        );
    printf("%d\n", total);
</pre>

<p>But Native AOT libraries won't be much fun
if we need to rewrite everything.
It would be preferable to leave <code>MapSum</code> unchanged and
still provide a way to call it.  In other words,
we want to convert our function pointer into a
delegate.  We can do that with <code>System.Delegate.CreateDelegate()</code>.</p>

<p>The .NET class libraries provide <code>CreateDelegate</code> as
a way to create delegates of a given type from other methods.
It has several overloads, but none of them accept a function
pointer, so we need to wrap our function pointer in a something
that <code>CreateDelegate</code> can accept.  I call this
wrapper a "shadow" class:</p>

<pre class="screen">
private unsafe class my_shadow 
{
    readonly delegate* unmanaged&lt;int,int&gt; <span style="color: red">_funcptr</span>;
    public my_shadow(delegate* unmanaged&lt;int,int&gt; funcptr)
    {
        _funcptr = funcptr;
    }
    public unsafe int Invoke(int x)
    {
        return _funcptr(x);
    }
}
</pre>

<p>Now we have a regular C# object that contains the
function pointer and provides a method to invoke it.
So we can create a delegate of type <code>MapOne</code>
by making an instance of that shadow class and passing it
to <code>CreateDelegate</code>:</p>

<pre class="screen">
var shadow = new my_shadow(funcptr);
var del = <span style="color: red">Delegate.CreateDelegate</span>(
    typeof(MapOne), 
    shadow, 
    typeof(my_shadow).GetMethod("Invoke")
    );
</pre>

<p>Note that some of the overloads for <code>CreateDelegate</code>
cause AOT or trimmer warnings.  I'm using an overload that
Native AOT likes.</p>

<p>Using the <code>GCHandle</code> approach described previously,
we can return the delegate object across the Native AOT
boundary so it can be passed back, and for that purpose we
need to expose our original <code>MapSum</code> function with a signature
that accepts that delegate object handle:</p>

<pre class="screen">
[UnmanagedCallersOnly(EntryPoint = "map_sum_with_delegate")]
public static int MapSumWithDelegate(
    int n, 
    IntPtr del
    )
{
    var actualDelegate = (MapOne) GCHandle.FromIntPtr(del).Target;
    return MapSum(n, actualDelegate);
}
</pre>

<p>And finally, we can call <code>MapSum</code> from C like this:</p>

<pre class="screen">
    intptr_t del = create_delegate(
        divisible_by_42
        );
    int32_t total = map_sum_with_delegate(
        1000, 
        del
        );
    printf("%d\n", total);
</pre>

<hr/>
<p>The code for this blog entry is available at:</p>

<p><a href="https://github.com/ericsink/native-aot-samples/tree/main/delegate_i32">https://github.com/ericsink/native-aot-samples/tree/main/delegate_i32</a></p>

]]></description></item><item><title>Looking ahead: QuestPDF/Rust demo</title><guid>https://ericsink.com/native_aot/looking_ahead_questpdf_rust.html</guid><link>https://ericsink.com/native_aot/looking_ahead_questpdf_rust.html</link><pubDate>Wed, 15 Feb 2023 13:00:00 CST</pubDate><description><![CDATA[
<p style="text-align: center; font-style: italic">
This is part of a series on Native AOT.<br/>
<a href="no_exceptions.html">Previous</a> -- <a href="index.html">Top</a> -- <a href="delegate_i32.html">Next</a>
</p>
<hr/>

<p>So far in this blog series, I have been writing
about Native AOT mostly in the context of libraries,
discussing how things work at a fairly low level.</p>

<p>For this post, I want to take a step back
and look at the big picture, and the road ahead.  
Where is all this going?  Why do we care about Native AOT libraries,
and what can we do with them?</p>

<p>One of the most commonly cited benefits of
Native AOT is faster startup time.  But if that
were the only thing that mattered, we wouldn't
need support for libraries, would we?</p>

<p>A primary benefit of Native AOT for libraries
is the ability to interop with other languages
and ecosystems.  If we can compile our C# library
into a regular native library that pretends to be
written in C, then we can integrate it into almost
anything.</p>

<p>Native AOT libraries do make some new things
possible, but that doesn't mean those things are easy.  As we
have noted in this series, exported functions need
to follow the rules of C, and that's a really big
impedance mismatch with .NET classes.</p>

<p>Furthermore, unless we are actually consuming
the library from C, we have another canyon to
cross, bridging to something idomatic for Go or Rust
or Python or whatever.</p>

<p>This set of problems is somewhat new to .NET, but
in general, it is fairly well-trodden ground.  Typically,
these kinds of gaps get crossed by generating
bindings automatically.</p>

<p>There are many examples of tools that generate
bindings or glue code or middleware to make it easier
to connect code written in one language to another.
Citing one example seems better than zero,
so I'll mention <a href="https://www.swig.org/">SWIG</a>.
Wikipedia says it has been around for 27 years.
But like I said, there are lots of other examples.
Anything that becomes popular creates a demand for it
to interoperate with something else.</p>

<p>But for .NET there hasn't been much of this
kinda thing going on, at least not for the
"calling .NET code from something else" direction.  
Prior to Native AOT, calling a C# library
from native code usually involved hosting the CLR.
I'm not saying <i>nobody</i> does that, but, well, nobody
does that.</p>

<p>But now we have Native AOT libraries.
We're going to want binding generators.  And intuitively,
we should be able to have good ones.  All that metadata in a .NET
assembly ought to be really useful information when 
generating glue.</p>

<p>I've been
experimenting along these lines, and I am seeing
some encouraging results.</p>

<p>There are several pieces in play:</p>

<ul>
    <li><p>The core binding generator is a console app that reads
        a set of .NET assemblies and uses the metadata to generate
        exported Native AOT functions, plus
        Rust bindings.  Its command-line options are ... complicated.</p></li>

    <li><p>That console app is contained within
        a nuget package that provides MSBuild
        targets which integrate with the Native AOT stuff
        in the .NET SDK.   It gathers up the necessary
        information and invokes the binding
        generator.</p></li>

    <li><p>A Cargo build script provides integration
        with the Rust tools.  It invokes <code>dotnet publish</code>,
        which triggers the MSBuild targets, which
        generates the bindings and makes them available for the rest of
        the build process.</p></li>

    <li><p>A <code>.csproj</code> file in the Rust crate directory
        is used to specify which assemblies are
        available for Native AOT binding.  This includes
        the regular .NET class libraries, and can also include
        nuget package references, or other references.</p></li>

    <li><p>A config file is used to specify which
        classes and members we want to generate bindings
        for.  This is currently not very friendly,
        but if we didn't provide a way to limit things, we 
        would end up generating bindings for everything,
        including all the nuget packages and the entire
        .NET system.  This would greatly increase build time and 
        the size of the resulting library.</p></li>
</ul>

<p>These tools have not yet been released, so in lieu
of posting sample code, the demo for this chapter is in the
form of a video.  It's just a few minutes long, and it
shows a QuestPDF sample app, first in C#, then compiled
to a native library with Native AOT, and ported to Rust.</p>

<p>As a teaser, here's a snippet from the original C#,
using fluent extension methods, and a lambda:</p>

<pre class="screen">
page.Footer()
    .AlignCenter()
    .Text(x =&gt;
    {
        x.Span("Page ");
        x.CurrentPageNumber();
    });
</pre>

<p>The Rust equivalent is noisier, as Rust tends to be, but
it's structurally similar.  The binding generator has created
wrapper objects for the necessary .NET classes, and traits for the extension methods.  
Where the lambda used to be, we have a Rust closure that gets wrapped as a .NET delegate.</p>

<pre class="screen">
page.Footer()?
    .AlignCenter()?
    .Text_Action(
        |x : &amp;TextDescriptor|
        {
            x.Span_String(Some(&amp;System::String::from("Page ")))?;
            x.CurrentPageNumber()?;
            Ok(())
        }
    )?;
</pre>

<p>The video is available at:</p>

<p><a href="https://youtu.be/XQI1SvvqbGk">https://youtu.be/XQI1SvvqbGk</a></p>

]]></description></item><item><title>Must follow C rules, no exceptions</title><guid>https://ericsink.com/native_aot/no_exceptions.html</guid><link>https://ericsink.com/native_aot/no_exceptions.html</link><pubDate>Thu, 09 Feb 2023 13:00:00 CST</pubDate><description><![CDATA[
<p style="text-align: center; font-style: italic">
This is part of a series on Native AOT.<br/>
<a href="objects_gotcha.html">Previous</a> -- <a href="index.html">Top</a> -- <a href="looking_ahead_questpdf_rust.html">Next</a>
</p>
<hr/>

<p>As we have said, functions exported by
a Native AOT library must follow the rules of C, and
that means exceptions cannot be thrown.
More specifically, it means that if we attempt to throw
an exception past the Native AOT function
boundary, the program will crash.  C doesn't have
exceptions, so it can't deal with them.</p>

<p>Once again, here's the code for a Native AOT function
to get the length of a string:</p>

<pre class="screen">
[UnmanagedCallersOnly(EntryPoint = "get_string_length")]
public static int GetStringLength(IntPtr v)
{
    GCHandle h = GCHandle.FromIntPtr(v);
    object ob = h.Target;
    string s = (string) ob;
    int len = s.Length;
    return len;
}
</pre>

<p>As trivial as this code is, it does have places an exception might get
thrown.  In fact, we did that intentionally in the previous chapter
by passing in an invalid <code>GCHandle</code>.</p>

<p>What should we do about this?  We basically have only two
options.  We can allow the exception to cause a crash, or we
can catch the exception and somehow propagate the error.</p>

<p>For this particular function, we might actually just
want to leave it alone.  Some errors can't be usefully
handled.  As far as I can tell, aside from memory corruption,
the only way this function can crash is when it was given an
invalid argument, which suggests a bug somewhere else that
should be found and fixed.</p>

<p>But that won't work in every scenario.
 In many cases, the body of a Native AOT
function might need to look something like this:</p>

<pre class="screen">
[UnmanagedCallersOnly(EntryPoint = "my_function")]
public static int my_function()
{
    try
    {
        // do something
        return 0; // 0 means no error
    }
    <span style="color: red">catch</span> (Exception e)
    {
        return -1; // TODO some kind of meaningful error code
    }
}
</pre>

<p>There are a myriad of choices here, but the basic point
is that we would need to decide how we want to represent
errors, catch exceptions, and return those exceptions
in the representation we chose.  In this example I chose
the commonly used approach of an integer error code,
but I do not wish to imply that this would be simple.
Error handling rarely is.</p>

<p>Let's pretend for a moment that we actually do want
our string length function to catch and propagate.
How would do that?  If we want to return an error code,
we now have two return values, since the function already
wants to return the string length.  One or both of these values
are going to need to be returned through a pointer parameter
(C rules, remember?).</p>

<p>Let's propagate the error code through a pointer
(which requires <code>unsafe</code>):</p>

<pre class="screen">
[UnmanagedCallersOnly(EntryPoint = "get_string_length_errcode_parm")]
public unsafe static int WithErrorCodeParm(IntPtr v, <span style="color: red">int* ptr_error_code</span>)
{
    try
    {
        GCHandle h = GCHandle.FromIntPtr(v);
        object ob = h.Target;
        string s = (string) ob;
        int len = s.Length;
        *ptr_error_code = 0; // no error
        return len;
    }
    catch (Exception e)
    {
        *ptr_error_code = -1; // TODO meaningful error code
        return -1; // TODO but what result should we return?
    }
}
</pre>

<p>One problem here is that we still have to return
something for the result even when the exception is caught.
A string length cannot be less than zero, so we just
return something invalid.</p>

<p>Hey, maybe this function doesn't need the error code to be separate?  
Couldn't we just make all our error codes negative and
thus store the length and the error code in the same
number?  In this case, sure, that'll work:</p>

<pre class="screen">
[UnmanagedCallersOnly(EntryPoint = "get_string_length_neg")]
public static int AsNegativeLength(IntPtr v)
{
    try
    {
        GCHandle h = GCHandle.FromIntPtr(v);
        object ob = h.Target;
        string s = (string) ob;
        int len = s.Length;
        return len;
    }
    catch (Exception e)
    {
        return -1; // TODO meaningful negative error code
    }
}
</pre>

<p>But that certainly won't work in all cases.
This function just happens to have a return type
that isn't using all its possible values.</p>

<p>And either way, the caller of this function now
has to actually deal with error conditions.  It needs
to check for a negative length, or it needs to check
that separate error code.  There is not much reason to
propagate errors and then ignore them.</p>

<p>And that brings us full circle to the realization
that error propagation for this particular function
is a cure that is worse than the disease.</p>

<p>Broadly speaking, error handling is a very complex topic.
For now, my intent is merely to scratch the surface,
and to observe that Native AOT libraries bring
error handling challenges that many .NET developers will
find unfamiliar.</p>

<p>In typical .NET development:</p>

<ul>
    <li><p>We don't need to decide how to represent errors,
        because <code>System.Exception</code> is the standard
        way to do that.</p></li>

    <li><p>The decision of whether to propagate error information
        does not affect the signature of a method, because
        exceptions can be thrown from anywhere.</p></li>
</ul>

<p>Providing a Native AOT API for a library can raise
a bunch of questions that don't typically come up.</p>

<p>I'll close with one more thing.  Even with Native AOT,
we could maaaaaybe stay with <code>System.Exception</code>
as the standard way of propagating error information.
Using a <code>GCHandle</code>, we can return exception 
objects -- we just can't throw them.</p>

<pre class="screen">
[UnmanagedCallersOnly(EntryPoint = "get_string_length_ex_parm")]
public unsafe static int WithExceptionParm(IntPtr v, IntPtr* ptr_ex)
{
    try
    {
        GCHandle h = GCHandle.FromIntPtr(v);
        object ob = h.Target;
        string s = (string) ob;
        int len = s.Length;
        *ptr_ex = IntPtr.Zero; // no error
        return len;
    }
    catch (Exception e)
    {
        *ptr_ex = <span style="color: red">GCHandle.ToIntPtr(GCHandle.Alloc(e))</span>;
        return -1; // TODO but what result should we return?
    }
}
</pre>

<p>That solves only one of our problems (and not
one of the more difficult ones).  And it raises
more problems of its own:  Object handles are opaque,
so in order to turn that <code>Exception</code> object into
any sort of useful information, we'll have to make
another trip across the Native AOT boundary, and,
well, what happens if an exception gets thrown while
trying to get information about the exception that
got thrown?</p>

<hr/>
<p>The code for this blog entry is available at:</p>

<p><a href="https://github.com/ericsink/native-aot-samples/tree/main/no_exceptions">https://github.com/ericsink/native-aot-samples/tree/main/no_exceptions</a></p>

]]></description></item><item><title>A "gotcha" with object handles</title><guid>https://ericsink.com/native_aot/objects_gotcha.html</guid><link>https://ericsink.com/native_aot/objects_gotcha.html</link><pubDate>Tue, 31 Jan 2023 13:00:00 CST</pubDate><description><![CDATA[
<p style="text-align: center; font-style: italic">
This is part of a series on Native AOT.<br/>
<a href="hello_string.html">Previous</a> -- <a href="index.html">Top</a> -- <a href="no_exceptions.html">Next</a>
</p>
<hr/>

<p>In the previous chapter, we talked about
<code>GCHandle</code> as a way to pass object
references into native code.
Let's dive a little deeper and talk about
a problem that can happen with these object handles
in the context of Native AOT.</p>

<p>As we said in the previous chapter, the <code>IntPtr</code> from a <code>GCHandle</code> is "opaque",
the only thing we can do with it is "give it back to the .NET code and ask it to do something".
But we need to be a bit more precise.
That <code>GCHandle</code> is owned by one
particular instance of the .NET GC,
and we can only "give it back" to that
particular instance.  If there happen
to be multiple copies of the GC around,
we must not ... cross the strands.</p>

<p>Let's illustrate this by taking the code
from last chapter's sample and breaking it
into two libraries.</p>

<ul>
    <li><p>One library will have <code>get_hello_string</code>
        and <code>free_objecthandle</code>.</li></p>
    <li><p>The other 2 functions, <code>get_string_length</code>
        and <code>banish_letter_l</code>, will go into a separate library.</code>.</li></p>
</ul>

<p>All we've done here is split one library into two.
We moved the functions, but we made no changes to their
code.</p>

<p>We can leave the C++ code unchanged.  We just need
to change its little build script to reference both
libraries.</p>

<p>When we run the program, we now get an error:</p>

<pre class="screen">
Unhandled Exception: System.InvalidCastException: Specified cast is not valid.
   at System.Runtime.TypeCast.CheckCastClass(MethodTable*, Object) + 0x6b
   at NativeExports.GetStringLength(IntPtr) + 0x70
Aborted
</pre>

<p>For convenience and review, here's the code for <code>get_string_length</code>:</p>

<pre class="screen">
[UnmanagedCallersOnly(EntryPoint = "get_string_length")]
public static int GetStringLength(IntPtr v)
{
    GCHandle h = GCHandle.FromIntPtr(v);
    object ob = h.Target;
    string s = (string) ob;
    int len = s.Length;
    return len;
}
</pre>

<ul>
    <li><p>We are given an <code>IntPtr</code>, </p></li>
    <li><p>which we convert back to its <code>GCHandle</code>, </p></li>
    <li><p>and then grab the original object from the <code>Target</code> property, </p></li>
    <li><p>and then cast that object to a <code>string</code>.</p></p></li>
</ul>

<p>The exception is being thrown on the attempted cast,
but the core problem is that the <code>GCHandle</code> 
is ... foreign.</p>

<p>Remember in the previous chapter when we said that
the GC keeps track of everything?  Our transgression
here is that we allocated a string in one library and 
tried to use it in a different library.  That's not
okay.  The GC can't keep track of stuff outside
of its library.</p>

<p>In other words:  Each library has its own GC.</p>

<p>We can see from the size of the two shared libraries
that each <code>.so</code> is large enough to suggest
that it has its own copy of all the dependencies
it needs:</p>

<pre class="screen">
$ ls -l cs_make_string/bin/Debug/net7.0/linux-x64/publish/
total 15448
-rw-r--r-- 1 eric eric 15814944 Jan 31 10:19 make_string.so
$ ls -l cs_use_string/bin/Debug/net7.0/linux-x64/publish/
total 15452
-rw-r--r-- 1 eric eric 15820792 Jan 31 10:33 use_string.so
</pre>

<p>Strictly speaking, this "foreign handle" problem is as old as <code>GCHandle</code>
itself, but it has been relatively uncommon.  Before Native AOT libraries,
you kinda had to go out
of your way to have multiple instances of the GC and pass
a <code>GCHandle</code> across them.</p>

<p>Now that Native AOT (by default)
bundles up all the dependencies with each library,
the result is a potentially bigger limitation
in practice:
When multiple Native AOT libraries are in play,
we cannot share objects between them.</p>

<p>This has ramifications for the use cases that
can be addressed with Native AOT libraries.</p>

<p>For example, suppose that Carole, Monica, and Kate each 
have a C# library
that they want to make available as a package for
C++ developers.  Such a package might contain the
compiled library (built with Native AOT) plus a C++ wrapper.
Carole's library is more foundational, and
each of the other two libraries have a dependency on it.</p>

<p>With the current limitations of Native AOT,
that scenario is difficult or impossible.</p>

<p>One possible solution here is "just don't share
objects".  For some cases, that might be okay.
For example, with a string, we could
convert to/from a C++ representation
that no longer relies on the object handle.
But this is not a general solution.
What if the object is a network socket?</p>

<p>How did we end up here?</p>

<p>I suspect the .NET team has simply prioritized
ease over power.  Building a Native AOT library 
is really elegant.  Just type
<code>dotnet publish -r RID</code> and the tooling
will figure out everything you need, warn you about
incompatibilities, and get it done.
The resulting shared library doesn't have any
weird dependencies -- everything it needs has been
included, and everything else has been trimmed out.</p>

<p>An alternative approach, one which prioritized
power-user scenarios, would have been possible,
but it would have taken longer to develop, and it
would have made the feature much harder to use.
Instead of being self-contained, the resulting shared library
would depend on other shared libraries, probably quite
a few of them.</p>

<p>(I should note here that the early support for
static libraries does offer cause for optimism,
although not yet a clean solution.
When Native AOT builds a static library, it does not
include a copy of the GC (and such things).  
Rather, (as mentioned in a <a href="mul_cpp_win_static.html">previous chapter</a>), the necessary runtime dependencies
need to be added at link time.  However,
each static library build by Native AOT does get
its own copy of certain other things,
so using two static libraries results in duplicate symbols.)</p>

<p>In my opinion, the team made the right choice for
the early stages of Native AOT.  Nonetheless, I do
hope the feature gets more flexibility in this area later.</p>

</p>

<hr/>
<p>The code for this blog entry is available at:</p>

<p><a href="https://github.com/ericsink/native-aot-samples/tree/main/problem_multiple_libraries">https://github.com/ericsink/native-aot-samples/tree/main/problem_multiple_libraries</a></p>

]]></description></item><item><title>Objects</title><guid>https://ericsink.com/native_aot/hello_string.html</guid><link>https://ericsink.com/native_aot/hello_string.html</link><pubDate>Wed, 25 Jan 2023 11:00:00 CST</pubDate><description><![CDATA[
<p style="text-align: center; font-style: italic">
This is part of a series on Native AOT.<br/>
<a href="mul_cpp_win_static.html">Previous</a> -- <a href="index.html">Top</a> -- <a href="objects_gotcha.html">Next</a>
</p>
<hr/>

<p>So far, our examples have been very simplistic,
using only integer types.  Native AOT won't be 
very useful if we can never use objects.</p>

<p>And we can.  We just need to express them in
terms of the conventions of C.</p>

<p>Actually, the techniques for dealing with
.NET objects in unmanaged code are not new.
We've had good interop capabilities
much longer than we've had Native AOT.
The primary way to pass an object reference to unmanaged code
is a <code>GCHandle</code>, and it first appeared
in .NET Framework about 20 years ago.</p>

<p>If you are already familiar with <code>GCHandle</code>,
please bear with me as I explain things from first
principles in the context of Native AOT.</p>

<p>In many languages, memory is managed manually.
If you need a bit of memory, you have to ask for it,
and when you are done, you have to release it.  In C, the
standard library functions for this are called <code>malloc</code>
and <code>free</code>.  The need to carefully manage
memory has been the source of countless bugs.</p>

<p>The memory for .NET objects is managed automatically
by a Garbage Collector (GC).  When you construct an object,
memory is allocated, but you don't have to worry about
explicitly releasing it.  The .NET runtime keeps track of
things for you, and when a block of memory is no longer
being used, it is classified as "garbage" and freed.</p>

<p>This works because .NET knows about all objects.
But if you want to store an object reference somewhere
that .NET cannot see, then the GC doesn't know about
that reference, so it might decide the object is
garbage, and your reference would become invalid.</p>

<p>This is the problem a <code>GCHandle</code> is designed to solve.
We can create a <code>GCHandle</code> for any object, and when we
do so, we are telling the GC that "as long as this handle
exists, the object is not garbage".  The <code>GCHandle</code> can
be passed into unmanaged code and stored in unmanaged
memory.</p>

<p>The following Native AOT function returns an object (a string):</p>

<pre class="screen">
[UnmanagedCallersOnly(EntryPoint = "get_hello_string")]
public static IntPtr GetHelloString()
{
    string s = "Hello World";
    GCHandle h = GCHandle.Alloc(s);
    return GCHandle.ToIntPtr(h);
}
</pre>

<ul>
    <li><p>Start with a string value.</p></li>
    <li><p>Construct a <code>GCHandle</code> for that string using <code>GCHandle.Alloc()</code>.</p></li>
    <li><p>Convert the <code>GCHandle</code> to an integer representation using <code>GCHandle.ToIntPtr()</code>.</p></li>
    <li><p>Because the string variable is local to the function, it would become garbage when the function returns, but the <code>GCHandle</code> prevents that.</p></li>
</ul>

<p>An <code>IntPtr</code> is an integer that is the same size as a pointer.  
On most modern systems, that'll be 64 bits.  On 32-bit systems, pointers are 32 bits wide, so <code>IntPtr</code> is as well.</p>

<p>In any case, an <code>IntPtr</code> is an integer, so we can return it across the Native AOT boundary, 
where it can be used by unmanaged code
in whatever way we like.</p>

<p>Well, actually, the unmanaged code can't do much with it at all.  The <code>IntPtr</code> is "opaque".  
It's probably the numerical address
of a block of memory, but it doesn't have to be, and even if it is, we're not
supposed to modify that memory or even look at it.</p>

<p>The only thing we can do with our
<code>IntPtr</code> is give it back to the .NET code and ask it to do something.  
But that opens lots of possibilities.</p>

<p>Here's a Native AOT function that retrieves the length
of a string:</p>

<pre class="screen">
[UnmanagedCallersOnly(EntryPoint = "get_string_length")]
public static int GetStringLength(IntPtr v)
{
    GCHandle h = GCHandle.FromIntPtr(v);
    object ob = h.Target;
    string s = (string) ob;
    int len = s.Length;
    return len;
}
</pre>

<p>This is the typical pattern when we have an object handle
in unmanaged code and we pass it back to .NET and ask it to
do something.</p>

<ul>
    <li><p>Start with the <code>IntPtr</code></p></li>
    <li><p>Convert it back to a <code>GCHandle</code> with <code>GCHandle.FromIntPtr</code></p></li>
    <li><p>Retrieve the underlying object with the <code>Target</code> property of the <code>GCHandle</code></p></li>
    <li><p>Cast the object to the type we expect it to be</p></li>
    <li><p>Do something</p></li>
</ul>

<p>So far, we've seen one code snippet that converts an object to an <code>IntPtr</code>, 
and one code snippet that converts an <code>IntPtr</code> back to an object.
But it's quite common to need both in the same function.
Here's a Native AOT function that accepts a string
and returns another string made by calling <code>String.Replace()</code>.</p>

<pre class="screen">
[UnmanagedCallersOnly(EntryPoint = "banish_letter_l")]
public static IntPtr BanishLetterL(IntPtr v)
{
    var s = (string) GCHandle.FromIntPtr(v).Target;
    var s2 = s.Replace("l", "NOT");
    return GCHandle.ToIntPtr(GCHandle.Alloc(s2));
}
</pre>

<p>It is important to remember that every <code>GCHandle</code>
must be released.
So, if we're going to return objects from Native AOT functions,
we must also provide something like the following:</p>

<pre class="screen">
[UnmanagedCallersOnly(EntryPoint = "free_object_handle")]
public static void FreeObjectHandle(IntPtr v)
{
    GCHandle h = GCHandle.FromIntPtr(v);
    h.Free();
}
</pre>

<p>The <code>GCHandle</code> concept is a way
of bridging the gap between the automatic memory management
of .NET and the world where memory is managed manually.
Like most any other form of manual memory management,
<code>GCHandle</code> is very unforgiving.  If we
don't release a handle, the object will never be freed,
and we get a memory leak.  If we release a handle more
than once, or if we release a handle that does not exist,
we are likely to cause memory corruption.</p>

<p>Finally, the C++ code below shows an example of how
to call the functions shown above.</p>

<pre class="screen">
#include &lt;cstdint&gt;
#include &lt;stdio.h&gt;

extern "C" uintptr_t get_hello_string();
extern "C" int32_t get_string_length(uintptr_t);
extern "C" uintptr_t banish_letter_l(uintptr_t);
extern "C" void free_object_handle(uintptr_t);

int main()
{
    // the original string is "Hello World"
    uintptr_t s1 = get_hello_string();

    // the length of the original string is 11
    int32_t len1 = get_string_length(s1);
    printf("%d\n", len1);

    // the new string should be "HeNOTNOTo WorNOTd"
    uintptr_t s2 = banish_letter_l(s1);

    // the length of the new string is now 17
    int32_t len2 = get_string_length(s2);
    printf("%d\n", len2);

    // need to release both string objects
    free_object_handle(s1);
    free_object_handle(s2);

    return 0;
}
</pre>

<p>Two final thoughts about the code sample for this chapter:</p>

<ul>
    <li><p>To keep things focused, I'm still giving no attention to proper error handling,
        but that topic needs to be discussed.</p></li>

    <li><p>So far, all the samples have been shown on Windows, even though they are inherently cross-platform.
        For the sake of mixing things up, this one is configured for Linux.</p></li>
</ul>

<hr/>
<p>The code for this blog entry is available at:</p>

<p><a href="https://github.com/ericsink/native-aot-samples/tree/main/hello_string">https://github.com/ericsink/native-aot-samples/tree/main/hello_string</a></p>

]]></description></item><item><title>Static libraries</title><guid>https://ericsink.com/native_aot/mul_cpp_win_static.html</guid><link>https://ericsink.com/native_aot/mul_cpp_win_static.html</link><pubDate>Mon, 23 Jan 2023 10:00:00 CST</pubDate><description><![CDATA[
<p style="text-align: center; font-style: italic">
This is part of a series on Native AOT.<br/>
<a href="mul_cs_pinvoke.html">Previous</a> -- <a href="index.html">Top</a> -- <a href="hello_string.html">Next</a>
</p>

<hr/>

<p>Buckle up folks, this part of the ride gets a little bumpy.
As of .NET 7, using Native AOT with static libraries is
implemented, but is not yet considered a supported and documented
feature.</p>

<p>As mentioned previously, building a static library with Native AOT
is straightforward. Just set the
<code>NativeLib</code> property to <code>static</code>:</p>

<pre class="screen">
$ dotnet publish <span style="color: red">--property NativeLib=Static</span> -r win-x64
</pre>

<p>And the code to call our <code>multiply()</code> function is
no more complicated than it would normally be for whatever language
is in play.  In this sample, we're using C++:</p>

<pre class="screen">
#include &lt;cstdint&gt;
#include &lt;stdio.h&gt;

extern "C" int32_t multiply(int32_t, int32_t);

int main()
{
    int32_t c = multiply(7, 6);
    printf("%d\n", c);
    return 0;
}
</pre>

<p>Where things get tricky is when we try to link.</p>

<p>To be fair, the various command line options for any
C++ compiler are usually arcane and complicated,
so we'll try to blame Native AOT only for the complexity
that it added to an already messy situation.</p>

<p>Using the Microsoft C++ compiler, our first attempt to build the 
program shown above might look something like the following: </p>

<pre class="screen">
cl.exe 
    (compiler options)
    mul_main.cpp
    ../mul_cs/bin/Debug/net7.0/win-x64/publish/mul.lib 
    (windows libraris)
</pre>

<p>I've omitted the usual compiler options and the long list of
Windows libraries so we can focus on the two things we really
care about.  What we want is to compile the C++ snippet from
above, and link it with the static library containing our 
<code>multiply()</code> function, built by Native AOT.</p>

<p>Unfortunately, this command will result in the linker
complaining about a bunch of unresolved symbols.
These symbols correspond to the various things that
Native AOT code needs, a stripped-down
form of the .NET runtime.</p>

<p>In principle, our trivial <code>multiply()</code> function
should not need that stuff, but it is a degenerate case.
There are settings we could have used to
omit certain dependencies, but we didn't use those settings,
so Native AOT has assumed that this library will need all
the usual stuff.</p>

<p>So we need to add a bunch of static libraries to our link.
When Native AOT building a dynamic library, this chore is performed
automatically, because the .dll (or .so or .dylib) is built by
the linker, but with a static library, we're deferring
the actual link step until later.</p>

<p>But what libraries do we need?  And where are they?</p>

<p>A few chapters ago, when we set things up for building
with Native AOT, we had to add the <code>PublishAOT</code>
property to our <code>csproj</code>.</p>

<p>When the .NET SDK sees this property, it does a lot of work
behind the scenes, including adding references to several
nuget packages which contain the tools and libraries that 
Native AOT needs.  In the case of Windows, those libraries
are in the nuget package 
<code>runtime.win-x64.microsoft.dotnet.ilcompiler</code>.</p>

<p>I mentioned above that none of this is considered to be
a supported feature for .NET 7.  In fact, the currently 
<a href="https://github.com/dotnet/samples/commit/3870722f5c5e80fd6a70946e6e96a5c990620e42">suggested</a>
way to figure this stuff out is to run a Native AOT build with
detailed logging and examine the output to see the actual
command line options for the C++ compiler.
That gives us the list of libraries, plus the fact that we
need to require the <code>NativeAOT_StaticInitialization</code> symbol.</p>

<p>The final command line is just too ugly to show in a
blog entry, but all the gory detail is in a BAT file in
the sample code.</p>

<p>We end up with one self-contained executable file (which is still
ridiculously large, grumble).</p>

<pre class="screen">
C:\Users\eric\dev\native-aot-samples\mul_cpp_win_static&gt; dir

 Directory of C:\Users\eric\dev\native-aot-samples\mul_cpp_win_static

...
01/23/2023  09:53 AM         4,763,136 mul_main.exe
...

C:\Users\eric\dev\native-aot-samples\mul_cpp_win_static&gt; .\mul_main.exe
42
</pre>

<hr/>
<p>The code for this blog entry is available at:</p>

<p><a href="https://github.com/ericsink/native-aot-samples/tree/main/mul_cpp_win_static">https://github.com/ericsink/native-aot-samples/tree/main/mul_cpp_win_static</a></p>

]]></description></item><item><title>Silly</title><guid>https://ericsink.com/native_aot/mul_cs_pinvoke.html</guid><link>https://ericsink.com/native_aot/mul_cs_pinvoke.html</link><pubDate>Wed, 18 Jan 2023 13:00:00 CST</pubDate><description><![CDATA[
<p style="text-align: center; font-style: italic">
This is part of a series on Native AOT.<br/>
<a href="mul_rs_win_dynamic.html">Previous</a> -- <a href="index.html">Top</a> -- <a href="mul_cpp_win_static.html">Next</a>
</p>

<hr/>

<p>Native AOT produces libraries that can be called from
any language that can call C.</p>

<p>C# is one such language.</p>

<p>Yes folks, that's right -- even though it is [probably] not useful, 
you can call into a Native AOT library from C# using P/Invoke:</p>

<pre class="screen">
using System;
using System.Runtime.InteropServices;

static class MulCsPinvoke
{
    [DllImport("mul")]
    static extern int multiply(int a, int b);

    public static void Main()
    {
        var c = multiply(7, 6);

        Console.WriteLine($"{c}");
    }
}
</pre>

<p>After we copy mul.dll into place, we can run it:</p>

<pre class="screen">
$ cp ../mul_cs/bin/Debug/net7.0/win-x64/publish/mul.dll .

$ dotnet run
42
</pre>

<hr/>
<p>The code for this blog entry is available at:</p>

<p><a href="https://github.com/ericsink/native-aot-samples/tree/main/mul_cs_pinvoke">https://github.com/ericsink/native-aot-samples/tree/main/mul_cs_pinvoke</a></p>


]]></description></item><item><title>Multiplying two integers from Rust</title><guid>https://ericsink.com/native_aot/mul_rs_win_dynamic.html</guid><link>https://ericsink.com/native_aot/mul_rs_win_dynamic.html</link><pubDate>Wed, 18 Jan 2023 12:00:00 CST</pubDate><description><![CDATA[
<p style="text-align: center; font-style: italic">
This is part of a series on Native AOT.<br/>
<a href="mul_cs.html">Previous</a> -- <a href="index.html">Top</a> -- <a href="mul_cs_pinvoke.html">Next</a>
</p>
<hr/>

<p>Rust has a really nice feature called raw-dylib,
which allows calling external functions without linking
at build time.  Given the name of the shared library,
Rust will dynamically load the library and lookup the function.
(At the time of this writing, raw-dylib is supported only
on Windows.)</p>

<p>So, because of this raw-dylib feature, Rust is one of the simpler examples
of how to invoke our trivial <code>multiply()</code> function.  We
just need to declare the <code>extern</code> function signature and include
a <code>link</code> attribute to let Rust know that
the function should be found in a shared library with the base name "mul":</p>

<pre class="screen">
#[link(name="mul", kind="raw-dylib")]
extern {
    fn multiply(a : i32, b : i32) -&gt; i32;
}
</pre>

<p>FWIW, raw-dylib in Rust is basically the same feature
as P/Invoke in .NET:</p>

<pre class="screen">
[DllImport("mul")]
static extern int multiply(int a, int b);
</pre>

<p>Anyway, once the declaration is in place, we can call
the function from Rust, keeping in mind that Rust considers
any FFI function to be unsafe:</p>

<pre class="screen">
fn main() {
    let c = unsafe { multiply(7, 6) };

    println!("{}", c);
}
</pre>

<p>Of course, the program will complain if it can't find the dynamic library:</p>

<pre class="screen">
$ cargo run
   Compiling mul_rs_win_dynamic v0.1.0 (C:\Users\eric\dev\native-aot-samples\mul_rs_win_dynamic)
    Finished dev [unoptimized + debuginfo] target(s) in 0.41s
     Running `target\debug\mul_rs_win_dynamic.exe`
error: process didn't exit successfully: `target\debug\mul_rs_win_dynamic.exe` 
    (exit code: 0xc0000135, <span style="color: red">STATUS_DLL_NOT_FOUND</span>)
C:/Users/eric/.cargo/bin/cargo.exe: error while loading shared libraries: 
    ?: cannot open shared object file: No such file or directory
</pre>

<p>But once mul.dll (built with Native AOT in the <a href="mul_cs.html">previous chapter</a>)
is copied into place, we can finally multiply two numbers from Rust:</p>

<pre class="screen">
$ cp ../mul_cs/bin/Debug/net7.0/win-x64/publish/mul.dll .

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target\debug\mul_rs_win_dynamic.exe`
42
</pre>

<hr/>
<p>The code for this blog entry is available at:</p>

<p><a href="https://github.com/ericsink/native-aot-samples/tree/main/mul_rs_win_dynamic">https://github.com/ericsink/native-aot-samples/tree/main/mul_rs_win_dynamic</a></p>


]]></description></item></channel></rss>