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
19 changes: 18 additions & 1 deletion docs/design/features/host-runtime-information.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,22 @@ Hex string representation of a pointer to a [`host_runtime_contract` struct](/sr

The `get_runtime_property` function provides key-value string information (like that provided for runtime initialization). This removes the requirement to pre-compute and store all properties. [Existing properties](#well-known-runtime-properties) can be migrated to go through this mechanism, allowing for pay-for-play properties and reducing the cost of properties.

Some existing properties (for example, [probing path properties](#probing-paths)) would benefit from being structured data rather than a single string. The contract can be extended to allow querying for that specific, structured information rather than relying only on strings. For backwards compatibility for existing properties, we would still allow getting the string via `get_runtime_property`, but that would be an on-demand cost.
Some existing properties benefit from being provided as structured data rather than a single string. The contract can expose callbacks for querying this information while continuing to provide the corresponding string through `get_runtime_property` for backwards compatibility.

### Application assembly paths

**Added in .NET 12**

The host provides resolved assembly paths through two callbacks:

- `get_assembly_names` returns the simple names of all host-resolved assemblies.
- `resolve_assembly_to_path` resolves a simple name to directory and file name components.

The returned strings are owned by the host and remain valid for the lifetime of the process. The runtime records the assembly names during initialization and resolves each path on demand.

Both callbacks must be available for the structured representation to be used. If either callback is unavailable or `get_assembly_names` returns `false`, the runtime falls back to the [`TRUSTED_PLATFORM_ASSEMBLIES`](#probing-paths) property.

For compatibility, `get_runtime_property` returns the path-separated `TRUSTED_PLATFORM_ASSEMBLIES` string on demand. A custom host can explicitly set that property to use the legacy representation instead of the structured callbacks.

## Well-known runtime properties

Expand Down Expand Up @@ -66,6 +81,8 @@ List of assemblies (paths or names) containing a [`StartupHook`](./host-startup-

List of platform and application assembly file paths. Paths are delimited by a [platform-specific path separator](#path-separator). This is used in [managed assembly probing](https://learn.microsoft.com/dotnet/core/dependency-loading/default-probing#managed-assembly-default-probing).

**.NET 12 and above** The host provides this information through the [application assembly callbacks](#application-assembly-paths) by default. The string representation remains available through `host_runtime_contract.get_runtime_property` and can still be explicitly supplied by a custom host.

`NATIVE_DLL_SEARCH_DIRECTORIES`

List of directory paths to search for unmanaged (native) libraries. Paths are delimited by a [platform-specific path separator](#path-separator). This is used in [unmanaged (native) assembly probing](https://learn.microsoft.com/dotnet/core/dependency-loading/default-probing#unmanaged-native-library-probing).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ private static bool IsKnownHostProperty(string name)
or "PLATFORM_RESOURCE_ROOTS"
or "APP_PATHS";

private static bool TryGetHostPropertyValue(string name, out string? value)
{
value = null;
return TryGetHostPropertyValue(name, new StringHandleOnStack(ref value));
}

[ErrorHandler(typeof(QCallExceptionStatusMarshaller), ErrorLocation.HiddenLastParameter)]
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "AppContext_TryGetHostPropertyValue", StringMarshalling = StringMarshalling.Utf16)]
[return: MarshalAs(UnmanagedType.Bool)]
Expand Down
74 changes: 39 additions & 35 deletions src/coreclr/binder/applicationcontext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "assemblyhashtraits.hpp"
#include "stringarraylist.h"
#include "failurecache.hpp"
#include "hostinformation.h"
#include "utils.hpp"
#include "ex.h"
#include "clr/fs/path.h"
Expand Down Expand Up @@ -91,51 +92,54 @@ namespace BINDER_SPACE
//
m_pTrustedPlatformAssemblyMap = new SimpleNameToFileNameMap();

sTrustedPlatformAssemblies.Normalize();
for (SString::Iterator i = sTrustedPlatformAssemblies.Begin(); i != sTrustedPlatformAssemblies.End(); )
const char* const* assemblyNames;
size_t assemblyCount;
if (HostInformation::GetAssemblyNames(&assemblyNames, &assemblyCount))
Comment thread
AaronRobinsonMSFT marked this conversation as resolved.
{
SString fileName;
SString simpleName;
HRESULT pathResult = S_OK;
IF_FAIL_GO(pathResult = GetNextTPAPath(sTrustedPlatformAssemblies, i, /*dllOnly*/ false, fileName, simpleName));
if (pathResult == S_FALSE)
for (size_t i = 0; i < assemblyCount; i++)
Comment thread
AaronRobinsonMSFT marked this conversation as resolved.
{
break;
}
StackSString simpleName(SString::Utf8, assemblyNames[i]);
_ASSERT(!simpleName.IsEmpty());

const SimpleNameToFileNameMapEntry *pExistingEntry = m_pTrustedPlatformAssemblyMap->LookupPtr(simpleName.GetUnicode());
if (pExistingEntry != nullptr)
{
continue;
}
if (m_pTrustedPlatformAssemblyMap->LookupPtr(simpleName.GetUnicode()) != nullptr)
continue;

LPWSTR wszSimpleName = nullptr;
if (pExistingEntry == nullptr)
{
wszSimpleName = new WCHAR[simpleName.GetCount() + 1];
if (wszSimpleName == nullptr)
{
GO_WITH_HRESULT(E_OUTOFMEMORY);
}
LPWSTR wszSimpleName = new WCHAR[simpleName.GetCount() + 1];
Comment thread
elinor-fung marked this conversation as resolved.
wcscpy_s(wszSimpleName, simpleName.GetCount() + 1, simpleName.GetUnicode());

Comment thread
elinor-fung marked this conversation as resolved.
SimpleNameToFileNameMapEntry mapEntry{ wszSimpleName, nullptr };
m_pTrustedPlatformAssemblyMap->AddOrReplace(mapEntry);
}
else
}
else
{
sTrustedPlatformAssemblies.Normalize();
for (SString::Iterator i = sTrustedPlatformAssemblies.Begin(); i != sTrustedPlatformAssemblies.End(); )
{
wszSimpleName = pExistingEntry->m_wszSimpleName;
}
SString fileName;
SString simpleName;
HRESULT pathResult = S_OK;
IF_FAIL_GO(pathResult = GetNextTPAPath(sTrustedPlatformAssemblies, i, /*dllOnly*/ false, fileName, simpleName));
if (pathResult == S_FALSE)
{
break;
}

LPWSTR wszFileName = new WCHAR[fileName.GetCount() + 1];
if (wszFileName == nullptr)
{
GO_WITH_HRESULT(E_OUTOFMEMORY);
}
wcscpy_s(wszFileName, fileName.GetCount() + 1, fileName.GetUnicode());
const SimpleNameToFileNameMapEntry *pExistingEntry = m_pTrustedPlatformAssemblyMap->LookupPtr(simpleName.GetUnicode());
if (pExistingEntry != nullptr)
{
continue;
Comment thread
elinor-fung marked this conversation as resolved.
}

SimpleNameToFileNameMapEntry mapEntry;
mapEntry.m_wszSimpleName = wszSimpleName;
mapEntry.m_wszILFileName = wszFileName;
LPWSTR wszSimpleName = new WCHAR[simpleName.GetCount() + 1];
Comment thread
elinor-fung marked this conversation as resolved.
wcscpy_s(wszSimpleName, simpleName.GetCount() + 1, simpleName.GetUnicode());

m_pTrustedPlatformAssemblyMap->AddOrReplace(mapEntry);
LPWSTR wszFileName = new WCHAR[fileName.GetCount() + 1];
Comment thread
elinor-fung marked this conversation as resolved.
wcscpy_s(wszFileName, fileName.GetCount() + 1, fileName.GetUnicode());

SimpleNameToFileNameMapEntry mapEntry{ wszSimpleName, wszFileName };
m_pTrustedPlatformAssemblyMap->AddOrReplace(mapEntry);
}
}

//
Expand Down
17 changes: 13 additions & 4 deletions src/coreclr/binder/assemblybindercommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include "common.h"
#include "assemblybindercommon.hpp"
#include "hostinformation.h"
#include "assemblyname.hpp"
#include "assembly.hpp"
#include "applicationcontext.hpp"
Expand Down Expand Up @@ -897,11 +898,21 @@ namespace BINDER_SPACE
// Is assembly on TPA list?
SimpleNameToFileNameMap * tpaMap = pApplicationContext->GetTpaList();
const SimpleNameToFileNameMapEntry *pTpaEntry = tpaMap->LookupPtr(simpleName.GetUnicode());
SString fileName;
if (pTpaEntry != nullptr)
{
_ASSERTE(pTpaEntry->m_wszILFileName != nullptr);
SString fileName(pTpaEntry->m_wszILFileName);
if (pTpaEntry->m_wszILFileName != nullptr)
{
fileName.Set(pTpaEntry->m_wszILFileName);
}
else
{
HostInformation::ResolveAssemblyToPath(pTpaEntry->m_wszSimpleName, fileName);
}
}

if (!fileName.IsEmpty())
{
ReleaseHolder<Assembly> pAssembly;
SString getAssemblyDiag;
hr = GetAssembly(fileName,
Expand Down Expand Up @@ -1349,5 +1360,3 @@ BOOL AssemblyBinderCommon::IsValidArchitecture(PEKIND kArchitecture)

#endif // !defined(DACCESS_COMPILE)
};


7 changes: 0 additions & 7 deletions src/coreclr/binder/inc/applicationcontext.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,6 @@ namespace BINDER_SPACE

void OnDestructPerEntryCleanupAction(const SimpleNameToFileNameMapEntry & e)
{
if (e.m_wszILFileName == nullptr)
{
// Don't delete simple name here since it's a filename only entry and will be cleaned up
// by the SimpleName -> FileName entry which reuses the same filename pointer.
return;
}

if (e.m_wszSimpleName != nullptr)
{
delete [] e.m_wszSimpleName;
Expand Down
Loading
Loading