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)