| File: Instance\RunningObjectTable.cs | Web Access |
| Project: src\msbuild\src\Build\Microsoft.Build.csproj (Microsoft.Build) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; #if FEATURE_WINDOWSINTEROP using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.System.Com; #endif #nullable disable namespace Microsoft.Build.Execution { /// <summary> /// Wrapper for the COM Running Object Table. /// </summary> /// <remarks> /// See https://docs.microsoft.com/en-us/windows/desktop/api/objidl/nn-objidl-irunningobjecttable. /// </remarks> internal class RunningObjectTable : IRunningObjectTableWrapper { #if FEATURE_WINDOWSINTEROP // Cached pointer to the IRunningObjectTable obtained on an MTA thread. Stored as nint // because ComScope<T> is a ref struct and cannot be a field. Released by the finalizer. private readonly Task<nint> _rotTask; #endif public RunningObjectTable() { #if FEATURE_WINDOWSINTEROP if (!NativeMethodsShared.IsWindows) { return; } if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) { _rotTask = Task.FromResult(GetRunningObjectTablePointer()); } else { // To avoid deadlock, create ROT on a threadpool thread which guarantees MTA. _rotTask = Task.Run(GetRunningObjectTablePointer); } #endif } #if FEATURE_WINDOWSINTEROP ~RunningObjectTable() { if (_rotTask is not null && _rotTask.Status == TaskStatus.RanToCompletion) { ReleasePointer(_rotTask.Result); } } [SupportedOSPlatform("windows5.0")] private static unsafe nint GetRunningObjectTablePointer() { IRunningObjectTable* rot; HRESULT hr = PInvoke.GetRunningObjectTable(0, &rot); return hr.Succeeded ? (nint)rot : 0; } private static unsafe void ReleasePointer(nint p) { if (p != 0) { ((IUnknown*)p)->Release(); } } #endif /// <summary> /// Attempts to retrieve an item from the ROT. /// </summary> #if FEATURE_WINDOWSINTEROP [SupportedOSPlatform("windows5.0")] #endif public object GetObject(string itemName) { #if FEATURE_WINDOWSINTEROP return GetObjectWindows(itemName); #else throw new PlatformNotSupportedException(); #endif } #if FEATURE_WINDOWSINTEROP [SupportedOSPlatform("windows5.0")] private unsafe object GetObjectWindows(string itemName) { nint rotPtr = _rotTask.GetAwaiter().GetResult(); if (rotPtr == 0) { throw new COMException("Failed to obtain the Running Object Table."); } // The IRunningObjectTable pointer was obtained on an MTA thread; calling through // it from an STA caller would bypass COM apartment marshalling. Run all ROT calls // on an MTA thread so the moniker creation and GetObject invocation happen in the // same apartment as the pointer's owner. if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) { return GetObjectCore(rotPtr, itemName); } return Task.Run(() => GetObjectCore(rotPtr, itemName)).GetAwaiter().GetResult(); } [SupportedOSPlatform("windows5.0")] private static unsafe object GetObjectCore(nint rotPtr, string itemName) { IRunningObjectTable* rot = (IRunningObjectTable*)rotPtr; IMoniker* monikerRaw; HRESULT hr = PInvoke.CreateItemMoniker("!", itemName, &monikerRaw); if (hr.Failed) { throw new COMException("CreateItemMoniker failed", hr); } using ComScope<IMoniker> moniker = new(monikerRaw); IUnknown* pUnk = null; hr = rot->GetObject(moniker.Pointer, &pUnk); if (hr.Failed) { ThrowComExceptionWithDetails(hr, itemName); } try { return Marshal.GetObjectForIUnknown((IntPtr)pUnk); } finally { if (pUnk is not null) { pUnk->Release(); } } } /// <summary> /// Throws an exception with detailed COM error information if available. /// </summary> [SupportedOSPlatform("windows5.0")] private static unsafe void ThrowComExceptionWithDetails(int hr, string itemName) { StringBuilder errorMessage = new(); errorMessage.AppendLine($"Failed to get object '{itemName}' from Running Object Table."); errorMessage.AppendLine($"HRESULT: 0x{hr:X8} ({hr})"); IErrorInfo* pErrorInfo = null; HRESULT errInfoHr = PInvoke.GetErrorInfo(0, &pErrorInfo); if (errInfoHr == HRESULT.S_OK && pErrorInfo is not null) { BSTR description = default; BSTR source = default; BSTR helpFile = default; uint helpContext = 0; try { pErrorInfo->GetDescription(&description); pErrorInfo->GetSource(&source); pErrorInfo->GetHelpFile(&helpFile); pErrorInfo->GetHelpContext(out helpContext); AppendIfNotEmpty("description", description.ToString()); AppendIfNotEmpty("source", source.ToString()); AppendIfNotEmpty("help file", helpFile.ToString()); if (helpContext != 0) { errorMessage.AppendLine($"help context: {helpContext}"); } } catch { // If we can't get error info details, just continue with the basic code. } finally { if (description.Value is not null) { PInvoke.SysFreeString(description); } if (source.Value is not null) { PInvoke.SysFreeString(source); } if (helpFile.Value is not null) { PInvoke.SysFreeString(helpFile); } pErrorInfo->Release(); } } throw new COMException(errorMessage.ToString(), hr); void AppendIfNotEmpty(string label, string value) { if (!string.IsNullOrEmpty(value)) { errorMessage.AppendLine($"{label}: {value}"); } } } #endif } }