This post was authored by Morgan Brown, a Software Development Engineer on the .NET Native team. It is the first post in a series of five about Runtime Directives.
If you’ve read our previous blog posts on .NET Native, you might be wondering how all of this works. We’ve discussed the performance benefits of .NET Native and dug into the tool-chain but we never answered a basic question: how does .NET Native do dynamic things when it’s compiling your code statically? A big part of the reason developers love .NET is the fact that you can do things like reflect on your code and call code on the fly. But when you’re compiling all your code statically, as .NET Native does today, how does the compiler know what code your app might want to call at runtime? The answer is something called Runtime Directives.
It’s actually our goal to make sure you never need to look at Runtime Directives—our compiler tries to predict what your program will need at runtime—but if you’re interested in knowing how .NET Native allows dynamic features in static code, you have to understand how it uses Runtime Directives. And if you’re using .NET Native while it’s still in preview, this information might be useful for diagnosing issues.
This first blog post is an introduction to Runtime Directives. We’ll be publishing four additional posts on Runtime Directives:
- Two posts on diagnosing issues that might come up while using .NET Native Preview
- One post on writing Runtime Directives for a library
- One post on how you can customize Runtime Directives to optimize your app
Why Runtime Directives?
First, we need to understand the differences between static compilation and dynamic compilation. In a statically compiled app, all code that will ever run gets compiled ahead of time. That’s how C++ compilers work. In contrast, dynamic compilation means there’s a compiler in the app’s runtime. In the .NET runtime, that’s the JIT compiler. Having dynamic compilation is extremely powerful since it lets your app call code on the fly using reflection. Many libraries and apps take advantage of that to provide functionality such as serialization and UI frameworks. So why build static compilation into .NET Native? Static compilation lets the compiler spend more time on advanced optimizations and doesn’t require it to run on the end user’s device.
How does .NET Native let you call any code through reflection and still stick to static compilation? In part, the .NET Native compiler has a number of algorithms to predict what classes and methods your code might dynamically use. To cover the situations that the compiler doesn’t pick up on its own, .NET Native Compilation includes a new file format for Runtime Directives you can add to your app or library called rd.xml.
What’s in a Directive?
There are lots of things that can go in a Runtime Directives document and we won’t go through the whole list here (they’re all documented under the Runtime Directives Configuration File Reference topic on MSDN), but let’s talk about a couple of terms that will help you write the documents (and that we’ll use in other blog entries about Runtime Directives).
When you create a .NET Native project, Visual Studio adds an rd.xml that looks something like this:
<!-- Your directives go here -->
A directive is a line of XML that talks about a type (a class, interface, or struct), a group of types (a namespace or assembly), or a type member (a method, field, property, or event). For example,
<Type Name="MyNamespace.MyClass" />
talks about just one class while
<Namespace Name="MyNamespace" />
talks about all types in MyNamespace.
Just naming a type or member alone isn’t enough to have any effect. To get the compiler to add new runtime behaviors for it, you also need to include one or more degrees. A degree is an XML attribute that declares what you’re planning on doing with that type or member at runtime. For example, the Dynamic degree means that you’re planning to create objects or invoke methods dynamically (like the C# dynamic keyword might – that’s one of the cases where you’d use the Dynamic degree). The DataContractSerializer degree means that you’ll serialize a class using the DataContractSerializer.
There’s also one other part of a degree – a policy, which says how to apply the degree to the type or member. Policies are expressed as the value of the XML attribute. A policy contains two things:
- Accessibility – This determines how this degree is applied. For example, if you specify “Public” on a type, then only its public members will get the directive applied. If you specify “All”, then public, internal, and private members will get the directive applied.
- Required (or not) – The word “Required” can be included or not. If you include “Required”, it means that the type or member is always included in the final binary, even though nothing statically needs it. Skipping it is mostly useful on things like namespaces and assemblies where you might not need every single member of a namespace, but plan to reflect over the ones that are getting used by something else anyway.
Full documentation on Runtime Directive Policy Settings is available on MSDN. Intellisense also provides tooltips within Visual Studio.
Here are a couple examples:
<Type Name="MyNamespace.MyClass" Dynamic="Public" />
This means that if MyClass gets used by something else in my program, then make it available to dynamic reflection. Also, make its public members (but not its internal or private members) available to reflection if they’re used by something else in the app.
<Namespace Name="MyNamespace" Serialize="Required All" />
For all types in MyNamespace, make sure they’re all kept and include the reflection data usually used by reflection-based serializers (such as many NuGet serialization libraries).
There’s a lot of great information coming up in our series of deep dives into how .NET Native using Runtime Directives. We hope you find these blog posts useful—let us know what you think about Runtime Directives. And we always welcome your feedback on .NET Native. You can leave comments on this post or send mail to firstname.lastname@example.org.