Microsoft has introduced a new way to interop with native code in the just-released .NET 7, a fundamental change which improves performance and assists debugging but could also give developers some tough problems figuring out how to port their existing code.
“We’ve decided to go in a new direction” said Jeremy Koritzinsky, senior software engineer on the .NET interop team.
The new approach means that platform invoke code is generated at compile time, not at runtime. This enables ahead of time compilation as well as manual fix-up of interop issues. In addition, the team is removing features which only existed to support Windows.
“.Net came from a very Windows-centric place, and as a result in the interop space there were a lot of Windows-centric rules … we had this weird thing about character sets because old Windows didn’t support Unicode and new Windows did, back when .NET came out in 1999,” said Koritzinsky. Ansi, the Windows character set in pre-Unicode days, “has fallen to a second-class citizen,” he said, though support is still available if needed. In the new interop world, UTF-16 and UTF-8 are the primary character encodings. A new attribute, DisableRuntimeMarshalling, “allows us to wash away some old weird behaviours we had from early in .NET,” added Koritzinsky.
As noted by the Microsoft engineer, the design of .NET interop, also known as Platform Invoke, goes back to the earliest days of .NET. All code has to call native operating system code eventually, but for the most part the languages and frameworks handle this behind the scenes so developers do not have to think about it, until, that is, they need to use some feature of the operating system that is otherwise inaccessible, or to use a native code library compiled from a different language such as C or C++. At this point interop becomes a necessity.
Unfortunately, it is complicated, leading .NET developers to reach for a book like Adam Nathan’s 1500-page tome .NET and COM (which also covers native code interop), just as old-style Visual Basic coders used to thumb through Daniel Appleman’s 1000-page VB Programmer’s Guide to the Windows API – still of some relevance to Visual Basic for Applications (VBA) developers – to figure out the magic that would let them call into a native code library.
In C# interop has until now worked by adding a DLLImport attribute to a function, which tells .NET that the implementation is in an external library. The challenge is to find the correct additional annotations to perform marshalling, which is the business of passing .NET types to the native library in the format it expects, and then converting the return value to the format that .NET expects. Breakage tends to be unforgiving.
.NET 7 replaces DLLImport with a new attribute called LibraryImport. It sounds similar but it is not. LibraryImport instructs the compiler to invoke a Roslyn Source Generator to create C# code that implement the native function wrapper. Currently it only works with C#, not Visual Basic. This code is part of the project and can be inspected via a right-click Go To Definition. “We’re going to use Roslyn source generators to generate this goo code which means you can easily diagnose and understand and step through,” said Koritzinsky. If it does not work quite right, developers can copy it, paste into a new function, and tweak it to fix any problems.
The snag here is that a non-trivial case may well not work out of the box, and being new, there may be less immediate help on LibraryImport than with the battle-tested DLLImport. Documentation is here. Fortunately DLLImport still works though, so despite the performance and debugging advantages, it would not be surprising if most developers with working code prefer to leave LibraryImport to one side for the time being.
That said, and despite Microsoft making little noise about it, this is a huge change in .NET and one that promises to further decouple it from Windows as well as improving performance. The full presentation at .NET Conf is here.