File: ManifestUtil\EmbeddedManifestReader.cs
Web Access
Project: src\msbuild\src\Tasks\Microsoft.Build.Tasks.csproj (Microsoft.Build.Tasks.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.IO;
#if FEATURE_WINDOWSINTEROP
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.LibraryLoader;
#endif

#nullable disable

namespace Microsoft.Build.Tasks.Deployment.ManifestUtilities
{
    internal class EmbeddedManifestReader
    {
#if FEATURE_WINDOWSINTEROP
        private Stream _manifest;

        // The Win32 RT_MANIFEST resource type and the application-manifest resource id (1).
        private const ushort RT_MANIFEST = 24;
        private const nint Id1 = 1;

        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        private delegate BOOL EnumResNameDelegate(HMODULE hModule, PCWSTR lpType, PWSTR lpName, nint lParam);

        private unsafe EmbeddedManifestReader(string path)
        {
            if (!NativeMethodsShared.IsWindows)
            {
                return;
            }

            HMODULE hModule = default;
            try
            {
                hModule = PInvoke.LoadLibraryEx(path, LOAD_LIBRARY_FLAGS.LOAD_LIBRARY_AS_DATAFILE);
                if (hModule.IsNull)
                {
                    return;
                }

                // EnumResourceNames takes an unmanaged stdcall callback. Bridge a managed delegate to a
                // function pointer (works on both .NET Framework and .NET Core) and keep it alive across the call.
                EnumResNameDelegate callback = EnumResNameCallback;
                var pCallback = (delegate* unmanaged[Stdcall]<HMODULE, PCWSTR, PWSTR, nint, BOOL>)Marshal.GetFunctionPointerForDelegate(callback);
                PInvoke.EnumResourceNames(hModule, new PCWSTR((char*)RT_MANIFEST), pCallback, 0);
                GC.KeepAlive(callback);
            }
            finally
            {
                if (!hModule.IsNull)
                {
                    PInvoke.FreeLibrary(hModule);
                }
            }
        }

        [SupportedOSPlatform("windows6.1")]
        private unsafe BOOL EnumResNameCallback(HMODULE hModule, PCWSTR lpType, PWSTR lpName, nint lParam)
        {
            if ((nint)lpName.Value != Id1)
            {
                return false; // only look for resources with ID=1
            }
            HRSRC hResInfo = PInvoke.FindResource(hModule, lpName, (PCWSTR)(char*)RT_MANIFEST);
            if (hResInfo.IsNull)
            {
                return false; // continue looking
            }
            HGLOBAL hResource = PInvoke.LoadResource(hModule, hResInfo);
            void* pData = PInvoke.LockResource(hResource);
            uint bufsize = PInvoke.SizeofResource(hModule, hResInfo);
            var buffer = new byte[bufsize];

            Marshal.Copy((IntPtr)pData, buffer, 0, buffer.Length);
            _manifest = new MemoryStream(buffer, false);
            return false; // found what we are looking for
        }

        public static Stream Read(string path)
        {
            if (path == null)
            {
                throw new ArgumentNullException(nameof(path));
            }

            if (!path.EndsWith(".manifest", StringComparison.Ordinal) && !path.EndsWith(".dll", StringComparison.Ordinal))
            {
                // Everything that does not end with .dll or .manifest is not a valid native assembly (this includes
                //    EXEs with ID1 manifest)
                return null;
            }

            int t1 = Environment.TickCount;
            EmbeddedManifestReader r = new EmbeddedManifestReader(path);
            Util.WriteLog($"EmbeddedManifestReader.Read t={Environment.TickCount - t1}");
            return r._manifest;
        }
#else
        // Reading embedded Win32 manifest resources requires Windows interop, which is unavailable in
        // source build (no CsWin32-generated PInvoke surface), so there is nothing to read.
        public static Stream Read(string path) => null;
#endif
    }
}