Friday, March 22, 2024

NET within NET

Amazing discovery of the day: you can host .NET 5+ within a .NET 4 application.

You can't plain reference a .NET 5+ assembly from a .NET 4.x (aka ".NET Framework") one and expect it to work. The frameworks are so different that Microsoft thought a rebrand/rename (to "Core") was warranted at the time. They went back on that later and returned to the .NET branding, but that's beside the point. The point is, straightforward calls from NET 4 to Core-targeting code don't work.

Enter hostfxr, the trusty DLL for hosting .NET Core 3+ from native code. You can P/Invoke into that, load a .NET 5+ (let's call it "Core") assembly and call a static public class method. The only caveat is, there has to be a public delegate datatype with the same signature as the method in question, declared in the Core world. If there isn't, the method has to follow a certain preset signature. Point is, it's not for calling assemblies that were not built with cross-version calling in mind in the first place.

This is decidedly a low level approach. It's also possible to call Core from Framework using good old COM. Again, the Core assembly has to be built with COM support.

The motivating use case for this was - I wanted to build a Visual Studio add-in that would inspect the compiled binary in a .NET 8 projects. The add-ins run under .NET 4 (along with the rest of Visual Studio). I have a strong suspicion Visual Studio itself already does a similar trick - when you create a .NET hosting context, on some runs I've seen the result code that means "context already exists".

UPDATE: the requirement that the callee method adheres to a publicly declared delegate type, and that the assembly qualified name for the latter must be passed to hostfxr, can't be sidestepped with passing  a specialization of an Action<...> or a Func<...>. It's possible to get Type objects for generic datatype specializations, and they have assembly qualified names, too, but passing a name like this doesn't work in this context.

This, for example, is an assembly qualified name for a Func<string, string> in a .NET 8 project:

System.Func`2[[System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e






No comments:

Post a Comment