-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Description
Summary of the new feature/enhancement
This issue is linked to Native Host using existing PowerShell 7 installation that was originally opened in dotnet/runtime. However, all solution paths in the core runtime are now exhausted, leaving only a possible solution implemented on the PowerShell side instead.
Native applications can interact with PowerShell through the command-line interface (pwsh.exe), but this approach is very limited as opposed to using (Microsoft.PowerShell.SDK) in a .NET application. The goal of this feature request is to expose an SDK-like interface to native applications written in C/C++ or Rust using only an existing PowerShell 7 installation, loaded and hosted dynamically at runtime, with no additional files outside of PowerShell.
A working proof-of-concept was already completed, but it requires an additional assembly file that needs to be distributed outside of PowerShell. This assembly file contains a single helper function used to load the real Microsoft.PowerShell.SDK native-to-managed function bindings in memory. Many solution paths were explored with the .NET team in the original issue, but none appears acceptable at this point. Since the .NET team won't provide a way to load additional assemblies in-memory from the hostfxr APIs, the proposed solution would be to expose the required helper function inside PowerShell.
Proposed technical implementation details (optional)
The PowerShell native host proof-of-concept does the following:
- Find the PowerShell installation path (directory where pwsh.exe is)
- Find the corresponding hostfxr native library (libhostfxr.so)
- Load the hostfxr library + hostfxr API functions dynamically
- Load the pwsh.dll runtimeconfig.json file with hostfxr APIs
- Load managed assembly containing SDK bindings in memory
- Call PowerShell SDK functions from native code through bindings
The loading of the managed assembly containing the SDK bindings is currently done using a helper assembly file containing a single function:
public static void LoadAssemblyData(IntPtr ptr, int size)
{
byte[] bytes = new byte[size];
Marshal.Copy(ptr, bytes, 0, size);
Stream stream = new MemoryStream(bytes);
AssemblyLoadContext.Default.LoadFromStream(stream);
}
This LoadAssemblyData function could be exported by any of the PowerShell assemblies, and would be accessible to native applications seeking to load their own bindings in-memory. The hostfxr APIs are relatively small and only allow calls to functions with primitive data types. Native applications can call any managed functions given the proper bindings, but bindings can currently only be loaded from files, making it impossible to embed as resources inside an application or a library.
On the native application side, the build process would look like the following:
- Generate C# bindings
- Generate C/C++/Rust bindings
- Compile native code
- Embed managed bindings
At runtime, the native application only has to load its managed bindings in-memory before calling the native wrapper functions that use them. Exposing a function meant to load managed bindings in-memory is preferred over exposing a larger set of native wrappers, because it only has to be done once without modifying PowerShell ever again, making it a very simple, highly decoupled change.
Imagine publishing a "pwsh_host" Rust library on crates.io, where any Rust application could magically load and execute PowerShell with no link-time dependencies, this is what this feature would unlock. We're 99% there, I just need a fix for the last step!