diff --git a/src/mscorlib/Resources/Strings.resx b/src/mscorlib/Resources/Strings.resx index 49c8e932498f..45036473f334 100644 --- a/src/mscorlib/Resources/Strings.resx +++ b/src/mscorlib/Resources/Strings.resx @@ -1252,6 +1252,12 @@ The specified serialized string '{0}' is not supported. + + The syntax of the startup hook variable was invalid. + + + The signature of the startup hook '{0}' in assembly '{1}' was invalid. It must be 'public static void Initialize()'. + An undefined TimeSpanStyles value is being used. diff --git a/src/mscorlib/System.Private.CoreLib.csproj b/src/mscorlib/System.Private.CoreLib.csproj index b40bb53cb53b..b365ff50ad29 100644 --- a/src/mscorlib/System.Private.CoreLib.csproj +++ b/src/mscorlib/System.Private.CoreLib.csproj @@ -355,6 +355,7 @@ + diff --git a/src/mscorlib/src/System/StartupHookProvider.cs b/src/mscorlib/src/System/StartupHookProvider.cs new file mode 100644 index 000000000000..cc6f8d41fdac --- /dev/null +++ b/src/mscorlib/src/System/StartupHookProvider.cs @@ -0,0 +1,120 @@ +// 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. + +using System; +using System.Diagnostics; +using System.IO; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Loader; + +namespace System +{ + internal static class StartupHookProvider + { + private const string StartupHookTypeName = "StartupHook"; + private const string InitializeMethodName = "Initialize"; + + // Parse a string specifying a list of assemblies and types + // containing a startup hook, and call each hook in turn. + private static void ProcessStartupHooks() + { + string startupHooksVariable = (string)AppContext.GetData("STARTUP_HOOKS"); + if (startupHooksVariable == null) + { + return; + } + + // Parse startup hooks variable + string[] startupHooks = startupHooksVariable.Split(Path.PathSeparator); + foreach (string startupHook in startupHooks) + { + if (String.IsNullOrEmpty(startupHook)) + { + throw new ArgumentException(SR.Argument_InvalidStartupHookSyntax); + } + if (PathInternal.IsPartiallyQualified(startupHook)) + { + throw new ArgumentException(SR.Argument_AbsolutePathRequired); + } + } + + // Call each hook in turn + foreach (string startupHook in startupHooks) + { + CallStartupHook(startupHook); + } + } + + // Load the specified assembly, and call the specified type's + // "static void Initialize()" method. + private static void CallStartupHook(string assemblyPath) + { + Debug.Assert(!String.IsNullOrEmpty(assemblyPath)); + Debug.Assert(!PathInternal.IsPartiallyQualified(assemblyPath)); + + Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); + Debug.Assert(assembly != null); + Type type = assembly.GetType(StartupHookTypeName, throwOnError: true); + + // Look for a static method without any parameters + MethodInfo initializeMethod = type.GetMethod(InitializeMethodName, + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, + null, // use default binder + Type.EmptyTypes, // parameters + null); // no parameter modifiers + + bool wrongSignature = false; + if (initializeMethod == null) + { + // There weren't any static methods without + // parameters. Look for any methods with the correct + // name, to provide precise error handling. + try + { + // This could find zero, one, or multiple methods + // with the correct name. + initializeMethod = type.GetMethod(InitializeMethodName, + BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Static | BindingFlags.Instance); + } + catch (AmbiguousMatchException) + { + // Found multiple + Debug.Assert(initializeMethod == null); + wrongSignature = true; + } + if (initializeMethod != null) + { + // Found one + wrongSignature = true; + } + else + { + // Didn't find any + throw new MissingMethodException(StartupHookTypeName, InitializeMethodName); + } + } + else if (initializeMethod.ReturnType != typeof(void)) + { + wrongSignature = true; + } + + if (wrongSignature) + { + throw new ArgumentException(SR.Format(SR.Argument_InvalidStartupHookSignature, + StartupHookTypeName + Type.Delimiter + InitializeMethodName, + assemblyPath)); + } + + Debug.Assert(initializeMethod != null && + initializeMethod.IsStatic && + initializeMethod.ReturnType == typeof(void) && + initializeMethod.GetParameters().Length == 0); + + initializeMethod.Invoke(null, null); + } + } +} diff --git a/src/vm/assembly.cpp b/src/vm/assembly.cpp index d2551b5683a5..a88be6b2e4b3 100644 --- a/src/vm/assembly.cpp +++ b/src/vm/assembly.cpp @@ -1764,6 +1764,21 @@ static void RunMainPost() } } +static void RunStartupHooks() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + MethodDescCallSite processStartupHooks(METHOD__STARTUP_HOOK_PROVIDER__PROCESS_STARTUP_HOOKS); + processStartupHooks.Call(NULL); +} + INT32 Assembly::ExecuteMainMethod(PTRARRAYREF *stringArgs, BOOL waitForOtherThreads) { CONTRACTL @@ -1808,7 +1823,6 @@ INT32 Assembly::ExecuteMainMethod(PTRARRAYREF *stringArgs, BOOL waitForOtherThre RunMainPre(); - // Set the root assembly as the assembly that is containing the main method // The root assembly is used in the GetEntryAssembly method that on CoreCLR is used // to get the TargetFrameworkMoniker for the app @@ -1819,6 +1833,7 @@ INT32 Assembly::ExecuteMainMethod(PTRARRAYREF *stringArgs, BOOL waitForOtherThre // Initialize the managed components of EventPipe and allow tracing to be started before Main. EventPipe::InitializeManaged(); #endif + RunStartupHooks(); hr = RunMain(pMeth, 1, &iRetVal, stringArgs); } diff --git a/src/vm/mscorlib.h b/src/vm/mscorlib.h index 810cc72b633c..6b65a0b692b5 100644 --- a/src/vm/mscorlib.h +++ b/src/vm/mscorlib.h @@ -854,6 +854,9 @@ DEFINE_CLASS(EVENTPIPE_CONTROLLER, Tracing, EventPipeController) DEFINE_METHOD(EVENTPIPE_CONTROLLER, INITIALIZE, Initialize, SM_RetVoid) #endif +DEFINE_CLASS(STARTUP_HOOK_PROVIDER, System, StartupHookProvider) +DEFINE_METHOD(STARTUP_HOOK_PROVIDER, PROCESS_STARTUP_HOOKS, ProcessStartupHooks, SM_RetVoid) + DEFINE_CLASS(STREAM, IO, Stream) DEFINE_METHOD(STREAM, BEGIN_READ, BeginRead, IM_ArrByte_Int_Int_AsyncCallback_Object_RetIAsyncResult) DEFINE_METHOD(STREAM, END_READ, EndRead, IM_IAsyncResult_RetInt)