diff --git a/src/coreclr/dlls/mscoree/exports.cpp b/src/coreclr/dlls/mscoree/exports.cpp index e8ee88275df8e..1d9cc711e06e2 100644 --- a/src/coreclr/dlls/mscoree/exports.cpp +++ b/src/coreclr/dlls/mscoree/exports.cpp @@ -19,6 +19,8 @@ #endif // FEATURE_GDBJIT #include "bundle.h" #include "pinvokeoverride.h" +#include +#include #define ASSERTE_ALL_BUILDS(expr) _ASSERTE_ALL_BUILDS((expr)) @@ -122,7 +124,8 @@ static void ConvertConfigPropertiesToUnicode( LPCWSTR** propertyValuesWRef, BundleProbeFn** bundleProbe, PInvokeOverrideFn** pinvokeOverride, - bool* hostPolicyEmbedded) + bool* hostPolicyEmbedded, + host_runtime_contract** hostContract) { LPCWSTR* propertyKeysW = new (nothrow) LPCWSTR[propertyCount]; ASSERTE_ALL_BUILDS(propertyKeysW != nullptr); @@ -135,23 +138,43 @@ static void ConvertConfigPropertiesToUnicode( propertyKeysW[propertyIndex] = StringToUnicode(propertyKeys[propertyIndex]); propertyValuesW[propertyIndex] = StringToUnicode(propertyValues[propertyIndex]); - if (strcmp(propertyKeys[propertyIndex], "BUNDLE_PROBE") == 0) + if (strcmp(propertyKeys[propertyIndex], HOST_PROPERTY_BUNDLE_PROBE) == 0) { // If this application is a single-file bundle, the bundle-probe callback // is passed in as the value of "BUNDLE_PROBE" property (encoded as a string). - *bundleProbe = (BundleProbeFn*)_wcstoui64(propertyValuesW[propertyIndex], nullptr, 0); + // The function in HOST_RUNTIME_CONTRACT is given priority over this property, + // so we only set the bundle probe if it has not already been set. + if (*bundleProbe == nullptr) + *bundleProbe = (BundleProbeFn*)_wcstoui64(propertyValuesW[propertyIndex], nullptr, 0); } - else if (strcmp(propertyKeys[propertyIndex], "PINVOKE_OVERRIDE") == 0) + else if (strcmp(propertyKeys[propertyIndex], HOST_PROPERTY_PINVOKE_OVERRIDE) == 0) { // If host provides a PInvoke override (typically in a single-file bundle), // the override callback is passed in as the value of "PINVOKE_OVERRIDE" property (encoded as a string). - *pinvokeOverride = (PInvokeOverrideFn*)_wcstoui64(propertyValuesW[propertyIndex], nullptr, 0); + // The function in HOST_RUNTIME_CONTRACT is given priority over this property, + // so we only set the p/invoke override if it has not already been set. + if (*pinvokeOverride == nullptr) + *pinvokeOverride = (PInvokeOverrideFn*)_wcstoui64(propertyValuesW[propertyIndex], nullptr, 0); } - else if (strcmp(propertyKeys[propertyIndex], "HOSTPOLICY_EMBEDDED") == 0) + else if (strcmp(propertyKeys[propertyIndex], HOST_PROPERTY_HOSTPOLICY_EMBEDDED) == 0) { // The HOSTPOLICY_EMBEDDED property indicates if the executable has hostpolicy statically linked in *hostPolicyEmbedded = (wcscmp(propertyValuesW[propertyIndex], W("true")) == 0); } + else if (strcmp(propertyKeys[propertyIndex], HOST_PROPERTY_RUNTIME_CONTRACT) == 0) + { + // Host contract is passed in as the value of HOST_RUNTIME_CONTRACT property (encoded as a string). + host_runtime_contract* hostContractLocal = (host_runtime_contract*)_wcstoui64(propertyValuesW[propertyIndex], nullptr, 0); + *hostContract = hostContractLocal; + + // Functions in HOST_RUNTIME_CONTRACT have priority over the individual properties + // for callbacks, so we set them as long as the contract has a non-null function. + if (hostContractLocal->bundle_probe != nullptr) + *bundleProbe = hostContractLocal->bundle_probe; + + if (hostContractLocal->pinvoke_override != nullptr) + *pinvokeOverride = hostContractLocal->pinvoke_override; + } } *propertyKeysWRef = propertyKeysW; @@ -196,6 +219,7 @@ int coreclr_initialize( BundleProbeFn* bundleProbe = nullptr; bool hostPolicyEmbedded = false; PInvokeOverrideFn* pinvokeOverride = nullptr; + host_runtime_contract* hostContract = nullptr; ConvertConfigPropertiesToUnicode( propertyKeys, @@ -205,7 +229,8 @@ int coreclr_initialize( &propertyValuesW, &bundleProbe, &pinvokeOverride, - &hostPolicyEmbedded); + &hostPolicyEmbedded, + &hostContract); #ifdef TARGET_UNIX DWORD error = PAL_InitializeCoreCLR(exePath, g_coreclr_embedded); @@ -221,6 +246,11 @@ int coreclr_initialize( g_hostpolicy_embedded = hostPolicyEmbedded; + if (hostContract != nullptr) + { + HostInformation::SetContract(hostContract); + } + if (pinvokeOverride != nullptr) { PInvokeOverride::SetPInvokeOverride(pinvokeOverride, PInvokeOverride::Source::RuntimeConfiguration); diff --git a/src/coreclr/inc/hostinformation.h b/src/coreclr/inc/hostinformation.h new file mode 100644 index 0000000000000..d57b4729d30e6 --- /dev/null +++ b/src/coreclr/inc/hostinformation.h @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef _HOSTINFORMATION_H_ +#define _HOSTINFORMATION_H_ + +#include + +class HostInformation +{ +public: + static void SetContract(_In_ host_runtime_contract* hostContract); + static bool GetProperty(_In_z_ const char* name, SString& value); +}; + +#endif // _HOSTINFORMATION_H_ diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index 68169ccb4edea..7d7826b5a0d70 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -328,6 +328,7 @@ set(VM_SOURCES_WKS genanalysis.cpp genmeth.cpp hosting.cpp + hostinformation.cpp ilmarshalers.cpp interopconverter.cpp interoputil.cpp diff --git a/src/coreclr/vm/corhost.cpp b/src/coreclr/vm/corhost.cpp index 72fe94761c1db..052e6a0480c18 100644 --- a/src/coreclr/vm/corhost.cpp +++ b/src/coreclr/vm/corhost.cpp @@ -38,6 +38,8 @@ #ifndef DACCESS_COMPILE +#include + extern void STDMETHODCALLTYPE EEShutDown(BOOL fIsDllUnloading); //*************************************************************************** @@ -578,22 +580,22 @@ HRESULT CorHost2::CreateAppDomainWithManager( for (int i = 0; i < nProperties; i++) { - if (wcscmp(pPropertyNames[i], W("NATIVE_DLL_SEARCH_DIRECTORIES")) == 0) + if (wcscmp(pPropertyNames[i], _T(HOST_PROPERTY_NATIVE_DLL_SEARCH_DIRECTORIES)) == 0) { pwzNativeDllSearchDirectories = pPropertyValues[i]; } else - if (wcscmp(pPropertyNames[i], W("TRUSTED_PLATFORM_ASSEMBLIES")) == 0) + if (wcscmp(pPropertyNames[i], _T(HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES)) == 0) { pwzTrustedPlatformAssemblies = pPropertyValues[i]; } else - if (wcscmp(pPropertyNames[i], W("PLATFORM_RESOURCE_ROOTS")) == 0) + if (wcscmp(pPropertyNames[i], _T(HOST_PROPERTY_PLATFORM_RESOURCE_ROOTS)) == 0) { pwzPlatformResourceRoots = pPropertyValues[i]; } else - if (wcscmp(pPropertyNames[i], W("APP_PATHS")) == 0) + if (wcscmp(pPropertyNames[i], _T(HOST_PROPERTY_APP_PATHS)) == 0) { pwzAppPaths = pPropertyValues[i]; } diff --git a/src/coreclr/vm/hostinformation.cpp b/src/coreclr/vm/hostinformation.cpp new file mode 100644 index 0000000000000..b440f6f2168ab --- /dev/null +++ b/src/coreclr/vm/hostinformation.cpp @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "common.h" +#include "hostinformation.h" + +namespace +{ + host_runtime_contract* s_hostContract = nullptr; +} + +void HostInformation::SetContract(_In_ host_runtime_contract* hostContract) +{ + _ASSERTE(s_hostContract == nullptr); + s_hostContract = hostContract; +} + +bool HostInformation::GetProperty(_In_z_ const char* name, SString& value) +{ + if (s_hostContract == nullptr || s_hostContract->get_runtime_property == nullptr) + return false; + + size_t len = MAX_PATH + 1; + char* dest = value.OpenUTF8Buffer(static_cast(len)); + size_t lenActual = s_hostContract->get_runtime_property(name, dest, len, s_hostContract->context); + value.CloseBuffer(); + + // Doesn't exist or failed to get property + if (lenActual == (size_t)-1 || lenActual == 0) + return false; + + if (lenActual <= len) + return true; + + // Buffer was not large enough + len = lenActual; + dest = value.OpenUTF8Buffer(static_cast(len)); + lenActual = s_hostContract->get_runtime_property(name, dest, len, s_hostContract->context); + value.CloseBuffer(); + + return lenActual > 0 && lenActual <= len; +} diff --git a/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostApiInvokerApp.csproj b/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostApiInvokerApp.csproj index 087d0c77a536d..d040ee25bcfec 100644 --- a/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostApiInvokerApp.csproj +++ b/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostApiInvokerApp.csproj @@ -5,10 +5,7 @@ Exe $(MNAVersion) WINDOWS;$(DefineConstants) + true - - - - diff --git a/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostRuntimeContract.cs b/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostRuntimeContract.cs new file mode 100644 index 0000000000000..a2a4d6161acea --- /dev/null +++ b/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/HostRuntimeContract.cs @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace HostApiInvokerApp +{ + public static unsafe class HostRuntimeContract + { + internal struct host_runtime_contract + { + public nint size; + public void* context; + public delegate* unmanaged[Stdcall] get_runtime_property; + public IntPtr bundle_probe; + public IntPtr pinvoke_override; + } + + private static host_runtime_contract GetContract() + { + string contractString = (string)AppContext.GetData("HOST_RUNTIME_CONTRACT"); + if (string.IsNullOrEmpty(contractString)) + throw new Exception("HOST_RUNTIME_CONTRACT not found"); + + host_runtime_contract* contract = (host_runtime_contract*)Convert.ToUInt64(contractString, 16); + if (contract->size != sizeof(host_runtime_contract)) + throw new Exception($"Unexpected contract size {contract->size}. Expected: {sizeof(host_runtime_contract)}"); + + return *contract; + } + + private static void Test_get_runtime_property(string[] args) + { + host_runtime_contract contract = GetContract(); + + foreach (string name in args) + { + string value = GetProperty(name, contract); + Console.WriteLine($"{nameof(host_runtime_contract.get_runtime_property)}: {name} = {(value == null ? "" : value)}"); + } + + static string GetProperty(string name, host_runtime_contract contract) + { + Span nameSpan = stackalloc byte[Encoding.UTF8.GetMaxByteCount(name.Length)]; + byte* namePtr = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(nameSpan)); + int nameLen = Encoding.UTF8.GetBytes(name, nameSpan); + nameSpan[nameLen] = 0; + + nint len = 256; + byte* buffer = stackalloc byte[(int)len]; + nint lenActual = contract.get_runtime_property(namePtr, buffer, len, contract.context); + if (lenActual <= 0) + { + Console.WriteLine($"No value for {name} - {nameof(host_runtime_contract.get_runtime_property)} returned {lenActual}"); + return null; + } + + if (lenActual <= len) + return Encoding.UTF8.GetString(buffer, (int)lenActual); + + len = lenActual; + byte* expandedBuffer = stackalloc byte[(int)len]; + lenActual = contract.get_runtime_property(namePtr, expandedBuffer, len, contract.context); + return Encoding.UTF8.GetString(expandedBuffer, (int)lenActual); + } + } + + public static bool RunTest(string apiToTest, string[] args) + { + switch (apiToTest) + { + case $"{nameof(host_runtime_contract)}.{nameof(host_runtime_contract.get_runtime_property)}": + Test_get_runtime_property(args); + break; + default: + return false; + } + + return true; + } + } + +} diff --git a/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/Program.cs b/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/Program.cs index 1455a80a358df..2831ed8c3d487 100644 --- a/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/Program.cs +++ b/src/installer/tests/Assets/TestProjects/HostApiInvokerApp/Program.cs @@ -31,16 +31,13 @@ public static void MainCore(string[] args) Console.WriteLine("Hello World!"); Console.WriteLine(string.Join(Environment.NewLine, args)); - // A small operation involving NewtonSoft.Json to ensure the assembly is loaded properly - var t = typeof(Newtonsoft.Json.JsonReader); - // Enable tracing so that test assertion failures are easier to diagnose. Environment.SetEnvironmentVariable("COREHOST_TRACE", "1"); // If requested, test multilevel lookup using fake Global SDK directories: // 1. using a fake ProgramFiles location // 2. using a fake SDK Self-Registered location - // Note that this has to be set here and not in the calling test process because + // Note that this has to be set here and not in the calling test process because // %ProgramFiles% gets reset on process creation. string testMultilevelLookupProgramFiles = Environment.GetEnvironmentVariable("TEST_MULTILEVEL_LOOKUP_PROGRAM_FILES"); string testMultilevelLookupSelfRegistered = Environment.GetEnvironmentVariable("TEST_MULTILEVEL_LOOKUP_SELF_REGISTERED"); @@ -65,17 +62,15 @@ public static void MainCore(string[] args) string apiToTest = args[0]; if (HostFXR.RunTest(apiToTest, args)) - { return; - } - else if (HostPolicy.RunTest(apiToTest, args)) - { + + if (HostPolicy.RunTest(apiToTest, args)) return; - } - else - { - throw new ArgumentException($"Invalid API to test passed as args[0]): {apiToTest}"); - } + + if (HostRuntimeContract.RunTest(apiToTest, args)) + return; + + throw new ArgumentException($"Invalid API to test passed as args[0]): {apiToTest}"); } } } diff --git a/src/installer/tests/HostActivation.Tests/NativeHostApis.cs b/src/installer/tests/HostActivation.Tests/NativeHostApis.cs index 213b3790335ce..1649b2afeb33f 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHostApis.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHostApis.cs @@ -473,6 +473,20 @@ public void Hostpolicy_corehost_set_error_writer_test() .Should().Pass(); } + [Fact] + public void HostRuntimeContract_get_runtime_property() + { + var fixture = sharedTestState.HostApiInvokerAppFixture; + + fixture.BuiltDotnet.Exec(fixture.TestProject.AppDll, "host_runtime_contract.get_runtime_property", "APP_CONTEXT_BASE_DIRECTORY", "DOES_NOT_EXIST") + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should().Pass() + .And.HaveStdOutContaining($"APP_CONTEXT_BASE_DIRECTORY = {Path.GetDirectoryName(fixture.TestProject.AppDll)}") + .And.HaveStdOutContaining($"DOES_NOT_EXIST = "); + } + public class SharedTestState : IDisposable { public TestProjectFixture HostApiInvokerAppFixture { get; } diff --git a/src/native/corehost/host_runtime_contract.h b/src/native/corehost/host_runtime_contract.h new file mode 100644 index 0000000000000..919766eae8fa3 --- /dev/null +++ b/src/native/corehost/host_runtime_contract.h @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __HOST_RUNTIME_CONTRACT_H__ +#define __HOST_RUNTIME_CONTRACT_H__ + +#include +#include + +#if defined(_WIN32) + #define HOST_CONTRACT_CALLTYPE __stdcall +#else + #define HOST_CONTRACT_CALLTYPE +#endif + +// Known host property names +#define HOST_PROPERTY_RUNTIME_CONTRACT "HOST_RUNTIME_CONTRACT" +#define HOST_PROPERTY_APP_PATHS "APP_PATHS" +#define HOST_PROPERTY_BUNDLE_PROBE "BUNDLE_PROBE" +#define HOST_PROPERTY_HOSTPOLICY_EMBEDDED "HOSTPOLICY_EMBEDDED" +#define HOST_PROPERTY_NATIVE_DLL_SEARCH_DIRECTORIES "NATIVE_DLL_SEARCH_DIRECTORIES" +#define HOST_PROPERTY_PINVOKE_OVERRIDE "PINVOKE_OVERRIDE" +#define HOST_PROPERTY_PLATFORM_RESOURCE_ROOTS "PLATFORM_RESOURCE_ROOTS" +#define HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES "TRUSTED_PLATFORM_ASSEMBLIES" + +struct host_runtime_contract +{ + size_t size; + + // Context for the contract. Pass to functions taking a contract context. + void* context; + + // Get the value of a runtime property. + // Returns the length of the property including a terminating null or -1 if not found. + size_t(HOST_CONTRACT_CALLTYPE* get_runtime_property)( + const char* key, + /*out*/ char* value_buffer, + size_t value_buffer_size, + void* contract_context); + + // Probe an app bundle for `path`. Sets its location (`offset`, `size`) in the bundle if found. + // Returns true if found, false otherwise. + bool(HOST_CONTRACT_CALLTYPE* bundle_probe)( + const char* path, + /*out*/ int64_t* offset, + /*out*/ int64_t* size, + /*out*/ int64_t* compressedSize); + + // Get the function overriding the specified p/invoke (`library_name`, `entry_point_name`). + // Returns a pointer to the function if the p/invoke is overridden, nullptr otherwise. + const void* (HOST_CONTRACT_CALLTYPE* pinvoke_override)( + const char* library_name, + const char* entry_point_name); +}; + +#endif // __HOST_RUNTIME_CONTRACT_H__ diff --git a/src/native/corehost/hostmisc/pal.h b/src/native/corehost/hostmisc/pal.h index 2af1dbd214fd6..dee5bf3c94d44 100644 --- a/src/native/corehost/hostmisc/pal.h +++ b/src/native/corehost/hostmisc/pal.h @@ -180,6 +180,7 @@ namespace pal return buffer; } + size_t pal_utf8string(const string_t& str, char* out_buffer, size_t len); bool pal_utf8string(const string_t& str, std::vector* out); bool pal_clrstring(const string_t& str, std::vector* out); bool clr_palstring(const char* cstr, string_t* out); @@ -236,6 +237,16 @@ namespace pal inline const string_t strerror(int errnum) { return ::strerror(errnum); } + inline size_t pal_utf8string(const string_t& str, char* out_buffer, size_t buffer_len) + { + size_t len = str.size() + 1; + if (buffer_len < len) + return len; + + ::strncpy(out_buffer, str.c_str(), str.size()); + out_buffer[len - 1] = '\0'; + return len; + } inline bool pal_utf8string(const string_t& str, std::vector* out) { out->assign(str.begin(), str.end()); out->push_back('\0'); return true; } inline bool pal_clrstring(const string_t& str, std::vector* out) { return pal_utf8string(str, out); } inline bool clr_palstring(const char* cstr, string_t* out) { out->assign(cstr); return true; } diff --git a/src/native/corehost/hostmisc/pal.windows.cpp b/src/native/corehost/hostmisc/pal.windows.cpp index bbb6fabb30b79..4dd632ac88d33 100644 --- a/src/native/corehost/hostmisc/pal.windows.cpp +++ b/src/native/corehost/hostmisc/pal.windows.cpp @@ -689,6 +689,17 @@ static bool wchar_convert_helper(DWORD code_page, const char* cstr, size_t len, return ::MultiByteToWideChar(code_page, 0, cstr, static_cast(len), &(*out)[0], static_cast(out->size())) != 0; } +size_t pal::pal_utf8string(const pal::string_t& str, char* out_buffer, size_t len) +{ + // Pass -1 as we want explicit null termination in the char buffer. + size_t size = ::WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, nullptr, 0, nullptr, nullptr); + if (size == 0 || size > len) + return size; + + // Pass -1 as we want explicit null termination in the char buffer. + return ::WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, out_buffer, static_cast(len), nullptr, nullptr); +} + bool pal::pal_utf8string(const pal::string_t& str, std::vector* out) { out->clear(); diff --git a/src/native/corehost/hostpolicy/hostpolicy_context.cpp b/src/native/corehost/hostpolicy/hostpolicy_context.cpp index e1cff0dc376ef..7d27951ed226f 100644 --- a/src/native/corehost/hostpolicy/hostpolicy_context.cpp +++ b/src/native/corehost/hostpolicy/hostpolicy_context.cpp @@ -2,7 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "hostpolicy_context.h" -#include "hostpolicy.h" +#include +#include #include "deps_resolver.h" #include @@ -104,6 +105,27 @@ namespace return nullptr; } #endif + + size_t HOST_CONTRACT_CALLTYPE get_runtime_property( + const char* key, + char* value_buffer, + size_t value_buffer_size, + void* contract_context) + { + hostpolicy_context_t* context = static_cast(contract_context); + + pal::string_t key_str; + if (pal::clr_palstring(key, &key_str)) + { + const pal::char_t* value; + if (context->coreclr_properties.try_get(key_str.c_str(), &value)) + { + return pal::pal_utf8string(value, value_buffer, value_buffer_size); + } + } + + return -1; + } } int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const arguments_t &args, bool enable_breadcrumbs) @@ -324,5 +346,25 @@ int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const a } #endif + { + host_contract = { sizeof(host_runtime_contract), this }; + if (bundle::info_t::is_single_file_bundle()) + { + host_contract.bundle_probe = &bundle_probe; +#if defined(NATIVE_LIBS_EMBEDDED) + host_contract.pinvoke_override = &pinvoke_override; +#endif + } + + host_contract.get_runtime_property = &get_runtime_property; + pal::stringstream_t ptr_stream; + ptr_stream << "0x" << std::hex << (size_t)(&host_contract); + if (!coreclr_properties.add(_STRINGIFY(HOST_PROPERTY_RUNTIME_CONTRACT), ptr_stream.str().c_str())) + { + log_duplicate_property_error(_STRINGIFY(HOST_PROPERTY_RUNTIME_CONTRACT)); + return StatusCode::LibHostDuplicateProperty; + } + } + return StatusCode::Success; } diff --git a/src/native/corehost/hostpolicy/hostpolicy_context.h b/src/native/corehost/hostpolicy/hostpolicy_context.h index 2853e6ae98fb1..bec4630762fe0 100644 --- a/src/native/corehost/hostpolicy/hostpolicy_context.h +++ b/src/native/corehost/hostpolicy/hostpolicy_context.h @@ -9,6 +9,7 @@ #include "args.h" #include "coreclr.h" #include +#include #include "hostpolicy_init.h" struct hostpolicy_context_t @@ -27,6 +28,8 @@ struct hostpolicy_context_t std::unique_ptr coreclr; + host_runtime_contract host_contract; + int initialize(hostpolicy_init_t &hostpolicy_init, const arguments_t &args, bool enable_breadcrumbs); };