From 39e930d9aaff250e0fd5019eeedaa40717a6c6fe Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Wed, 29 Apr 2020 19:28:50 +1000 Subject: Add DotNetCoreBootstrapperApplicationHost for an SCD-style .NET Core BA. --- src/Custom.Build.props | 8 +- .../BootstrapperApplicationFactory.cs | 87 +++++ .../DnchostAssemblyLoadContext.cs | 58 ++++ src/WixToolset.Dnc.Host/Exceptions.cs | 145 +++++++++ .../IBootstrapperApplicationFactory.cs | 16 + src/WixToolset.Dnc.Host/NativeMethods.cs | 18 ++ src/WixToolset.Dnc.Host/WixToolset.Dnc.Host.csproj | 38 +++ .../BootstrapperApplicationFactory.cs | 3 +- src/WixToolset.Mba.Host/WixToolset.Mba.Host.csproj | 6 +- src/dnchost/coreclrhost.h | 137 ++++++++ src/dnchost/dnchost.cpp | 229 +++++++++++++ src/dnchost/dnchost.def | 6 + src/dnchost/dnchost.h | 19 ++ src/dnchost/dnchost.vcxproj | 77 +++++ src/dnchost/dncutil.cpp | 359 +++++++++++++++++++++ src/dnchost/dncutil.h | 36 +++ src/dnchost/hostfxr.h | 96 ++++++ src/dnchost/packages.config | 8 + src/dnchost/precomp.cpp | 3 + src/dnchost/precomp.h | 29 ++ .../WixToolsetTest.ManagedHost/DncHostFixture.cs | 232 +++++++++++++ .../HarvestDirectoryToPayloadGroup.ps1 | 42 +++ .../TestData/EarliestCoreMBA/HarvestedSCD.wxs | 235 ++++++++++++++ .../EarliestCoreMBA/HarvestedTrimmedSCD.wxs | 113 +++++++ .../EarliestCoreMBA/SelfContainedBundle.wxs | 12 + .../EarliestCoreMBA/TrimmedSelfContainedBundle.wxs | 12 + .../TestData/LatestCoreMBA/HarvestedSCD.wxs | 235 ++++++++++++++ .../TestData/LatestCoreMBA/HarvestedTrimmedSCD.wxs | 113 +++++++ .../TestData/LatestCoreMBA/SelfContainedBundle.wxs | 12 + .../LatestCoreMBA/TrimmedSelfContainedBundle.wxs | 12 + .../WixToolsetTest.ManagedHost.csproj | 30 ++ .../examples/EarliestCoreMBA/EarliestCoreBA.cs | 34 ++ .../EarliestCoreMBA/EarliestCoreBAFactory.cs | 22 ++ .../EarliestCoreMBA/Example.EarliestCoreMBA.csproj | 18 ++ .../LatestCoreMBA/Example.LatestCoreMBA.csproj | 23 ++ src/test/examples/LatestCoreMBA/LatestCoreBA.cs | 33 ++ .../examples/LatestCoreMBA/LatestCoreBAFactory.cs | 22 ++ src/test/examples/TestEngine/TestEngine.cpp | 16 +- src/test/examples/TestEngine/precomp.h | 1 + src/wixext/BalCompiler.cs | 9 + src/wixext/Tuples/BalTupleDefinitions.cs | 15 + src/wixext/Tuples/WixBalBAFactoryAssemblyTuple.cs | 47 +++ src/wixext/bal.xsd | 17 +- src/wixlib/Dnc.wxs | 21 ++ src/wixlib/bal.wixproj | 5 + 45 files changed, 2700 insertions(+), 9 deletions(-) create mode 100644 src/WixToolset.Dnc.Host/BootstrapperApplicationFactory.cs create mode 100644 src/WixToolset.Dnc.Host/DnchostAssemblyLoadContext.cs create mode 100644 src/WixToolset.Dnc.Host/Exceptions.cs create mode 100644 src/WixToolset.Dnc.Host/IBootstrapperApplicationFactory.cs create mode 100644 src/WixToolset.Dnc.Host/NativeMethods.cs create mode 100644 src/WixToolset.Dnc.Host/WixToolset.Dnc.Host.csproj create mode 100644 src/dnchost/coreclrhost.h create mode 100644 src/dnchost/dnchost.cpp create mode 100644 src/dnchost/dnchost.def create mode 100644 src/dnchost/dnchost.h create mode 100644 src/dnchost/dnchost.vcxproj create mode 100644 src/dnchost/dncutil.cpp create mode 100644 src/dnchost/dncutil.h create mode 100644 src/dnchost/hostfxr.h create mode 100644 src/dnchost/packages.config create mode 100644 src/dnchost/precomp.cpp create mode 100644 src/dnchost/precomp.h create mode 100644 src/test/WixToolsetTest.ManagedHost/DncHostFixture.cs create mode 100644 src/test/WixToolsetTest.ManagedHost/HarvestDirectoryToPayloadGroup.ps1 create mode 100644 src/test/WixToolsetTest.ManagedHost/TestData/EarliestCoreMBA/HarvestedSCD.wxs create mode 100644 src/test/WixToolsetTest.ManagedHost/TestData/EarliestCoreMBA/HarvestedTrimmedSCD.wxs create mode 100644 src/test/WixToolsetTest.ManagedHost/TestData/EarliestCoreMBA/SelfContainedBundle.wxs create mode 100644 src/test/WixToolsetTest.ManagedHost/TestData/EarliestCoreMBA/TrimmedSelfContainedBundle.wxs create mode 100644 src/test/WixToolsetTest.ManagedHost/TestData/LatestCoreMBA/HarvestedSCD.wxs create mode 100644 src/test/WixToolsetTest.ManagedHost/TestData/LatestCoreMBA/HarvestedTrimmedSCD.wxs create mode 100644 src/test/WixToolsetTest.ManagedHost/TestData/LatestCoreMBA/SelfContainedBundle.wxs create mode 100644 src/test/WixToolsetTest.ManagedHost/TestData/LatestCoreMBA/TrimmedSelfContainedBundle.wxs create mode 100644 src/test/examples/EarliestCoreMBA/EarliestCoreBA.cs create mode 100644 src/test/examples/EarliestCoreMBA/EarliestCoreBAFactory.cs create mode 100644 src/test/examples/EarliestCoreMBA/Example.EarliestCoreMBA.csproj create mode 100644 src/test/examples/LatestCoreMBA/Example.LatestCoreMBA.csproj create mode 100644 src/test/examples/LatestCoreMBA/LatestCoreBA.cs create mode 100644 src/test/examples/LatestCoreMBA/LatestCoreBAFactory.cs create mode 100644 src/wixext/Tuples/WixBalBAFactoryAssemblyTuple.cs create mode 100644 src/wixlib/Dnc.wxs (limited to 'src') diff --git a/src/Custom.Build.props b/src/Custom.Build.props index f4352014..66e74d81 100644 --- a/src/Custom.Build.props +++ b/src/Custom.Build.props @@ -2,8 +2,10 @@ - - $(OutputPath)examples\$(ProjectName)\ - $(OutDir)examples\$(ProjectName)\ + + $(OutputPath)examples\$(ProjectName)\ + + + $(OutDir)examples\$(ProjectName)\ diff --git a/src/WixToolset.Dnc.Host/BootstrapperApplicationFactory.cs b/src/WixToolset.Dnc.Host/BootstrapperApplicationFactory.cs new file mode 100644 index 00000000..0c6ea367 --- /dev/null +++ b/src/WixToolset.Dnc.Host/BootstrapperApplicationFactory.cs @@ -0,0 +1,87 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Dnc.Host +{ + using System; + using System.Linq; + using System.Reflection; + using System.Runtime.InteropServices; + + /// + /// Entry point for the .NET Core host to create and return the BA to the engine. + /// Reflection is used instead of referencing WixToolset.Mba.Core directly to avoid requiring it in the AssemblyLoadContext. + /// + public sealed class BootstrapperApplicationFactory : IBootstrapperApplicationFactory + { + private string baFactoryAssemblyName; + private string baFactoryAssemblyPath; + + public BootstrapperApplicationFactory(string baFactoryAssemblyName, string baFactoryAssemblyPath) + { + this.baFactoryAssemblyName = baFactoryAssemblyName; + this.baFactoryAssemblyPath = baFactoryAssemblyPath; + } + + /// + /// Loads the bootstrapper application assembly and calls its IBootstrapperApplicationFactory.Create method. + /// + /// Pointer to BOOTSTRAPPER_CREATE_ARGS struct. + /// Pointer to BOOTSTRAPPER_CREATE_RESULTS struct. + /// The bootstrapper application assembly + /// does not define the . + public void Create(IntPtr pArgs, IntPtr pResults) + { + // Load the BA's IBootstrapperApplicationFactory. + var baFactoryType = BootstrapperApplicationFactory.GetBAFactoryTypeFromAssembly(this.baFactoryAssemblyName, this.baFactoryAssemblyPath); + var baFactory = Activator.CreateInstance(baFactoryType); + if (null == baFactory) + { + throw new InvalidBootstrapperApplicationFactoryException(); + } + + var createMethod = baFactoryType.GetMethod(nameof(Create), new[] { typeof(IntPtr), typeof(IntPtr) }); + if (null == createMethod) + { + throw new InvalidBootstrapperApplicationFactoryException(); + } + createMethod.Invoke(baFactory, new object[] { pArgs, pResults }); + } + + /// + /// Locates the and returns the specified type. + /// + /// The assembly that defines the IBootstrapperApplicationFactory implementation. + /// The bootstrapper application factory . + private static Type GetBAFactoryTypeFromAssembly(string assemblyName, string assemblyPath) + { + // The default ALC shouldn't need help loading the assembly, since the host should have provided the deps.json + // when starting the runtime. But it doesn't hurt so keep this in case an isolated ALC is ever needed. + var alc = new DnchostAssemblyLoadContext(assemblyPath, false); + var asm = alc.LoadFromAssemblyName(new AssemblyName(assemblyName)); + + var attr = asm.GetCustomAttributes() + .Where(a => a.GetType().FullName == "WixToolset.Mba.Core.BootstrapperApplicationFactoryAttribute") + .SingleOrDefault(); + + if (null == attr) + { + throw new MissingAttributeException(); + } + + var baFactoryTypeProperty = attr.GetType().GetProperty("BootstrapperApplicationFactoryType", typeof(Type)); + if (baFactoryTypeProperty == null || baFactoryTypeProperty.GetMethod == null) + { + throw new MissingAttributeException(); + } + + var baFactoryType = (Type)baFactoryTypeProperty.GetMethod.Invoke(attr, null); + return baFactoryType; + } + + // Entry point for the DNC host. + public static IBootstrapperApplicationFactory CreateBAFactory([MarshalAs(UnmanagedType.LPWStr)] string baFactoryAssemblyName, [MarshalAs(UnmanagedType.LPWStr)] string baFactoryAssemblyPath) + { + return new BootstrapperApplicationFactory(baFactoryAssemblyName, baFactoryAssemblyPath); + } + } +} diff --git a/src/WixToolset.Dnc.Host/DnchostAssemblyLoadContext.cs b/src/WixToolset.Dnc.Host/DnchostAssemblyLoadContext.cs new file mode 100644 index 00000000..1a383058 --- /dev/null +++ b/src/WixToolset.Dnc.Host/DnchostAssemblyLoadContext.cs @@ -0,0 +1,58 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Dnc.Host +{ + using System; + using System.Reflection; + using System.Runtime.Loader; + + public sealed class DnchostAssemblyLoadContext : AssemblyLoadContext + { + private readonly AssemblyDependencyResolver resolver; + + public DnchostAssemblyLoadContext(string assemblyPath, bool isolateFromDefault) + : base(nameof(DnchostAssemblyLoadContext), isolateFromDefault) + { + this.resolver = new AssemblyDependencyResolver(assemblyPath); + + if (!this.IsCollectible) + { + AssemblyLoadContext.Default.Resolving += this.ResolveAssembly; + AssemblyLoadContext.Default.ResolvingUnmanagedDll += this.ResolveUnmanagedDll; + } + } + + private Assembly ResolveAssembly(AssemblyLoadContext defaultAlc, AssemblyName assemblyName) + { + var path = this.resolver.ResolveAssemblyToPath(assemblyName); + if (path != null) + { + var targetAlc = this.IsCollectible ? this : defaultAlc; + return targetAlc.LoadFromAssemblyPath(path); + } + + return null; + } + + private IntPtr ResolveUnmanagedDll(Assembly assembly, string unmanagedDllName) + { + var path = this.resolver.ResolveUnmanagedDllToPath(unmanagedDllName); + if (path != null) + { + return this.LoadUnmanagedDllFromPath(path); + } + + return IntPtr.Zero; + } + + protected override Assembly Load(AssemblyName assemblyName) + { + return this.ResolveAssembly(AssemblyLoadContext.Default, assemblyName); + } + + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + return this.ResolveUnmanagedDll(null, unmanagedDllName); + } + } +} diff --git a/src/WixToolset.Dnc.Host/Exceptions.cs b/src/WixToolset.Dnc.Host/Exceptions.cs new file mode 100644 index 00000000..32d4d4c5 --- /dev/null +++ b/src/WixToolset.Dnc.Host/Exceptions.cs @@ -0,0 +1,145 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Dnc.Host +{ + using System; + using System.Runtime.Serialization; + + /// + /// Base class for exception returned to the bootstrapper application host. + /// + [Serializable] + public abstract class BootstrapperException : Exception + { + /// + /// Creates an instance of the base class with the given HRESULT. + /// + /// The HRESULT for the exception that is used by the bootstrapper application host. + public BootstrapperException(int hr) + { + this.HResult = hr; + } + + /// + /// Initializes a new instance of the class. + /// + /// Exception message. + public BootstrapperException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Exception message + /// Inner exception associated with this one + public BootstrapperException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Serialization information for this exception + /// Streaming context to serialize to + protected BootstrapperException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } + + /// + /// The bootstrapper application assembly loaded by the host does not contain exactly one instance of the + /// class. + /// + /// + [Serializable] + public class MissingAttributeException : BootstrapperException + { + /// + /// Creates a new instance of the class. + /// + public MissingAttributeException() + : base(NativeMethods.E_NOTFOUND) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Exception message. + public MissingAttributeException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Exception message + /// Inner exception associated with this one + public MissingAttributeException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Serialization information for this exception + /// Streaming context to serialize to + protected MissingAttributeException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } + + /// + /// The bootstrapper application factory specified by the + /// does not extend the base class. + /// + /// + /// + [Serializable] + public class InvalidBootstrapperApplicationFactoryException : BootstrapperException + { + /// + /// Creates a new instance of the class. + /// + public InvalidBootstrapperApplicationFactoryException() + : base(NativeMethods.E_UNEXPECTED) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Exception message. + public InvalidBootstrapperApplicationFactoryException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Exception message + /// Inner exception associated with this one + public InvalidBootstrapperApplicationFactoryException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Serialization information for this exception + /// Streaming context to serialize to + protected InvalidBootstrapperApplicationFactoryException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/WixToolset.Dnc.Host/IBootstrapperApplicationFactory.cs b/src/WixToolset.Dnc.Host/IBootstrapperApplicationFactory.cs new file mode 100644 index 00000000..96731192 --- /dev/null +++ b/src/WixToolset.Dnc.Host/IBootstrapperApplicationFactory.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Dnc.Host +{ + using System; + using System.Runtime.InteropServices; + + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IBootstrapperApplicationFactory + { + void Create( + IntPtr pArgs, + IntPtr pResults + ); + } +} diff --git a/src/WixToolset.Dnc.Host/NativeMethods.cs b/src/WixToolset.Dnc.Host/NativeMethods.cs new file mode 100644 index 00000000..6dc393c6 --- /dev/null +++ b/src/WixToolset.Dnc.Host/NativeMethods.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Dnc.Host +{ + using System; + using System.Runtime.InteropServices; + + /// + /// Contains native constants, functions, and structures for this assembly. + /// + internal static class NativeMethods + { + #region Error Constants + internal const int E_NOTFOUND = unchecked((int)0x80070490); + internal const int E_UNEXPECTED = unchecked((int)0x8000ffff); + #endregion + } +} diff --git a/src/WixToolset.Dnc.Host/WixToolset.Dnc.Host.csproj b/src/WixToolset.Dnc.Host/WixToolset.Dnc.Host.csproj new file mode 100644 index 00000000..09580c2d --- /dev/null +++ b/src/WixToolset.Dnc.Host/WixToolset.Dnc.Host.csproj @@ -0,0 +1,38 @@ + + + + + + netcoreapp3.0 + WixToolset.Dnc.Host + WiX Toolset .NET Core BA Host + WiX Toolset .NET Core BA Host + + + + + False + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/WixToolset.Mba.Host/BootstrapperApplicationFactory.cs b/src/WixToolset.Mba.Host/BootstrapperApplicationFactory.cs index 9385d1d1..78e68bd9 100644 --- a/src/WixToolset.Mba.Host/BootstrapperApplicationFactory.cs +++ b/src/WixToolset.Mba.Host/BootstrapperApplicationFactory.cs @@ -9,13 +9,14 @@ namespace WixToolset.Mba.Host using WixToolset.Mba.Core; /// - /// Entry point for the MBA host to create and return the BA to the engine. + /// Entry point for the managed host to create and return the BA to the engine. /// [ClassInterface(ClassInterfaceType.None)] public sealed class BootstrapperApplicationFactory : MarshalByRefObject, IBootstrapperApplicationFactory { /// /// Creates a new instance of the class. + /// Entry point for the MBA host. /// public BootstrapperApplicationFactory() { diff --git a/src/WixToolset.Mba.Host/WixToolset.Mba.Host.csproj b/src/WixToolset.Mba.Host/WixToolset.Mba.Host.csproj index 4b257195..65467acc 100644 --- a/src/WixToolset.Mba.Host/WixToolset.Mba.Host.csproj +++ b/src/WixToolset.Mba.Host/WixToolset.Mba.Host.csproj @@ -60,9 +60,9 @@ - - - + + + diff --git a/src/dnchost/coreclrhost.h b/src/dnchost/coreclrhost.h new file mode 100644 index 00000000..07f28735 --- /dev/null +++ b/src/dnchost/coreclrhost.h @@ -0,0 +1,137 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + + + +// ***** ABOUT THIS HEADER ***** +// ************************************************************************************** +// +// This is the version on 2019-12-22 from +// https://github.com/dotnet/runtime/blob/master/src/coreclr/src/coreclr/hosts/inc/coreclrhost.h +// +// ************************************************************************************** +// **************************** + + +// +// APIs for hosting CoreCLR +// + +#ifndef __CORECLR_HOST_H__ +#define __CORECLR_HOST_H__ + +#if defined(_WIN32) && defined(_M_IX86) +#define CORECLR_CALLING_CONVENTION __stdcall +#else +#define CORECLR_CALLING_CONVENTION +#endif + +// For each hosting API, we define a function prototype and a function pointer +// The prototype is useful for implicit linking against the dynamic coreclr +// library and the pointer for explicit dynamic loading (dlopen, LoadLibrary) +#define CORECLR_HOSTING_API(function, ...) \ + extern "C" int CORECLR_CALLING_CONVENTION function(__VA_ARGS__); \ + typedef int (CORECLR_CALLING_CONVENTION *function##_ptr)(__VA_ARGS__) + +// +// Initialize the CoreCLR. Creates and starts CoreCLR host and creates an app domain +// +// Parameters: +// exePath - Absolute path of the executable that invoked the ExecuteAssembly (the native host application) +// appDomainFriendlyName - Friendly name of the app domain that will be created to execute the assembly +// propertyCount - Number of properties (elements of the following two arguments) +// propertyKeys - Keys of properties of the app domain +// propertyValues - Values of properties of the app domain +// hostHandle - Output parameter, handle of the created host +// domainId - Output parameter, id of the created app domain +// +// Returns: +// HRESULT indicating status of the operation. S_OK if the assembly was successfully executed +// +CORECLR_HOSTING_API(coreclr_initialize, + const char* exePath, + const char* appDomainFriendlyName, + int propertyCount, + const char** propertyKeys, + const char** propertyValues, + void** hostHandle, + unsigned int* domainId); + +// +// Shutdown CoreCLR. It unloads the app domain and stops the CoreCLR host. +// +// Parameters: +// hostHandle - Handle of the host +// domainId - Id of the domain +// +// Returns: +// HRESULT indicating status of the operation. S_OK if the assembly was successfully executed +// +CORECLR_HOSTING_API(coreclr_shutdown, + void* hostHandle, + unsigned int domainId); + +// +// Shutdown CoreCLR. It unloads the app domain and stops the CoreCLR host. +// +// Parameters: +// hostHandle - Handle of the host +// domainId - Id of the domain +// latchedExitCode - Latched exit code after domain unloaded +// +// Returns: +// HRESULT indicating status of the operation. S_OK if the assembly was successfully executed +// +CORECLR_HOSTING_API(coreclr_shutdown_2, + void* hostHandle, + unsigned int domainId, + int* latchedExitCode); + +// +// Create a native callable function pointer for a managed method. +// +// Parameters: +// hostHandle - Handle of the host +// domainId - Id of the domain +// entryPointAssemblyName - Name of the assembly which holds the custom entry point +// entryPointTypeName - Name of the type which holds the custom entry point +// entryPointMethodName - Name of the method which is the custom entry point +// delegate - Output parameter, the function stores a native callable function pointer to the delegate at the specified address +// +// Returns: +// HRESULT indicating status of the operation. S_OK if the assembly was successfully executed +// +CORECLR_HOSTING_API(coreclr_create_delegate, + void* hostHandle, + unsigned int domainId, + const char* entryPointAssemblyName, + const char* entryPointTypeName, + const char* entryPointMethodName, + void** delegate); + +// +// Execute a managed assembly with given arguments +// +// Parameters: +// hostHandle - Handle of the host +// domainId - Id of the domain +// argc - Number of arguments passed to the executed assembly +// argv - Array of arguments passed to the executed assembly +// managedAssemblyPath - Path of the managed assembly to execute (or NULL if using a custom entrypoint). +// exitCode - Exit code returned by the executed assembly +// +// Returns: +// HRESULT indicating status of the operation. S_OK if the assembly was successfully executed +// +CORECLR_HOSTING_API(coreclr_execute_assembly, + void* hostHandle, + unsigned int domainId, + int argc, + const char** argv, + const char* managedAssemblyPath, + unsigned int* exitCode); + +#undef CORECLR_HOSTING_API + +#endif // __CORECLR_HOST_H__ \ No newline at end of file diff --git a/src/dnchost/dnchost.cpp b/src/dnchost/dnchost.cpp new file mode 100644 index 00000000..c4b0d222 --- /dev/null +++ b/src/dnchost/dnchost.cpp @@ -0,0 +1,229 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +static DNCSTATE vstate = { }; + + +// internal function declarations + +static HRESULT LoadModulePaths( + __in DNCSTATE* pState + ); +static HRESULT LoadDncConfiguration( + __in DNCSTATE* pState, + __in const BOOTSTRAPPER_CREATE_ARGS* pArgs + ); +static HRESULT LoadRuntime( + __in DNCSTATE* pState + ); +static HRESULT LoadManagedBootstrapperApplicationFactory( + __in DNCSTATE* pState + ); + + +// function definitions + +extern "C" BOOL WINAPI DllMain( + IN HINSTANCE hInstance, + IN DWORD dwReason, + IN LPVOID /* pvReserved */ + ) +{ + switch (dwReason) + { + case DLL_PROCESS_ATTACH: + ::DisableThreadLibraryCalls(hInstance); + vstate.hInstance = hInstance; + break; + + case DLL_PROCESS_DETACH: + vstate.hInstance = NULL; + break; + } + + return TRUE; +} + +extern "C" HRESULT WINAPI BootstrapperApplicationCreate( + __in const BOOTSTRAPPER_CREATE_ARGS* pArgs, + __inout BOOTSTRAPPER_CREATE_RESULTS* pResults + ) +{ + HRESULT hr = S_OK; + IBootstrapperEngine* pEngine = NULL; + + // coreclr.dll doesn't support unloading, so the rest of the .NET Core hosting stack doesn't support it either. + // This means we also can't unload. + pResults->fDisableUnloading = TRUE; + + hr = BalInitializeFromCreateArgs(pArgs, &pEngine); + ExitOnFailure(hr, "Failed to initialize Bal."); + + if (!vstate.fInitialized) + { + hr = XmlInitialize(); + BalExitOnFailure(hr, "Failed to initialize XML."); + + hr = LoadModulePaths(&vstate); + BalExitOnFailure(hr, "Failed to get the host base path."); + + hr = LoadDncConfiguration(&vstate, pArgs); + BalExitOnFailure(hr, "Failed to get the dnc configuration."); + + vstate.fInitialized = TRUE; + } + + if (!vstate.fInitializedRuntime) + { + hr = LoadRuntime(&vstate); + BalExitOnFailure(hr, "Failed to load .NET Core runtime."); + + vstate.fInitializedRuntime = TRUE; + + hr = LoadManagedBootstrapperApplicationFactory(&vstate); + BalExitOnFailure(hr, "Failed to create the .NET Core bootstrapper application factory."); + } + + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Loading .NET Core SCD bootstrapper application."); + + hr = vstate.pAppFactory->Create(pArgs, pResults); + BalExitOnFailure(hr, "Failed to create the .NET Core bootstrapper application."); + +LExit: + ReleaseNullObject(pEngine); + + return hr; +} + +extern "C" void WINAPI BootstrapperApplicationDestroy() +{ + BalUninitialize(); +} + +static HRESULT LoadModulePaths( + __in DNCSTATE* pState + ) +{ + HRESULT hr = S_OK; + + hr = PathForCurrentProcess(&pState->sczModuleFullPath, pState->hInstance); + BalExitOnFailure(hr, "Failed to get the full host path."); + + hr = PathGetDirectory(pState->sczModuleFullPath, &pState->sczAppBase); + BalExitOnFailure(hr, "Failed to get the directory of the full process path."); + + hr = PathConcat(pState->sczAppBase, DNC_ASSEMBLY_FILE_NAME, &pState->sczManagedHostPath); + BalExitOnFailure(hr, "Failed to create managed host path."); + +LExit: + return hr; +} + +static HRESULT LoadDncConfiguration( + __in DNCSTATE* pState, + __in const BOOTSTRAPPER_CREATE_ARGS* pArgs + ) +{ + HRESULT hr = S_OK; + IXMLDOMDocument* pixdManifest = NULL; + IXMLDOMNode* pixnHost = NULL; + IXMLDOMNode* pixnPayload = NULL; + LPWSTR sczPayloadId = NULL; + LPWSTR sczPayloadXPath = NULL; + LPWSTR sczPayloadName = NULL; + + hr = XmlLoadDocumentFromFile(pArgs->pCommand->wzBootstrapperApplicationDataPath, &pixdManifest); + BalExitOnFailure(hr, "Failed to load BalManifest '%ls'", pArgs->pCommand->wzBootstrapperApplicationDataPath); + + hr = XmlSelectSingleNode(pixdManifest, L"/BootstrapperApplicationData/WixBalBAFactoryAssembly", &pixnHost); + BalExitOnFailure(hr, "Failed to get WixBalBAFactoryAssembly element."); + + if (S_FALSE == hr) + { + hr = E_NOTFOUND; + BalExitOnRootFailure(hr, "Failed to find WixBalBAFactoryAssembly element in bootstrapper application config."); + } + + hr = XmlGetAttributeEx(pixnHost, L"PayloadId", &sczPayloadId); + BalExitOnFailure(hr, "Failed to get WixBalBAFactoryAssembly/@PayloadId."); + + hr = StrAllocFormatted(&sczPayloadXPath, L"/BootstrapperApplicationData/WixPayloadProperties[@Payload='%ls']", sczPayloadId); + BalExitOnFailure(hr, "Failed to format BAFactoryAssembly payload XPath."); + + hr = XmlSelectSingleNode(pixdManifest, sczPayloadXPath, &pixnPayload); + if (S_FALSE == hr) + { + hr = E_NOTFOUND; + } + BalExitOnFailure(hr, "Failed to find WixPayloadProperties node for BAFactoryAssembly PayloadId: %ls.", sczPayloadId); + + hr = XmlGetAttributeEx(pixnPayload, L"Name", &sczPayloadName); + BalExitOnFailure(hr, "Failed to get BAFactoryAssembly payload Name."); + + hr = PathConcat(pArgs->pCommand->wzBootstrapperWorkingFolder, sczPayloadName, &pState->sczBaFactoryAssemblyPath); + BalExitOnFailure(hr, "Failed to create BaFactoryAssemblyPath."); + + LPCWSTR wzFileName = PathFile(pState->sczBaFactoryAssemblyPath); + LPCWSTR wzExtension = PathExtension(pState->sczBaFactoryAssemblyPath); + if (!wzExtension) + { + BalExitOnFailure(hr = E_FAIL, "BaFactoryAssemblyPath has no extension."); + } + + hr = StrAllocString(&pState->sczBaFactoryAssemblyName, wzFileName, wzExtension - wzFileName); + BalExitOnFailure(hr, "Failed to copy BAFactoryAssembly payload Name."); + + hr = StrAllocString(&pState->sczBaFactoryDepsJsonPath, pState->sczBaFactoryAssemblyPath, wzExtension - pState->sczBaFactoryAssemblyPath); + BalExitOnFailure(hr, "Failed to initialize deps json path."); + + hr = StrAllocString(&pState->sczBaFactoryRuntimeConfigPath, pState->sczBaFactoryDepsJsonPath, 0); + BalExitOnFailure(hr, "Failed to initialize runtime config path."); + + hr = StrAllocConcat(&pState->sczBaFactoryDepsJsonPath, L".deps.json", 0); + BalExitOnFailure(hr, "Failed to concat extension to deps json path."); + + hr = StrAllocConcat(&pState->sczBaFactoryRuntimeConfigPath, L".runtimeconfig.json", 0); + BalExitOnFailure(hr, "Failed to concat extension to runtime config path."); + +LExit: + ReleaseStr(sczPayloadName); + ReleaseObject(pixnPayload); + ReleaseStr(sczPayloadXPath); + ReleaseStr(sczPayloadId); + ReleaseObject(pixnHost); + ReleaseObject(pixdManifest); + + return hr; +} + +static HRESULT LoadRuntime( + __in DNCSTATE* pState + ) +{ + HRESULT hr = S_OK; + + hr = DnchostLoadRuntime( + &pState->hostfxrState, + pState->sczModuleFullPath, + pState->sczManagedHostPath, + pState->sczBaFactoryDepsJsonPath, + pState->sczBaFactoryRuntimeConfigPath); + + return hr; +} + +static HRESULT LoadManagedBootstrapperApplicationFactory( + __in DNCSTATE* pState + ) +{ + HRESULT hr = S_OK; + + hr = DnchostCreateFactory( + &pState->hostfxrState, + pState->sczBaFactoryAssemblyName, + pState->sczBaFactoryAssemblyPath, + &pState->pAppFactory); + + return hr; +} diff --git a/src/dnchost/dnchost.def b/src/dnchost/dnchost.def new file mode 100644 index 00000000..4488df94 --- /dev/null +++ b/src/dnchost/dnchost.def @@ -0,0 +1,6 @@ +; Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +EXPORTS + BootstrapperApplicationCreate + BootstrapperApplicationDestroy diff --git a/src/dnchost/dnchost.h b/src/dnchost/dnchost.h new file mode 100644 index 00000000..e498edaf --- /dev/null +++ b/src/dnchost/dnchost.h @@ -0,0 +1,19 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +struct DNCSTATE +{ + BOOL fInitialized; + BOOL fInitializedRuntime; + HINSTANCE hInstance; + LPWSTR sczModuleFullPath; + LPWSTR sczAppBase; + LPWSTR sczManagedHostPath; + LPWSTR sczBaFactoryAssemblyName; + LPWSTR sczBaFactoryAssemblyPath; + LPWSTR sczBaFactoryDepsJsonPath; + LPWSTR sczBaFactoryRuntimeConfigPath; + HOSTFXR_STATE hostfxrState; + IBootstrapperApplicationFactory* pAppFactory; +}; diff --git a/src/dnchost/dnchost.vcxproj b/src/dnchost/dnchost.vcxproj new file mode 100644 index 00000000..e2b8b529 --- /dev/null +++ b/src/dnchost/dnchost.vcxproj @@ -0,0 +1,77 @@ + + + + + + + + + Debug + Win32 + + + Release + Win32 + + + + {B6F70281-6583-4138-BB7F-AABFEBBB3CA2} + DynamicLibrary + v141 + Unicode + dnchost + dnchost.def + + + + + ..\..\packages\runtime.win-x86.Microsoft.NETCore.DotNetAppHost.3.1.3\runtimes\win-x86\native\ + shlwapi.lib;$(NetHostPath)nethost.lib + + + + + + Create + + + + + + + + + + + + + + + + PreserveNewest + False + + + + + $(BaseOutputPath)obj;$(NetHostPath);%(AdditionalIncludeDirectories) + + + + + {0D780900-C2FF-4FA2-8CB5-8A19768724C5} + true + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + \ No newline at end of file diff --git a/src/dnchost/dncutil.cpp b/src/dnchost/dncutil.cpp new file mode 100644 index 00000000..996bf086 --- /dev/null +++ b/src/dnchost/dncutil.cpp @@ -0,0 +1,359 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +// https://github.com/dotnet/runtime/blob/master/src/installer/corehost/error_codes.h +#define HostApiBufferTooSmall 0x80008098 + +// internal function declarations + +static HRESULT GetHostfxrPath( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzNativeHostPath + ); +static HRESULT LoadHostfxr( + __in HOSTFXR_STATE* pState + ); +static HRESULT InitializeHostfxr( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzManagedHostPath, + __in LPCWSTR wzDepsJsonPath, + __in LPCWSTR wzRuntimeConfigPath + ); +static HRESULT InitializeCoreClr( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzNativeHostPath + ); +static HRESULT LoadCoreClr( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzCoreClrPath + ); +static HRESULT StartCoreClr( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzNativeHostPath, + __in size_t cProperties, + __in LPCWSTR* propertyKeys, + __in LPCWSTR* propertyValues + ); + + +// function definitions + +HRESULT DnchostLoadRuntime( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzNativeHostPath, + __in LPCWSTR wzManagedHostPath, + __in LPCWSTR wzDepsJsonPath, + __in LPCWSTR wzRuntimeConfigPath + ) +{ + HRESULT hr = S_OK; + + hr = GetHostfxrPath(pState, wzNativeHostPath); + BalExitOnFailure(hr, "Failed to find hostfxr."); + + hr = LoadHostfxr(pState); + BalExitOnFailure(hr, "Failed to load hostfxr."); + + hr = InitializeHostfxr(pState, wzManagedHostPath, wzDepsJsonPath, wzRuntimeConfigPath); + BalExitOnFailure(hr, "Failed to initialize hostfxr."); + + hr = InitializeCoreClr(pState, wzNativeHostPath); + BalExitOnFailure(hr, "Failed to initialize coreclr."); + +LExit: + return hr; +} + +HRESULT DnchostCreateFactory( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzBaFactoryAssemblyName, + __in LPCWSTR wzBaFactoryAssemblyPath, + __out IBootstrapperApplicationFactory** ppAppFactory + ) +{ + HRESULT hr = S_OK; + PFNCREATEBAFACTORY pfnCreateBAFactory = NULL; + + hr = pState->pfnCoreclrCreateDelegate( + pState->pClrHandle, + pState->dwDomainId, + DNC_ASSEMBLY_FULL_NAME, + DNC_ENTRY_TYPE, + DNC_STATIC_ENTRY_METHOD, + reinterpret_cast(&pfnCreateBAFactory)); + BalExitOnFailure(hr, "Failed to create delegate in app domain."); + + *ppAppFactory = pfnCreateBAFactory(wzBaFactoryAssemblyName, wzBaFactoryAssemblyPath); + +LExit: + return hr; +} + +static HRESULT GetHostfxrPath( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzNativeHostPath + ) +{ + HRESULT hr = S_OK; + get_hostfxr_parameters getHostfxrParameters = { }; + int nrc = 0; + size_t cchHostFxrPath = MAX_PATH; + + getHostfxrParameters.size = sizeof(get_hostfxr_parameters); + getHostfxrParameters.assembly_path = wzNativeHostPath; + + // get_hostfxr_path does a full search on every call, so + // minimize the number of calls + // need to loop + for (;;) + { + cchHostFxrPath *= 2; + hr = StrAlloc(&pState->sczHostfxrPath, cchHostFxrPath); + BalExitOnFailure(hr, "Failed to allocate hostFxrPath."); + + nrc = get_hostfxr_path(pState->sczHostfxrPath, &cchHostFxrPath, &getHostfxrParameters); + if (HostApiBufferTooSmall != nrc) + { + break; + } + } + if (0 != nrc) + { + BalExitOnFailure(hr = nrc, "GetHostfxrPath failed"); + } + +LExit: + return hr; +} + +static HRESULT LoadHostfxr( + __in HOSTFXR_STATE* pState + ) +{ + HRESULT hr = S_OK; + HMODULE hHostfxr; + + hHostfxr = ::LoadLibraryExW(pState->sczHostfxrPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + BalExitOnNullWithLastError(hHostfxr, hr, "Failed to load hostfxr from '%ls'.", pState->sczHostfxrPath); + + pState->pfnHostfxrInitializeForApp = reinterpret_cast(::GetProcAddress(hHostfxr, "hostfxr_initialize_for_dotnet_command_line")); + BalExitOnNullWithLastError(pState->pfnHostfxrInitializeForApp, hr, "Failed to get procedure address for hostfxr_initialize_for_dotnet_command_line."); + + pState->pfnHostfxrGetRuntimeProperties = reinterpret_cast(::GetProcAddress(hHostfxr, "hostfxr_get_runtime_properties")); + BalExitOnNullWithLastError(pState->pfnHostfxrGetRuntimeProperties, hr, "Failed to get procedure address for hostfxr_get_runtime_properties."); + + pState->pfnHostfxrSetErrorWriter = reinterpret_cast(::GetProcAddress(hHostfxr, "hostfxr_set_error_writer")); + BalExitOnNullWithLastError(pState->pfnHostfxrSetErrorWriter, hr, "Failed to get procedure address for hostfxr_set_error_writer."); + + pState->pfnHostfxrClose = reinterpret_cast(::GetProcAddress(hHostfxr, "hostfxr_close")); + BalExitOnNullWithLastError(pState->pfnHostfxrClose, hr, "Failed to get procedure address for hostfxr_close."); + +LExit: + // Never unload the module since it isn't meant to be unloaded. + + return hr; +} + +static void HOSTFXR_CALLTYPE DnchostErrorWriter( + __in LPCWSTR wzMessage + ) +{ + BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "error from hostfxr: %ls", wzMessage); +} + +static HRESULT InitializeHostfxr( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzManagedHostPath, + __in LPCWSTR wzDepsJsonPath, + __in LPCWSTR wzRuntimeConfigPath + ) +{ + HRESULT hr = S_OK; + + pState->pfnHostfxrSetErrorWriter(static_cast(&DnchostErrorWriter)); + + LPCWSTR argv[] = { + L"exec", + L"--depsfile", + wzDepsJsonPath, + L"--runtimeconfig", + wzRuntimeConfigPath, + wzManagedHostPath, + }; + hr = pState->pfnHostfxrInitializeForApp(sizeof(argv)/sizeof(LPWSTR), argv, NULL, &pState->hostContextHandle); + BalExitOnFailure(hr, "HostfxrInitializeForApp failed"); + +LExit: + return hr; +} + +static HRESULT InitializeCoreClr( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzNativeHostPath + ) +{ + HRESULT hr = S_OK; + int32_t rc = 0; + LPCWSTR* rgPropertyKeys = NULL; + LPCWSTR* rgPropertyValues = NULL; + size_t cProperties = 0; + LPWSTR* rgDirectories = NULL; + UINT cDirectories = 0; + LPWSTR sczCoreClrPath = NULL; + + // We are not using hostfxr as it was intended to be used. We need to initialize hostfxr so that it properly initializes hostpolicy - + // there are pieces of the framework such as AssemblyDependencyResolver that won't work without that. We also need hostfxr to find a + // compatible framework for framework-dependent deployed BAs. We had to use hostfxr_initialize_for_dotnet_command_line since + // hostfxr_initialize_for_runtime_config doesn't currently (3.x) support self-contained deployed BAs. That means we're supposed to + // start the runtime through hostfxr_run_app, but that method shuts down the runtime before returning. We actually want to call + // hostfxr_get_runtime_delegate, but that method currently requires hostfxr to be initialized through + // hostfxr_initialize_for_runtime_config. So we're forced to locate coreclr.dll and manually load the runtime ourselves. + + // Unfortunately, that's not the only problem. hostfxr has global state that tracks whether it started the runtime. While we keep our + // hostfxr_handle open, everyone that calls the hostfxr_initialize_* methods will block until we have started the runtime through + // hostfxr or closed our handle. If we close the handle, then hostfxr could potentially try to load a second runtime into the + // process, which is not supported. We're going to just keep our handle open since no one else in the process should be trying to + // start the runtime anyway. + + rc = pState->pfnHostfxrGetRuntimeProperties(pState->hostContextHandle, &cProperties, rgPropertyKeys, rgPropertyValues); + if (HostApiBufferTooSmall != rc) + { + BalExitOnFailure(hr = rc, "HostfxrGetRuntimeProperties failed to return required size."); + } + + rgPropertyKeys = static_cast(MemAlloc(sizeof(LPWSTR) * cProperties, TRUE)); + rgPropertyValues = static_cast(MemAlloc(sizeof(LPWSTR) * cProperties, TRUE)); + if (!rgPropertyKeys || !rgPropertyValues) + { + BalExitOnFailure(hr = E_OUTOFMEMORY, "Failed to allocate buffers for runtime properties."); + } + + hr = pState->pfnHostfxrGetRuntimeProperties(pState->hostContextHandle, &cProperties, rgPropertyKeys, rgPropertyValues); + BalExitOnFailure(hr, "HostfxrGetRuntimeProperties failed."); + + for (DWORD i = 0; i < cProperties; ++i) + { + if (CSTR_EQUAL == ::CompareString(LOCALE_INVARIANT, 0, rgPropertyKeys[i], -1, L"NATIVE_DLL_SEARCH_DIRECTORIES", -1)) + { + hr = StrSplitAllocArray(&rgDirectories, &cDirectories, rgPropertyValues[i], L";"); + BalExitOnFailure(hr, "Failed to split NATIVE_DLL_SEARCH_DIRECTORIES '%ls'", rgPropertyValues[i]); + } + } + + for (DWORD i = 0; i < cDirectories; ++i) + { + hr = PathConcat(rgDirectories[i], L"coreclr.dll", &sczCoreClrPath); + BalExitOnFailure(hr, "Failed to allocate path to coreclr."); + + if (::PathFileExists(sczCoreClrPath)) + { + break; + } + else + { + ReleaseNullStr(sczCoreClrPath); + } + } + + if (!sczCoreClrPath) + { + for (DWORD i = 0; i < cProperties; ++i) + { + BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "%ls: %ls", rgPropertyKeys[i], rgPropertyValues[i]); + } + BalExitOnFailure(hr = E_FILENOTFOUND, "Failed to locate coreclr.dll."); + } + + hr = LoadCoreClr(pState, sczCoreClrPath); + BalExitOnFailure(hr, "Failed to load coreclr."); + + hr = StartCoreClr(pState, wzNativeHostPath, cProperties, rgPropertyKeys, rgPropertyValues); + BalExitOnFailure(hr, "Failed to start coreclr."); + +LExit: + MemFree(rgDirectories); + MemFree(rgPropertyValues); + MemFree(rgPropertyKeys); + ReleaseStr(sczCoreClrPath); + + return hr; +} + +static HRESULT LoadCoreClr( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzCoreClrPath + ) +{ + HRESULT hr = S_OK; + HMODULE hModule = NULL; + + hModule = ::LoadLibraryExW(wzCoreClrPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + BalExitOnNullWithLastError(hModule, hr, "Failed to load coreclr.dll from '%ls'.", wzCoreClrPath); + + pState->pfnCoreclrInitialize = reinterpret_cast(::GetProcAddress(hModule, "coreclr_initialize")); + BalExitOnNullWithLastError(pState->pfnCoreclrInitialize, hr, "Failed to get procedure address for coreclr_initialize."); + + pState->pfnCoreclrCreateDelegate = reinterpret_cast(::GetProcAddress(hModule, "coreclr_create_delegate")); + BalExitOnNullWithLastError(pState->pfnCoreclrCreateDelegate, hr, "Failed to get procedure address for coreclr_create_delegate."); + +LExit: + // Never unload the module since coreclr doesn't support it. + + return hr; +} + +static HRESULT StartCoreClr( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzNativeHostPath, + __in size_t cProperties, + __in LPCWSTR* propertyKeys, + __in LPCWSTR* propertyValues + ) +{ + HRESULT hr = S_OK; + LPSTR szNativeHostPath = NULL; + LPSTR* rgPropertyKeys = NULL; + LPSTR* rgPropertyValues = NULL; + + rgPropertyKeys = static_cast(MemAlloc(sizeof(LPSTR) * cProperties, TRUE)); + rgPropertyValues = static_cast(MemAlloc(sizeof(LPSTR) * cProperties, TRUE)); + if (!rgPropertyKeys || !rgPropertyValues) + { + BalExitOnFailure(hr = E_OUTOFMEMORY, "Failed to allocate buffers for runtime properties."); + } + + hr = StrAnsiAllocString(&szNativeHostPath, wzNativeHostPath, 0, CP_UTF8); + BalExitOnFailure(hr, "Failed to convert module path to UTF8: %ls", wzNativeHostPath); + + for (DWORD i = 0; i < cProperties; ++i) + { + hr = StrAnsiAllocString(&rgPropertyKeys[i], propertyKeys[i], 0, CP_UTF8); + BalExitOnFailure(hr, "Failed to convert property key to UTF8: %ls", propertyKeys[i]); + + hr = StrAnsiAllocString(&rgPropertyValues[i], propertyValues[i], 0, CP_UTF8); + BalExitOnFailure(hr, "Failed to convert property value to UTF8: %ls", propertyValues[i]); + } + + hr = pState->pfnCoreclrInitialize(szNativeHostPath, "MBA", cProperties, (LPCSTR*)rgPropertyKeys, (LPCSTR*)rgPropertyValues, &pState->pClrHandle, &pState->dwDomainId); + BalExitOnFailure(hr, "CoreclrInitialize failed."); + +LExit: + for (DWORD i = 0; i < cProperties; ++i) + { + if (rgPropertyKeys) + { + ReleaseStr(rgPropertyKeys[i]); + } + + if (rgPropertyValues) + { + ReleaseStr(rgPropertyValues[i]); + } + } + ReleaseMem(rgPropertyValues); + ReleaseMem(rgPropertyKeys); + ReleaseStr(szNativeHostPath); + + return hr; +} diff --git a/src/dnchost/dncutil.h b/src/dnchost/dncutil.h new file mode 100644 index 00000000..1a7c16e3 --- /dev/null +++ b/src/dnchost/dncutil.h @@ -0,0 +1,36 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +typedef IBootstrapperApplicationFactory* (STDMETHODCALLTYPE* PFNCREATEBAFACTORY)( + __in LPCWSTR wzBaFactoryAssemblyName, + __in LPCWSTR wzBaFactoryAssemblyPath + ); + +struct HOSTFXR_STATE +{ + LPWSTR sczHostfxrPath; + hostfxr_handle hostContextHandle; + hostfxr_initialize_for_dotnet_command_line_fn pfnHostfxrInitializeForApp; + hostfxr_get_runtime_properties_fn pfnHostfxrGetRuntimeProperties; + hostfxr_set_error_writer_fn pfnHostfxrSetErrorWriter; + hostfxr_close_fn pfnHostfxrClose; + coreclr_initialize_ptr pfnCoreclrInitialize; + coreclr_create_delegate_ptr pfnCoreclrCreateDelegate; + void* pClrHandle; + UINT dwDomainId; +}; + +HRESULT DnchostLoadRuntime( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzNativeHostPath, + __in LPCWSTR wzManagedHostPath, + __in LPCWSTR wzDepsJsonPath, + __in LPCWSTR wzRuntimeConfigPath + ); + +HRESULT DnchostCreateFactory( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzBaFactoryAssemblyName, + __in LPCWSTR wzBaFactoryAssemblyPath, + __out IBootstrapperApplicationFactory** ppAppFactory + ); diff --git a/src/dnchost/hostfxr.h b/src/dnchost/hostfxr.h new file mode 100644 index 00000000..85e6e0ab --- /dev/null +++ b/src/dnchost/hostfxr.h @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + + + +// ***** ABOUT THIS HEADER ***** +// ************************************************************************************** +// +// This is the version on 2019-12-22 from +// https://github.com/dotnet/runtime/blob/master/src/installer/corehost/cli/hostfxr.h +// +// ************************************************************************************** +// **************************** + + +#ifndef __HOSTFXR_H__ +#define __HOSTFXR_H__ + +#include +#include + +#if defined(_WIN32) + #define HOSTFXR_CALLTYPE __cdecl + #ifdef _WCHAR_T_DEFINED + typedef wchar_t char_t; + #else + typedef unsigned short char_t; + #endif +#else + #define HOSTFXR_CALLTYPE + typedef char char_t; +#endif + +enum hostfxr_delegate_type +{ + hdt_com_activation, + hdt_load_in_memory_assembly, + hdt_winrt_activation, + hdt_com_register, + hdt_com_unregister, + hdt_load_assembly_and_get_function_pointer +}; + +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_main_fn)(const int argc, const char_t **argv); +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_main_startupinfo_fn)( + const int argc, + const char_t **argv, + const char_t *host_path, + const char_t *dotnet_root, + const char_t *app_path); + +typedef void(HOSTFXR_CALLTYPE *hostfxr_error_writer_fn)(const char_t *message); +typedef hostfxr_error_writer_fn(HOSTFXR_CALLTYPE *hostfxr_set_error_writer_fn)(hostfxr_error_writer_fn error_writer); + +typedef void* hostfxr_handle; +struct hostfxr_initialize_parameters +{ + size_t size; + const char_t *host_path; + const char_t *dotnet_root; +}; + +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_initialize_for_dotnet_command_line_fn)( + int argc, + const char_t **argv, + const struct hostfxr_initialize_parameters *parameters, + /*out*/ hostfxr_handle *host_context_handle); +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_initialize_for_runtime_config_fn)( + const char_t *runtime_config_path, + const struct hostfxr_initialize_parameters *parameters, + /*out*/ hostfxr_handle *host_context_handle); + +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_get_runtime_property_value_fn)( + const hostfxr_handle host_context_handle, + const char_t *name, + /*out*/ const char_t **value); +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_set_runtime_property_value_fn)( + const hostfxr_handle host_context_handle, + const char_t *name, + const char_t *value); +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_get_runtime_properties_fn)( + const hostfxr_handle host_context_handle, + /*inout*/ size_t * count, + /*out*/ const char_t **keys, + /*out*/ const char_t **values); + +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_run_app_fn)(const hostfxr_handle host_context_handle); +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_get_runtime_delegate_fn)( + const hostfxr_handle host_context_handle, + enum hostfxr_delegate_type type, + /*out*/ void **delegate); + +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_close_fn)(const hostfxr_handle host_context_handle); + +#endif //__HOSTFXR_H__ \ No newline at end of file diff --git a/src/dnchost/packages.config b/src/dnchost/packages.config new file mode 100644 index 00000000..c8911ea5 --- /dev/null +++ b/src/dnchost/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/dnchost/precomp.cpp b/src/dnchost/precomp.cpp new file mode 100644 index 00000000..37664a1c --- /dev/null +++ b/src/dnchost/precomp.cpp @@ -0,0 +1,3 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" diff --git a/src/dnchost/precomp.h b/src/dnchost/precomp.h new file mode 100644 index 00000000..6a12ef67 --- /dev/null +++ b/src/dnchost/precomp.h @@ -0,0 +1,29 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "coreclrhost.h" +#include "hostfxr.h" +#include "dncutil.h" +#include "dnchost.h" diff --git a/src/test/WixToolsetTest.ManagedHost/DncHostFixture.cs b/src/test/WixToolsetTest.ManagedHost/DncHostFixture.cs new file mode 100644 index 00000000..f5714c67 --- /dev/null +++ b/src/test/WixToolsetTest.ManagedHost/DncHostFixture.cs @@ -0,0 +1,232 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolsetTest.ManagedHost +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class DncHostFixture + { + [Fact] + public void CanLoadSCDEarliestCoreMBA() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundleFile = Path.Combine(binFolder, "SCDEarliestCoreMBA.exe"); + var baSourceFolder = TestData.Get(@"..\examples"); + var bundleSourceFolder = TestData.Get(@"TestData\EarliestCoreMBA"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "SelfContainedBundle.wxs"), + Path.Combine(bundleSourceFolder, "HarvestedSCD.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", baSourceFolder, + "-burnStub", TestEngine.BurnStubFile, + "-o", bundleFile, + }); + compileResult.AssertSuccess(); + var testEngine = new TestEngine(); + + var result = testEngine.RunShutdownEngine(bundleFile, baseFolder); + var logMessages = result.Output; + Assert.Equal("Loading .NET Core SCD bootstrapper application.", logMessages[0]); + Assert.Equal("Creating BA thread to run asynchronously.", logMessages[1]); + Assert.Equal("EarliestCoreBA", logMessages[2]); + Assert.Equal("Shutdown,ReloadBootstrapper,0", logMessages[3]); + } + } + + [Fact] + public void CanLoadTrimmedSCDEarliestCoreMBA() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundleFile = Path.Combine(binFolder, "TrimmedSCDEarliestCoreMBA.exe"); + var baSourceFolder = TestData.Get(@"..\examples"); + var bundleSourceFolder = TestData.Get(@"TestData\EarliestCoreMBA"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "TrimmedSelfContainedBundle.wxs"), + Path.Combine(bundleSourceFolder, "HarvestedTrimmedSCD.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", baSourceFolder, + "-burnStub", TestEngine.BurnStubFile, + "-o", bundleFile, + }); + compileResult.AssertSuccess(); + var testEngine = new TestEngine(); + + var result = testEngine.RunShutdownEngine(bundleFile, baseFolder); + var logMessages = result.Output; + Assert.Equal("Loading .NET Core SCD bootstrapper application.", logMessages[0]); + Assert.Equal("Creating BA thread to run asynchronously.", logMessages[1]); + Assert.Equal("EarliestCoreBA", logMessages[2]); + Assert.Equal("Shutdown,ReloadBootstrapper,0", logMessages[3]); + } + } + + [Fact] + public void CanReloadSCDEarliestCoreMBA() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundleFile = Path.Combine(binFolder, "SCDEarliestCoreMBA.exe"); + var baSourceFolder = TestData.Get(@"..\examples"); + var bundleSourceFolder = TestData.Get(@"TestData\EarliestCoreMBA"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "SelfContainedBundle.wxs"), + Path.Combine(bundleSourceFolder, "HarvestedSCD.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", baSourceFolder, + "-burnStub", TestEngine.BurnStubFile, + "-o", bundleFile, + }); + compileResult.AssertSuccess(); + var testEngine = new TestEngine(); + + var result = testEngine.RunReloadEngine(bundleFile, baseFolder); + var logMessages = result.Output; + Assert.Equal("Loading .NET Core SCD bootstrapper application.", logMessages[0]); + Assert.Equal("Creating BA thread to run asynchronously.", logMessages[1]); + Assert.Equal("EarliestCoreBA", logMessages[2]); + Assert.Equal("Shutdown,ReloadBootstrapper,0", logMessages[3]); + Assert.Equal("Loading .NET Core SCD bootstrapper application.", logMessages[4]); + Assert.Equal("Reloaded 1 time(s)", logMessages[5]); // dnchost doesn't currently support unloading + Assert.Equal("Creating BA thread to run asynchronously.", logMessages[6]); + Assert.Equal("EarliestCoreBA", logMessages[7]); + Assert.Equal("Shutdown,Restart,0", logMessages[8]); + } + } + + [Fact] + public void CanLoadSCDLatestCoreMBA() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundleFile = Path.Combine(binFolder, "SCDLatestCoreMBA.exe"); + var baSourceFolder = TestData.Get(@"..\examples"); + var bundleSourceFolder = TestData.Get(@"TestData\LatestCoreMBA"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "SelfContainedBundle.wxs"), + Path.Combine(bundleSourceFolder, "HarvestedSCD.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", baSourceFolder, + "-burnStub", TestEngine.BurnStubFile, + "-o", bundleFile, + }); + compileResult.AssertSuccess(); + var testEngine = new TestEngine(); + + var result = testEngine.RunShutdownEngine(bundleFile, baseFolder); + var logMessages = result.Output; + Assert.Equal("Loading .NET Core SCD bootstrapper application.", logMessages[0]); + Assert.Equal("Creating BA thread to run asynchronously.", logMessages[1]); + Assert.Equal("LatestCoreBA", logMessages[2]); + Assert.Equal("Shutdown,ReloadBootstrapper,0", logMessages[3]); + } + } + + [Fact] + public void CanLoadTrimmedSCDLatestCoreMBA() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundleFile = Path.Combine(binFolder, "TrimmedSCDLatestCoreMBA.exe"); + var baSourceFolder = TestData.Get(@"..\examples"); + var bundleSourceFolder = TestData.Get(@"TestData\LatestCoreMBA"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "TrimmedSelfContainedBundle.wxs"), + Path.Combine(bundleSourceFolder, "HarvestedTrimmedSCD.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", baSourceFolder, + "-burnStub", TestEngine.BurnStubFile, + "-o", bundleFile, + }); + compileResult.AssertSuccess(); + var testEngine = new TestEngine(); + + var result = testEngine.RunShutdownEngine(bundleFile, baseFolder); + var logMessages = result.Output; + Assert.Equal("Loading .NET Core SCD bootstrapper application.", logMessages[0]); + Assert.Equal("Creating BA thread to run asynchronously.", logMessages[1]); + Assert.Equal("LatestCoreBA", logMessages[2]); + Assert.Equal("Shutdown,ReloadBootstrapper,0", logMessages[3]); + } + } + + [Fact] + public void CanReloadSCDLatestCoreMBA() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundleFile = Path.Combine(binFolder, "SCDLatestCoreMBA.exe"); + var baSourceFolder = TestData.Get(@"..\examples"); + var bundleSourceFolder = TestData.Get(@"TestData\LatestCoreMBA"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "SelfContainedBundle.wxs"), + Path.Combine(bundleSourceFolder, "HarvestedSCD.wxs"), + "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", baSourceFolder, + "-burnStub", TestEngine.BurnStubFile, + "-o", bundleFile, + }); + compileResult.AssertSuccess(); + var testEngine = new TestEngine(); + + var result = testEngine.RunReloadEngine(bundleFile, baseFolder); + var logMessages = result.Output; + Assert.Equal("Loading .NET Core SCD bootstrapper application.", logMessages[0]); + Assert.Equal("Creating BA thread to run asynchronously.", logMessages[1]); + Assert.Equal("LatestCoreBA", logMessages[2]); + Assert.Equal("Shutdown,ReloadBootstrapper,0", logMessages[3]); + Assert.Equal("Loading .NET Core SCD bootstrapper application.", logMessages[4]); + Assert.Equal("Reloaded 1 time(s)", logMessages[5]); // dnchost doesn't currently support unloading + Assert.Equal("Creating BA thread to run asynchronously.", logMessages[6]); + Assert.Equal("LatestCoreBA", logMessages[7]); + Assert.Equal("Shutdown,Restart,0", logMessages[8]); + } + } + } +} diff --git a/src/test/WixToolsetTest.ManagedHost/HarvestDirectoryToPayloadGroup.ps1 b/src/test/WixToolsetTest.ManagedHost/HarvestDirectoryToPayloadGroup.ps1 new file mode 100644 index 00000000..928470b0 --- /dev/null +++ b/src/test/WixToolsetTest.ManagedHost/HarvestDirectoryToPayloadGroup.ps1 @@ -0,0 +1,42 @@ +param([string]$RootFolder, [string]$HarvestFolder, [string]$OutputFile) + +function harvestFileToPayload { + param([System.IO.FileInfo]$file, [string]$rootFolder, [string]$harvestFolder) + + $sourceFile = $file.FullName.Substring($rootFolder.Length + 1) + $name = $sourceFile.Substring($harvestFolder.Length + 1) + $payloadContents = "" + $payloadContents +} + +function harvestDirectoryToPayloadGroup { + param([string]$rootFolder, [string]$harvestFolder, [string]$outputFile) + + $beginFileContents = @" + + + + " + [System.Environment]::NewLine + + $targetFolder = [System.IO.Path]::Combine($rootFolder, $harvestFolder) + Get-ChildItem -Path $targetFolder -Recurse -File | ForEach-Object { + $fileContents += ' ' + (harvestFileToPayload -file $_ -rootFolder $rootFolder -harvestFolder $harvestFolder) + [System.Environment]::NewLine + } + + $fileContents += $endFileContents + + [System.IO.File]::WriteAllText($outputFile, $fileContents) +} + +harvestDirectoryToPayloadGroup -rootFolder $RootFolder -harvestFolder $HarvestFolder -outputFile $OutputFile \ No newline at end of file diff --git a/src/test/WixToolsetTest.ManagedHost/TestData/EarliestCoreMBA/HarvestedSCD.wxs b/src/test/WixToolsetTest.ManagedHost/TestData/EarliestCoreMBA/HarvestedSCD.wxs new file mode 100644 index 00000000..bb8d56aa --- /dev/null +++ b/src/test/WixToolsetTest.ManagedHost/TestData/EarliestCoreMBA/HarvestedSCD.wxs @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/WixToolsetTest.ManagedHost/TestData/EarliestCoreMBA/HarvestedTrimmedSCD.wxs b/src/test/WixToolsetTest.ManagedHost/TestData/EarliestCoreMBA/HarvestedTrimmedSCD.wxs new file mode 100644 index 00000000..336eef4c --- /dev/null +++ b/src/test/WixToolsetTest.ManagedHost/TestData/EarliestCoreMBA/HarvestedTrimmedSCD.wxs @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/WixToolsetTest.ManagedHost/TestData/EarliestCoreMBA/SelfContainedBundle.wxs b/src/test/WixToolsetTest.ManagedHost/TestData/EarliestCoreMBA/SelfContainedBundle.wxs new file mode 100644 index 00000000..4f3b2f20 --- /dev/null +++ b/src/test/WixToolsetTest.ManagedHost/TestData/EarliestCoreMBA/SelfContainedBundle.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.ManagedHost/TestData/EarliestCoreMBA/TrimmedSelfContainedBundle.wxs b/src/test/WixToolsetTest.ManagedHost/TestData/EarliestCoreMBA/TrimmedSelfContainedBundle.wxs new file mode 100644 index 00000000..15dc72bb --- /dev/null +++ b/src/test/WixToolsetTest.ManagedHost/TestData/EarliestCoreMBA/TrimmedSelfContainedBundle.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.ManagedHost/TestData/LatestCoreMBA/HarvestedSCD.wxs b/src/test/WixToolsetTest.ManagedHost/TestData/LatestCoreMBA/HarvestedSCD.wxs new file mode 100644 index 00000000..09433669 --- /dev/null +++ b/src/test/WixToolsetTest.ManagedHost/TestData/LatestCoreMBA/HarvestedSCD.wxs @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/WixToolsetTest.ManagedHost/TestData/LatestCoreMBA/HarvestedTrimmedSCD.wxs b/src/test/WixToolsetTest.ManagedHost/TestData/LatestCoreMBA/HarvestedTrimmedSCD.wxs new file mode 100644 index 00000000..58ba7b39 --- /dev/null +++ b/src/test/WixToolsetTest.ManagedHost/TestData/LatestCoreMBA/HarvestedTrimmedSCD.wxs @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/WixToolsetTest.ManagedHost/TestData/LatestCoreMBA/SelfContainedBundle.wxs b/src/test/WixToolsetTest.ManagedHost/TestData/LatestCoreMBA/SelfContainedBundle.wxs new file mode 100644 index 00000000..015cc099 --- /dev/null +++ b/src/test/WixToolsetTest.ManagedHost/TestData/LatestCoreMBA/SelfContainedBundle.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.ManagedHost/TestData/LatestCoreMBA/TrimmedSelfContainedBundle.wxs b/src/test/WixToolsetTest.ManagedHost/TestData/LatestCoreMBA/TrimmedSelfContainedBundle.wxs new file mode 100644 index 00000000..39e850a8 --- /dev/null +++ b/src/test/WixToolsetTest.ManagedHost/TestData/LatestCoreMBA/TrimmedSelfContainedBundle.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.ManagedHost/WixToolsetTest.ManagedHost.csproj b/src/test/WixToolsetTest.ManagedHost/WixToolsetTest.ManagedHost.csproj index 5026af85..1ea4522b 100644 --- a/src/test/WixToolsetTest.ManagedHost/WixToolsetTest.ManagedHost.csproj +++ b/src/test/WixToolsetTest.ManagedHost/WixToolsetTest.ManagedHost.csproj @@ -11,9 +11,23 @@ NU1701 + + ..\examples\EarliestCoreMBA\Example.EarliestCoreMBA.csproj + ..\examples\LatestCoreMBA\Example.LatestCoreMBA.csproj + $(OutputPath)examples\publish\ + + + + + + + + + + @@ -21,6 +35,17 @@ + + $(MBAPublishPath)Example.EarliestCoreMBA + + + $(MBAPublishPath)Example.LatestCoreMBA + + + + + + @@ -40,4 +65,9 @@ + + + + + diff --git a/src/test/examples/EarliestCoreMBA/EarliestCoreBA.cs b/src/test/examples/EarliestCoreMBA/EarliestCoreBA.cs new file mode 100644 index 00000000..c9291a7f --- /dev/null +++ b/src/test/examples/EarliestCoreMBA/EarliestCoreBA.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace Example.EarliestCoreMBA +{ + using WixToolset.Mba.Core; + + public class EarliestCoreBA : BootstrapperApplication + { + public EarliestCoreBA(IEngine engine) + : base(engine) + { + + } + + protected override void Run() + { + } + + protected override void OnStartup(StartupEventArgs args) + { + base.OnStartup(args); + + this.engine.Log(LogLevel.Standard, nameof(EarliestCoreBA)); + } + + protected override void OnShutdown(ShutdownEventArgs args) + { + base.OnShutdown(args); + + var message = "Shutdown," + args.Action.ToString() + "," + args.HResult.ToString(); + this.engine.Log(LogLevel.Standard, message); + } + } +} diff --git a/src/test/examples/EarliestCoreMBA/EarliestCoreBAFactory.cs b/src/test/examples/EarliestCoreMBA/EarliestCoreBAFactory.cs new file mode 100644 index 00000000..672e17ee --- /dev/null +++ b/src/test/examples/EarliestCoreMBA/EarliestCoreBAFactory.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +[assembly: WixToolset.Mba.Core.BootstrapperApplicationFactory(typeof(Example.EarliestCoreMBA.EarliestCoreBAFactory))] +namespace Example.EarliestCoreMBA +{ + using WixToolset.Mba.Core; + + public class EarliestCoreBAFactory : BaseBootstrapperApplicationFactory + { + private static int loadCount = 0; + + protected override IBootstrapperApplication Create(IEngine engine, IBootstrapperCommand bootstrapperCommand) + { + if (loadCount > 0) + { + engine.Log(LogLevel.Standard, $"Reloaded {loadCount} time(s)"); + } + ++loadCount; + return new EarliestCoreBA(engine); + } + } +} diff --git a/src/test/examples/EarliestCoreMBA/Example.EarliestCoreMBA.csproj b/src/test/examples/EarliestCoreMBA/Example.EarliestCoreMBA.csproj new file mode 100644 index 00000000..326633ba --- /dev/null +++ b/src/test/examples/EarliestCoreMBA/Example.EarliestCoreMBA.csproj @@ -0,0 +1,18 @@ + + + + netcoreapp3.0 + win-x86;win-x64 + true + Earliest .NET Core MBA + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/examples/LatestCoreMBA/Example.LatestCoreMBA.csproj b/src/test/examples/LatestCoreMBA/Example.LatestCoreMBA.csproj new file mode 100644 index 00000000..1d325b1b --- /dev/null +++ b/src/test/examples/LatestCoreMBA/Example.LatestCoreMBA.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp3.1 + win-x86;win-x64 + true + Latest .NET Core MBA + + + + false + true + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/examples/LatestCoreMBA/LatestCoreBA.cs b/src/test/examples/LatestCoreMBA/LatestCoreBA.cs new file mode 100644 index 00000000..50386a87 --- /dev/null +++ b/src/test/examples/LatestCoreMBA/LatestCoreBA.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace Example.LatestCoreMBA +{ + using WixToolset.Mba.Core; + + public class LatestCoreBA : BootstrapperApplication + { + public LatestCoreBA(IEngine engine) + : base(engine) + { + } + + protected override void Run() + { + } + + protected override void OnStartup(StartupEventArgs args) + { + base.OnStartup(args); + + this.engine.Log(LogLevel.Standard, nameof(LatestCoreBA)); + } + + protected override void OnShutdown(ShutdownEventArgs args) + { + base.OnShutdown(args); + + var message = "Shutdown," + args.Action.ToString() + "," + args.HResult.ToString(); + this.engine.Log(LogLevel.Standard, message); + } + } +} diff --git a/src/test/examples/LatestCoreMBA/LatestCoreBAFactory.cs b/src/test/examples/LatestCoreMBA/LatestCoreBAFactory.cs new file mode 100644 index 00000000..fff3b5c5 --- /dev/null +++ b/src/test/examples/LatestCoreMBA/LatestCoreBAFactory.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +[assembly: WixToolset.Mba.Core.BootstrapperApplicationFactory(typeof(Example.LatestCoreMBA.LatestCoreBAFactory))] +namespace Example.LatestCoreMBA +{ + using WixToolset.Mba.Core; + + public class LatestCoreBAFactory : BaseBootstrapperApplicationFactory + { + private static int loadCount = 0; + + protected override IBootstrapperApplication Create(IEngine engine, IBootstrapperCommand bootstrapperCommand) + { + if (loadCount > 0) + { + engine.Log(LogLevel.Standard, $"Reloaded {loadCount} time(s)"); + } + ++loadCount; + return new LatestCoreBA(engine); + } + } +} diff --git a/src/test/examples/TestEngine/TestEngine.cpp b/src/test/examples/TestEngine/TestEngine.cpp index 203df115..f0811e0a 100644 --- a/src/test/examples/TestEngine/TestEngine.cpp +++ b/src/test/examples/TestEngine/TestEngine.cpp @@ -35,6 +35,12 @@ HRESULT TestEngine::LoadBA( command.cbSize = sizeof(BOOTSTRAPPER_COMMAND); + hr = PathGetDirectory(wzBAFilePath, &command.wzBootstrapperWorkingFolder); + ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Failed to allocate wzBootstrapperWorkingFolder"); + + hr = PathConcat(command.wzBootstrapperWorkingFolder, L"BootstrapperApplicationData.xml", &command.wzBootstrapperApplicationDataPath); + ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Failed to allocate wzBootstrapperApplicationDataPath"); + args.cbSize = sizeof(BOOTSTRAPPER_CREATE_ARGS); args.pCommand = &command; args.pfnBootstrapperEngineProc = TestEngine::EngineProc; @@ -53,6 +59,9 @@ HRESULT TestEngine::LoadBA( ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "BA returned failure on BootstrapperApplicationCreate."); LExit: + ReleaseStr(command.wzBootstrapperApplicationDataPath); + ReleaseStr(command.wzBootstrapperWorkingFolder); + return hr; } @@ -92,6 +101,7 @@ HRESULT TestEngine::SendStartupEvent() void TestEngine::UnloadBA() { PFN_BOOTSTRAPPER_APPLICATION_DESTROY pfnDestroy = NULL; + BOOL fDisableUnloading = m_pCreateResults && m_pCreateResults->fDisableUnloading; ReleaseNullMem(m_pCreateResults); @@ -104,7 +114,11 @@ void TestEngine::UnloadBA() if (m_hBAModule) { - ::FreeLibrary(m_hBAModule); + if (!fDisableUnloading) + { + ::FreeLibrary(m_hBAModule); + } + m_hBAModule = NULL; } } diff --git a/src/test/examples/TestEngine/precomp.h b/src/test/examples/TestEngine/precomp.h index 0d2afb06..3fbc7e90 100644 --- a/src/test/examples/TestEngine/precomp.h +++ b/src/test/examples/TestEngine/precomp.h @@ -9,6 +9,7 @@ #include "logutil.h" #include "memutil.h" #include "pathutil.h" +#include "strutil.h" #include "BootstrapperEngine.h" #include "BootstrapperApplication.h" diff --git a/src/wixext/BalCompiler.cs b/src/wixext/BalCompiler.cs index da32234c..33400f3b 100644 --- a/src/wixext/BalCompiler.cs +++ b/src/wixext/BalCompiler.cs @@ -197,6 +197,15 @@ namespace WixToolset.Bal { switch (attribute.Name.LocalName) { + case "BAFactoryAssembly": + if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attribute)) + { + section.AddTuple(new WixBalBAFactoryAssemblyTuple(sourceLineNumbers) + { + PayloadId = payloadId, + }); + } + break; case "BAFunctions": if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attribute)) { diff --git a/src/wixext/Tuples/BalTupleDefinitions.cs b/src/wixext/Tuples/BalTupleDefinitions.cs index 676db9f6..48199f95 100644 --- a/src/wixext/Tuples/BalTupleDefinitions.cs +++ b/src/wixext/Tuples/BalTupleDefinitions.cs @@ -4,9 +4,11 @@ namespace WixToolset.Bal { using System; using WixToolset.Data; + using WixToolset.Data.Burn; public enum BalTupleDefinitionType { + WixBalBAFactoryAssembly, WixBalBAFunctions, WixBalCondition, WixMbaPrereqInformation, @@ -32,6 +34,9 @@ namespace WixToolset.Bal { switch (type) { + case BalTupleDefinitionType.WixBalBAFactoryAssembly: + return BalTupleDefinitions.WixBalBAFactoryAssembly; + case BalTupleDefinitionType.WixBalBAFunctions: return BalTupleDefinitions.WixBalBAFunctions; @@ -51,5 +56,15 @@ namespace WixToolset.Bal throw new ArgumentOutOfRangeException(nameof(type)); } } + + static BalTupleDefinitions() + { + WixBalBAFactoryAssembly.AddTag(BurnConstants.BootstrapperApplicationDataTupleDefinitionTag); + WixBalBAFunctions.AddTag(BurnConstants.BootstrapperApplicationDataTupleDefinitionTag); + WixBalCondition.AddTag(BurnConstants.BootstrapperApplicationDataTupleDefinitionTag); + WixMbaPrereqInformation.AddTag(BurnConstants.BootstrapperApplicationDataTupleDefinitionTag); + WixStdbaOptions.AddTag(BurnConstants.BootstrapperApplicationDataTupleDefinitionTag); + WixStdbaOverridableVariable.AddTag(BurnConstants.BootstrapperApplicationDataTupleDefinitionTag); + } } } diff --git a/src/wixext/Tuples/WixBalBAFactoryAssemblyTuple.cs b/src/wixext/Tuples/WixBalBAFactoryAssemblyTuple.cs new file mode 100644 index 00000000..e33ea562 --- /dev/null +++ b/src/wixext/Tuples/WixBalBAFactoryAssemblyTuple.cs @@ -0,0 +1,47 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Bal +{ + using WixToolset.Data; + using WixToolset.Bal.Tuples; + + public static partial class BalTupleDefinitions + { + public static readonly IntermediateTupleDefinition WixBalBAFactoryAssembly = new IntermediateTupleDefinition( + BalTupleDefinitionType.WixBalBAFactoryAssembly.ToString(), + new[] + { + new IntermediateFieldDefinition(nameof(WixBalBAFactoryTupleFields.PayloadId), IntermediateFieldType.String), + }, + typeof(WixBalBAFactoryAssemblyTuple)); + } +} + +namespace WixToolset.Bal.Tuples +{ + using WixToolset.Data; + + public enum WixBalBAFactoryTupleFields + { + PayloadId, + } + + public class WixBalBAFactoryAssemblyTuple : IntermediateTuple + { + public WixBalBAFactoryAssemblyTuple() : base(BalTupleDefinitions.WixBalBAFactoryAssembly, null, null) + { + } + + public WixBalBAFactoryAssemblyTuple(SourceLineNumber sourceLineNumber, Identifier id = null) : base(BalTupleDefinitions.WixBalBAFactoryAssembly, sourceLineNumber, id) + { + } + + public IntermediateField this[WixBalBAFactoryTupleFields index] => this.Fields[(int)index]; + + public string PayloadId + { + get => this.Fields[(int)WixBalBAFactoryTupleFields.PayloadId].AsString(); + set => this.Set((int)WixBalBAFactoryTupleFields.PayloadId, value); + } + } +} \ No newline at end of file diff --git a/src/wixext/bal.xsd b/src/wixext/bal.xsd index 3081a279..52f9142f 100644 --- a/src/wixext/bal.xsd +++ b/src/wixext/bal.xsd @@ -9,7 +9,7 @@ xmlns="http://wixtoolset.org/schemas/v4/wxs/bal"> - The source code schema for the WiX Toolset Burn User Experience Extension. + The source code schema for the WiX Toolset Bootstrapper Application Layer Extension. @@ -215,10 +215,25 @@ + + + + When set to "yes", DotNetCoreBootstrapperApplicationHost will load the DLL and instantiate the type with the BootstrapperApplicationFactoryAttribute. + There must be corresponding deps.json and runtimeconfig.json files (set EnableDynamicLoading to True in the .NET Core project). + The .NET Core project must have been published, not just built. + Only one payload may be marked with this attribute set to "yes". + + + + + + + When set to "yes", WixStdBA will load the DLL and work with it to handle BA messages. + Only one payload may be marked with this attribute set to "yes". diff --git a/src/wixlib/Dnc.wxs b/src/wixlib/Dnc.wxs new file mode 100644 index 00000000..65a59e61 --- /dev/null +++ b/src/wixlib/Dnc.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/wixlib/bal.wixproj b/src/wixlib/bal.wixproj index c33375fe..a158cd81 100644 --- a/src/wixlib/bal.wixproj +++ b/src/wixlib/bal.wixproj @@ -14,6 +14,7 @@ + @@ -33,6 +34,10 @@ + + dnchost + {B6F70281-6583-4138-BB7F-AABFEBBB3CA2} + mbahost {12C87C77-3547-44F8-8134-29BC915CB19D} -- cgit v1.2.3-55-g6feb