diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Windows.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Windows.targets index cff4b2e65291e8..a0081669622dfa 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Windows.targets +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Windows.targets @@ -149,7 +149,7 @@ The .NET Foundation licenses this file to you under the MIT license. @@ -159,7 +159,7 @@ The .NET Foundation licenses this file to you under the MIT license. + DependsOnTargets="LinkNative;GenerateAggregateExecutableShimResFiles"> <_AggregateExecutableReferencePath> @@ -171,6 +171,7 @@ int wmain(int argc, unsigned short** argv) } ]]> /PDB:"%(_AggregateExecutableReferencePath.ShimSymbol)" + "%(_AggregateExecutableReferencePath.ShimResourceFile)" @@ -195,6 +196,18 @@ int wmain(int argc, unsigned short** argv) - + + + + + + + + diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets index ce0f7292f4c76d..840ce18de4ba69 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets @@ -192,6 +192,7 @@ The .NET Foundation licenses this file to you under the MIT license. $(NativeIntermediateOutputPath)%(AssemblyName).aggregateexe$(NativeObjectExt) $(NativeIntermediateOutputPath)%(AssemblyName).aggregateexe.cl.rsp $(NativeIntermediateOutputPath)%(AssemblyName).aggregateexe.link.rsp + $(NativeIntermediateOutputPath)%(AssemblyName).aggregateexe.res $(NativeIntermediateOutputPath)%(AssemblyName).aggregateexe.rsp $(NativeOutputPath)%(AssemblyName)$(NativeExecutableExt) $(NativeOutputPath)%(AssemblyName)$(NativeSymbolExt) diff --git a/src/coreclr/tools/aot/ILCompiler.Build.Tasks/DumpNativeResources.cs b/src/coreclr/tools/aot/ILCompiler.Build.Tasks/DumpNativeResources.cs new file mode 100644 index 00000000000000..954e835c51ef74 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Build.Tasks/DumpNativeResources.cs @@ -0,0 +1,255 @@ +// 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.IO; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using System.Text; + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Build.Tasks +{ + /// + /// Dumps native Win32 resources in the given assembly into a specified *.res file. + /// + public class DumpNativeResources : Task + { + /// + /// File name of the assembly with Win32 resources to be dumped. + /// + [Required] + public string MainAssembly + { + get; + set; + } + + /// + /// File name into which to dump the Win32 resources. + /// + [Required] + public string ResourceFile + { + get; + set; + } + + public override bool Execute() + { + using (FileStream fs = File.OpenRead(MainAssembly)) + using (PEReader peFile = new PEReader(fs)) + { + DirectoryEntry resourceDirectory = peFile.PEHeaders.PEHeader.ResourceTableDirectory; + if (resourceDirectory.Size != 0 + && peFile.PEHeaders.TryGetDirectoryOffset(resourceDirectory, out int rsrcOffset)) + { + using (var bw = new BinaryWriter(File.Create(ResourceFile))) + { + ResWriter.WriteResources(peFile, rsrcOffset, resourceDirectory.Size, bw); + } + } + else + { + using (var bw = new BinaryWriter(File.Create(ResourceFile))) + { + ResWriter.WriteEmptyResourceFile(bw); + } + } + } + + return true; + } + } + + /// + /// Helper class that converts from a Win32 PE resource directory format to + /// a set of RESOURCEHEADER structures (https://docs.microsoft.com/en-us/windows/desktop/menurc/resourceheader) + /// that form the basis of Win32 RES files. + /// + internal sealed class ResWriter + { + private readonly PEMemoryBlock _memoryBlock; + private readonly PEReader _peReader; + private readonly int _rsrcOffset; + private readonly int _rsrcSize; + private readonly BinaryWriter _bw; + + private object _typeIdOrName; + private object _resourceIdOrName; + private int _languageId; + + private ResWriter(PEMemoryBlock memoryBlock, PEReader peReader, int rsrcOffset, int rsrcSize, BinaryWriter bw) + { + _memoryBlock = memoryBlock; + _peReader = peReader; + _rsrcOffset = rsrcOffset; + _rsrcSize = rsrcSize; + _bw = bw; + } + + public static void WriteResources(PEReader reader, int rsrcOffset, int rsrcSize, BinaryWriter bw) + { + var rw = new ResWriter(reader.GetEntireImage(), reader, rsrcOffset, rsrcSize, bw); + + WriteEmptyResourceFile(bw); + + rw.DumpDirectory(reader.GetEntireImage().GetReader(rsrcOffset, rsrcSize), 0); + } + + public static void WriteEmptyResourceFile(BinaryWriter bw) + { + // First entry is a null resource entry + bw.Write(new byte[] { + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }); + } + + private void DumpDirectory(BlobReader br, int level) + { + // Skip characteristics + br.ReadInt32(); + + // Skip major/minor version + br.ReadInt32(); + + // Skip time/date stamp + br.ReadInt32(); + + ushort numNamed = br.ReadUInt16(); + ushort numId = br.ReadUInt16(); + + for (int i = 0; i < numNamed + numId; i++) + { + int nameOffsetOrId = br.ReadInt32(); + uint entryTableOrSubdirectoryOffset = br.ReadUInt32(); + + if (i < numNamed) + { + nameOffsetOrId &= 0x7FFFFFFF; + BlobReader nameReader = _memoryBlock.GetReader(_rsrcOffset + nameOffsetOrId, _rsrcSize - nameOffsetOrId); + ushort nameLength = nameReader.ReadUInt16(); + StringBuilder sb = new StringBuilder(nameLength); + for (int charIndex = 0; charIndex < nameLength; charIndex++) + sb.Append((char)nameReader.ReadUInt16()); + string name = sb.ToString(); + + if (level == 0) + { + _typeIdOrName = name; + } + else if (level == 1) + { + _resourceIdOrName = name; + } + else + throw new BadImageFormatException(); + } + else + { + if (level == 0) + { + _typeIdOrName = nameOffsetOrId; + } + else if (level == 1) + { + _resourceIdOrName = nameOffsetOrId; + } + else if (level == 2) + { + _languageId = nameOffsetOrId; + } + else + throw new BadImageFormatException(); + } + + if (level < 2) + { + if ((entryTableOrSubdirectoryOffset & 0x80000000) == 0) + throw new BadImageFormatException(); + entryTableOrSubdirectoryOffset &= 0x7FFFFFFF; + DumpDirectory(_memoryBlock.GetReader(_rsrcOffset + (int)entryTableOrSubdirectoryOffset, _rsrcSize - (int)entryTableOrSubdirectoryOffset), level + 1); + } + else + { + DumpEntry(_memoryBlock.GetReader(_rsrcOffset + (int)entryTableOrSubdirectoryOffset, _rsrcSize - (int)entryTableOrSubdirectoryOffset)); + } + } + } + + private void DumpEntry(BlobReader br) + { + int dataRva = br.ReadInt32(); + int size = br.ReadInt32(); + + // Skip codepage + br.ReadInt32(); + + // Skip reserved + br.ReadInt32(); + + var ms = new MemoryStream(); + var hdr = new BinaryWriter(ms, System.Text.Encoding.Unicode); + + hdr.Write(size); // DataSize + hdr.Write(0); // HeaderSize + hdr.WriteNameOrId(_typeIdOrName); // TYPE + hdr.WriteNameOrId(_resourceIdOrName); // NAME + + // round up to "DWORD" offset + long curHeaderSize = hdr.BaseStream.Position; + while (curHeaderSize % 4 != 0) + { + hdr.Write((byte)0); + curHeaderSize++; + } + + hdr.Write(0); // DataVersion + hdr.Write((short)0); // MemoryFlags + hdr.Write((ushort)_languageId); // LanguageId + hdr.Write(0); // Version + hdr.Write(0); // Characteristics + + // Patch up HeaderSize + var headerSize = hdr.Seek(0, SeekOrigin.Current); + hdr.Seek(4, SeekOrigin.Begin); + hdr.Write((int)headerSize); + + var hdrData = ms.ToArray(); + + _bw.Write(hdrData); + _bw.Write(_peReader.GetSectionData(dataRva).GetReader().ReadBytes(size)); + + // Make sure we are DWORD aligned + var totalSize = hdrData.Length + size; + while (totalSize % 4 != 0) + { + _bw.Write((byte)0); + totalSize++; + } + + } + } + + internal static class ResourceHelper + { + public static void WriteNameOrId(this BinaryWriter bw, object nameOrId) + { + if (nameOrId is string s) + { + // String + bw.Write(s.ToCharArray()); + bw.Write((short)0); + } + else + { + // Integer + bw.Write((short)-1); + bw.Write((short)(int)nameOrId); + } + } + } +} diff --git a/src/tests/nativeaot/AggregateExecutableLibrary/HelloExe/HelloExe.cs b/src/tests/nativeaot/AggregateExecutableLibrary/HelloExe/HelloExe.cs index 9f476b06021221..1f389151291c5a 100644 --- a/src/tests/nativeaot/AggregateExecutableLibrary/HelloExe/HelloExe.cs +++ b/src/tests/nativeaot/AggregateExecutableLibrary/HelloExe/HelloExe.cs @@ -3,4 +3,9 @@ using System; +if (OperatingSystem.IsWindows()) +{ + Program.ValidateWin32Resources(); +} + Console.WriteLine("Hello from HelloExe"); diff --git a/src/tests/nativeaot/AggregateExecutableLibrary/HelloExe/HelloExe.csproj b/src/tests/nativeaot/AggregateExecutableLibrary/HelloExe/HelloExe.csproj index f1bb752fd9c004..24aa9ef2d4b868 100644 --- a/src/tests/nativeaot/AggregateExecutableLibrary/HelloExe/HelloExe.csproj +++ b/src/tests/nativeaot/AggregateExecutableLibrary/HelloExe/HelloExe.csproj @@ -2,8 +2,12 @@ Exe SharedLibrary + true + $(DefineConstants);EXCLUDE_WIN32RESOURCES_MAIN + ..\..\SmokeTests\Win32Resources\test.res + diff --git a/src/tests/nativeaot/SmokeTests/Win32Resources/Win32Resources.cs b/src/tests/nativeaot/SmokeTests/Win32Resources/Win32Resources.cs index 1dfc7a8424792f..e6e9d6a346220f 100644 --- a/src/tests/nativeaot/SmokeTests/Win32Resources/Win32Resources.cs +++ b/src/tests/nativeaot/SmokeTests/Win32Resources/Win32Resources.cs @@ -4,21 +4,30 @@ using System; using System.Runtime.InteropServices; -unsafe +unsafe partial class Program { - nint lib = 0; +#if !EXCLUDE_WIN32RESOURCES_MAIN + private static int Main() + { + ValidateWin32Resources(); + return 100; + } +#endif - if (GetIntValueFromResource(lib, (ushort*)(nuint)(ushort)10, 0x041B) != 3) - throw new Exception(); + public static void ValidateWin32Resources() + { + nint lib = 0; - ReadOnlySpan resName = "funny"; - fixed (char* pResName = resName) - if (GetIntValueFromResource(lib, (ushort*)pResName, 0x041B) != 1) + if (GetIntValueFromResource(lib, (ushort*)(nuint)(ushort)10, 0x041B) != 3) throw new Exception(); - return 100; + ReadOnlySpan resName = "funny"; + fixed (char* pResName = resName) + if (GetIntValueFromResource(lib, (ushort*)pResName, 0x041B) != 1) + throw new Exception(); + } - static int GetIntValueFromResource(nint hModule, ushort* lpName, ushort wLanguage) + private static int GetIntValueFromResource(nint hModule, ushort* lpName, ushort wLanguage) { ushort* RT_RCDATA = (ushort*)(nuint)(ushort)10; @@ -36,14 +45,14 @@ static int GetIntValueFromResource(nint hModule, ushort* lpName, ushort wLanguag } [DllImport("kernel32")] - static extern nint FindResourceExW(nint hModule, ushort* lpType, ushort* lpName, ushort wLanguage); + private static extern nint FindResourceExW(nint hModule, ushort* lpType, ushort* lpName, ushort wLanguage); [DllImport("kernel32")] - static extern nint LoadResource(nint hModule, nint hResInfo); + private static extern nint LoadResource(nint hModule, nint hResInfo); [DllImport("kernel32")] - static extern void* LockResource(nint hResData); + private static extern void* LockResource(nint hResData); [DllImport("kernel32")] - static extern uint SizeofResource(nint hModule, nint hResInfo); + private static extern uint SizeofResource(nint hModule, nint hResInfo); }