Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public void ManagedHost(bool selfContained)
result.Should().Pass()
.And.HaveStdOutContaining("New instance of Server created")
.And.HaveStdOutContaining($"Activation of {sharedState.ClsidString} succeeded.")
.And.HaveStdErrContaining($"Executing as a {(selfContained ? "self-contained" : "framework-dependent")} app");
.And.ExecuteSelfContained(selfContained);
}

public class SharedTestState : Comhost.SharedTestState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public void RunApp(CommandLine commandLine, bool isSelfContained, string checkPr
result.Should().Pass()
.And.InitializeContextForApp(expectedAppPath)
.And.ExecuteAssemblyMock(expectedAppPath, appArgs)
.And.HaveStdErrContaining($"Executing as a {(isSelfContained ? "self-contained" : "framework-dependent")} app");
.And.ExecuteSelfContained(isSelfContained);

CheckPropertiesValidation propertyValidation = new CheckPropertiesValidation(checkProperties, LogPrefix.App, SharedTestState.AppPropertyName, SharedTestState.AppPropertyValue);
propertyValidation.ValidateActiveContext(result, newPropertyName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting
{
internal static class HostContextResultExtensions
{
public static AndConstraint<CommandResultAssertions> ExecuteSelfContained(this CommandResultAssertions assertion, bool selfContained)
{
return assertion.HaveStdErrContaining($"Executing as a {(selfContained ? "self-contained" : "framework-dependent")} app");
}

public static AndConstraint<CommandResultAssertions> ExecuteAssemblyMock(this CommandResultAssertions assertion, string appPath, string[] appArgs)
{
var constraint = assertion.HaveStdOutContaining("mock coreclr_initialize() called")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public void ManagedHost(bool selfContained)
result.Should().Pass()
.And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class")
.And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext")
.And.HaveStdErrContaining($"Executing as a {(selfContained ? "self-contained" : "framework-dependent")} app");
.And.ExecuteSelfContained(selfContained);
}

public class SharedTestState : SharedTestStateBase
Expand Down
156 changes: 156 additions & 0 deletions src/installer/tests/HostActivation.Tests/NativeHosting/LoadAssembly.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.DotNet.Cli.Build.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Xunit;

namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting
{
public partial class LoadAssembly : IClassFixture<LoadAssembly.SharedTestState>
{
private const string AppLoadAssemblyArg = "app_load_assembly";
private const string ComponentLoadAssemblyArg = "component_load_assembly";

private readonly SharedTestState sharedState;

public LoadAssembly(SharedTestState sharedTestState)
{
sharedState = sharedTestState;
}

[Fact]
public void ApplicationContext()
{
var appProject = sharedState.Application;
var componentProject = sharedState.ComponentWithNoDependenciesFixture.TestProject;
string[] args =
{
AppLoadAssemblyArg,
sharedState.HostFxrPath,
appProject.AppDll,
componentProject.AppDll,
sharedState.ComponentTypeName,
sharedState.ComponentEntryPoint1,
};
CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.DotNetRoot)
.Execute();

result.Should().Pass()
.And.InitializeContextForApp(appProject.AppDll)
.And.ExecuteSelfContained(selfContained: false)
.And.ExecuteInDefaultContext(componentProject.AssemblyName)
.And.ExecuteFunctionPointer(sharedState.ComponentEntryPoint1, 1, 1);
}

[Fact]
public void ComponentContext()
{
var appProject = sharedState.Application;
var componentProject = sharedState.ComponentWithNoDependenciesFixture.TestProject;
string[] args =
{
ComponentLoadAssemblyArg,
sharedState.HostFxrPath,
componentProject.RuntimeConfigJson,
componentProject.AppDll,
sharedState.ComponentTypeName,
sharedState.ComponentEntryPoint1,
};
CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.DotNetRoot)
.Execute();

result.Should().Pass()
.And.InitializeContextForConfig(componentProject.RuntimeConfigJson)
.And.ExecuteInDefaultContext(componentProject.AssemblyName)
.And.ExecuteFunctionPointer(sharedState.ComponentEntryPoint1, 1, 1);
}

[Fact]
public void SelfContainedApplicationContext()
{
var appProject = sharedState.SelfContainedApplication;
var componentProject = sharedState.ComponentWithNoDependenciesFixture.TestProject;
string[] args =
{
AppLoadAssemblyArg,
appProject.HostFxrDll,
appProject.AppDll,
componentProject.AppDll,
sharedState.ComponentTypeName,
sharedState.ComponentEntryPoint1
};
CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.DotNetRoot)
.Execute();

result.Should().Pass()
.And.InitializeContextForApp(appProject.AppDll)
.And.ExecuteSelfContained(selfContained: true)
.And.ExecuteInDefaultContext(componentProject.AssemblyName)
.And.ExecuteFunctionPointer(sharedState.ComponentEntryPoint1, 1, 1);
}

public class SharedTestState : SharedTestStateBase
{
public string HostFxrPath { get; }
public string DotNetRoot { get; }

public TestApp Application { get; }
public TestApp SelfContainedApplication { get; }

public TestProjectFixture ComponentWithNoDependenciesFixture { get; }

public string ComponentTypeName { get; }
public string ComponentEntryPoint1 => "ComponentEntryPoint1";
public string UnmanagedFunctionPointerEntryPoint1 => "UnmanagedFunctionPointerEntryPoint1";

public SharedTestState()
{
var dotNet = new Microsoft.DotNet.Cli.Build.DotNetCli(RepoDirectories.BuiltDotnet);
DotNetRoot = dotNet.BinPath;
HostFxrPath = dotNet.GreatestVersionHostFxrFilePath;

Application = TestApp.CreateEmpty("App");
RuntimeConfig.Path(Application.RuntimeConfigJson)
.WithFramework(Constants.MicrosoftNETCoreApp, RepoDirectories.MicrosoftNETCoreAppVersion)
.Save();
Application = NetCoreAppBuilder.PortableForNETCoreApp(Application)
.WithProject(p => p.WithAssemblyGroup(null, g => g.WithMainAssembly()))
.Build(Application);

SelfContainedApplication = TestApp.CreateEmpty("SelfContainedApp");
File.WriteAllText(SelfContainedApplication.AppDll, string.Empty);
var toCopy = Directory.GetFiles(dotNet.GreatestVersionSharedFxPath)
.Concat(Directory.GetFiles(dotNet.GreatestVersionHostFxrPath));
foreach (string file in toCopy)
{
File.Copy(file, Path.Combine(SelfContainedApplication.Location, Path.GetFileName(file)));
}

ComponentWithNoDependenciesFixture = new TestProjectFixture("ComponentWithNoDependencies", RepoDirectories)
.EnsureRestored()
.PublishProject();

ComponentTypeName = $"Component.Component, {ComponentWithNoDependenciesFixture.TestProject.AssemblyName}";
}

protected override void Dispose(bool disposing)
{
if (Application != null)
Application.Dispose();

if (SelfContainedApplication != null)
SelfContainedApplication.Dispose();

if (ComponentWithNoDependenciesFixture != null)
ComponentWithNoDependenciesFixture.Dispose();

base.Dispose(disposing);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
</type>
<type fullname="Internal.Runtime.InteropServices.ComponentActivator">
<!-- Used by hostpolicy.cpp -->
<method name="LoadAssembly" />
<method name="LoadAssemblyAndGetFunctionPointer" />
<method name="GetFunctionPointer" />
</type>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
is enabled and only then we show the trimming warnings about possibly missing dependencies.
-->
<type fullname="Internal.Runtime.InteropServices.ComponentActivator">
<method name="LoadAssembly" />
<method name="LoadAssemblyAndGetFunctionPointer" />
</type>
</assembly>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ internal static partial class ComponentActivator
[UnsupportedOSPlatform("tvos")]
private static readonly Dictionary<string, IsolatedComponentLoadContext> s_assemblyLoadContexts = new Dictionary<string, IsolatedComponentLoadContext>(StringComparer.InvariantCulture);
private static readonly Dictionary<IntPtr, Delegate> s_delegates = new Dictionary<IntPtr, Delegate>();
private static readonly HashSet<string> s_loadedInDefaultContext = new HashSet<string>(StringComparer.InvariantCulture);

// Use a value defined in https://github.com/dotnet/runtime/blob/main/docs/design/features/host-error-codes.md
// To indicate the specific error when IsSupported is false
Expand Down Expand Up @@ -96,6 +97,72 @@ public static unsafe int LoadAssemblyAndGetFunctionPointer(IntPtr assemblyPathNa
return 0;
}

/// <summary>
/// Native hosting entry point for loading an assembly from a path
/// </summary>
/// <param name="assemblyPathNative">Fully qualified path to assembly</param>
/// <param name="loadContext">Extensibility parameter (currently unused)</param>
/// <param name="reserved">Extensibility parameter (currently unused)</param>
[RequiresDynamicCode(NativeAOTIncompatibleWarningMessage)]
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("maccatalyst")]
[UnsupportedOSPlatform("tvos")]
[UnmanagedCallersOnly]
public static unsafe int LoadAssembly(IntPtr assemblyPathNative, IntPtr loadContext, IntPtr reserved)
{
if (!IsSupported)
return HostFeatureDisabled;

try
{
string assemblyPath = MarshalToString(assemblyPathNative, nameof(assemblyPathNative));

if (loadContext != IntPtr.Zero)
{
throw new ArgumentOutOfRangeException(nameof(loadContext));
}

if (reserved != IntPtr.Zero)
{
throw new ArgumentOutOfRangeException(nameof(reserved));
}

LoadAssemblyLocal(assemblyPath);
}
catch (Exception e)
{
return e.HResult;
}

return 0;

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "The same feature switch applies to GetFunctionPointer and this function. We rely on the warning from GetFunctionPointer.")]
static void LoadAssemblyLocal(string assemblyPath)
{
lock(s_loadedInDefaultContext)
{
if (s_loadedInDefaultContext.Contains(assemblyPath))
return;

var resolver = new AssemblyDependencyResolver(assemblyPath);
AssemblyLoadContext.Default.Resolving +=
(context, assemblyName) =>
{
string? assemblyPath = resolver.ResolveAssemblyToPath(assemblyName);
return assemblyPath != null
? context.LoadFromAssemblyPath(assemblyPath)
: null;
};

AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
s_loadedInDefaultContext.Add(assemblyPath);
}
}
}

/// <summary>
/// Native hosting entry point for creating a native delegate
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/native/corehost/coreclr_delegates.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,9 @@ typedef int (CORECLR_DELEGATE_CALLTYPE *get_function_pointer_fn)(
void *reserved /* Extensibility parameter (currently unused and must be 0) */,
/*out*/ void **delegate /* Pointer where to store the function pointer result */);

typedef int (CORECLR_DELEGATE_CALLTYPE *load_assembly_fn)(
const char_t *assembly_path /* Fully qualified path to assembly */,
void *load_context /* Extensibility parameter (currently unused and must be 0) */,
void *reserved /* Extensibility parameter (currently unused and must be 0) */);

#endif // __CORECLR_DELEGATES_H__
1 change: 1 addition & 0 deletions src/native/corehost/corehost_context_contract.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ enum class coreclr_delegate_type
com_unregister,
load_assembly_and_get_function_pointer,
get_function_pointer,
load_assembly,

__last, // Sentinel value for determining the last known delegate type
};
Expand Down
2 changes: 2 additions & 0 deletions src/native/corehost/fxr/hostfxr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,8 @@ namespace
return coreclr_delegate_type::load_assembly_and_get_function_pointer;
case hostfxr_delegate_type::hdt_get_function_pointer:
return coreclr_delegate_type::get_function_pointer;
case hostfxr_delegate_type::hdt_load_assembly:
return coreclr_delegate_type::load_assembly;
}
return coreclr_delegate_type::invalid;
}
Expand Down
1 change: 1 addition & 0 deletions src/native/corehost/hostfxr.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ enum hostfxr_delegate_type
hdt_com_unregister,
hdt_load_assembly_and_get_function_pointer,
hdt_get_function_pointer,
hdt_load_assembly,
};

typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_main_fn)(const int argc, const char_t **argv);
Expand Down
6 changes: 6 additions & 0 deletions src/native/corehost/hostpolicy/hostpolicy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,12 @@ namespace
"Internal.Runtime.InteropServices.ComponentActivator",
"GetFunctionPointer",
delegate);
case coreclr_delegate_type::load_assembly:
return coreclr->create_delegate(
"System.Private.CoreLib",
"Internal.Runtime.InteropServices.ComponentActivator",
"LoadAssembly",
delegate);
default:
return StatusCode::LibHostInvalidArgs;
}
Expand Down
Loading