Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ The .NET Foundation licenses this file to you under the MIT license.
<Target Name="CreateShimExes"
Condition="'$(_AggregateExecutable)' == 'true'"
DependsOnTargets="LinkNative;WriteAggregateExecutableShimRspFiles"
Inputs="$(NativeImportLibrary);@(_AggregateExecutableReferencePath->'%(ShimSource)');@(_AggregateExecutableReferencePath->'%(ShimCompileRspFile)');@(_AggregateExecutableReferencePath->'%(ShimLinkRspFile)')"
Inputs="$(NativeImportLibrary);@(_AggregateExecutableReferencePath->'%(ShimSource)');@(_AggregateExecutableReferencePath->'%(ShimCompileRspFile)');@(_AggregateExecutableReferencePath->'%(ShimLinkRspFile)');@(_AggregateExecutableReferencePath->'%(ShimResourceFile)')"
Outputs="@(_AggregateExecutableShim);@(_AggregateExecutableShimSymbol)">

<MakeDir Directories="$(NativeIntermediateOutputPath);$(NativeOutputPath)" />
Expand All @@ -159,7 +159,7 @@ The .NET Foundation licenses this file to you under the MIT license.

<Target Name="WriteAggregateExecutableShimRspFiles"
Condition="'$(_AggregateExecutable)' == 'true'"
DependsOnTargets="LinkNative">
DependsOnTargets="LinkNative;GenerateAggregateExecutableShimResFiles">

<ItemGroup>
<_AggregateExecutableReferencePath>
Expand All @@ -171,6 +171,7 @@ int wmain(int argc, unsigned short** argv)
}
]]></ShimSourceCode>
<ShimPdbLinkerArg Condition="'$(NativeDebugSymbols)' == 'true'">/PDB:"%(_AggregateExecutableReferencePath.ShimSymbol)"</ShimPdbLinkerArg>
<ShimResourceLinkerArg>"%(_AggregateExecutableReferencePath.ShimResourceFile)"</ShimResourceLinkerArg>
</_AggregateExecutableReferencePath>
</ItemGroup>

Expand All @@ -195,6 +196,18 @@ int wmain(int argc, unsigned short** argv)
<MakeDir Directories="$(NativeIntermediateOutputPath);$(NativeOutputPath)" />
<WriteLinesToFile File="%(_AggregateExecutableReferencePath.ShimSource)" Lines="$([MSBuild]::Escape('%(_AggregateExecutableReferencePath.ShimSourceCode)'))" Overwrite="true" Encoding="utf-8" WriteOnlyWhenDifferent="true" />
<WriteLinesToFile File="%(_AggregateExecutableReferencePath.ShimCompileRspFile)" Lines="@(_AggregateExecutableShimCompilerArg);&quot;%(_AggregateExecutableReferencePath.ShimSource)&quot;;/Fo&quot;%(_AggregateExecutableReferencePath.ShimObject)&quot;" Overwrite="true" Encoding="utf-8" WriteOnlyWhenDifferent="true" />
<WriteLinesToFile File="%(_AggregateExecutableReferencePath.ShimLinkRspFile)" Lines="&quot;%(_AggregateExecutableReferencePath.ShimObject)&quot;;&quot;$(NativeImportLibrary)&quot;;/OUT:&quot;%(_AggregateExecutableReferencePath.ShimBinary)&quot;;/SUBSYSTEM:%(_AggregateExecutableReferencePath.ShimLinkerSubsystem);%(_AggregateExecutableReferencePath.ShimPdbLinkerArg);@(_AggregateExecutableShimLinkerArg)" Overwrite="true" Encoding="utf-8" WriteOnlyWhenDifferent="true" />
<WriteLinesToFile File="%(_AggregateExecutableReferencePath.ShimLinkRspFile)" Lines="&quot;%(_AggregateExecutableReferencePath.ShimObject)&quot;;&quot;$(NativeImportLibrary)&quot;;%(_AggregateExecutableReferencePath.ShimResourceLinkerArg);/OUT:&quot;%(_AggregateExecutableReferencePath.ShimBinary)&quot;;/SUBSYSTEM:%(_AggregateExecutableReferencePath.ShimLinkerSubsystem);%(_AggregateExecutableReferencePath.ShimPdbLinkerArg);@(_AggregateExecutableShimLinkerArg)" Overwrite="true" Encoding="utf-8" WriteOnlyWhenDifferent="true" />
</Target>

<UsingTask TaskName="DumpNativeResources" AssemblyFile="$(IlcBuildTasksPath)" />

<Target Name="GenerateAggregateExecutableShimResFiles"
Condition="'$(_AggregateExecutable)' == 'true'"
Inputs="@(_AggregateExecutableReferencePath)"
Outputs="@(_AggregateExecutableReferencePath->'%(ShimResourceFile)')">

<DumpNativeResources
MainAssembly="%(_AggregateExecutableReferencePath.Identity)"
ResourceFile="%(_AggregateExecutableReferencePath.ShimResourceFile)" />
</Target>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ The .NET Foundation licenses this file to you under the MIT license.
<ShimObject>$(NativeIntermediateOutputPath)%(AssemblyName).aggregateexe$(NativeObjectExt)</ShimObject>
<ShimCompileRspFile>$(NativeIntermediateOutputPath)%(AssemblyName).aggregateexe.cl.rsp</ShimCompileRspFile>
<ShimLinkRspFile>$(NativeIntermediateOutputPath)%(AssemblyName).aggregateexe.link.rsp</ShimLinkRspFile>
<ShimResourceFile>$(NativeIntermediateOutputPath)%(AssemblyName).aggregateexe.res</ShimResourceFile>
<ShimCompileLinkRspFile>$(NativeIntermediateOutputPath)%(AssemblyName).aggregateexe.rsp</ShimCompileLinkRspFile>
<ShimBinary>$(NativeOutputPath)%(AssemblyName)$(NativeExecutableExt)</ShimBinary>
<ShimSymbol>$(NativeOutputPath)%(AssemblyName)$(NativeSymbolExt)</ShimSymbol>
Expand Down
255 changes: 255 additions & 0 deletions src/coreclr/tools/aot/ILCompiler.Build.Tasks/DumpNativeResources.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Dumps native Win32 resources in the given assembly into a specified *.res file.
/// </summary>
public class DumpNativeResources : Task
{
/// <summary>
/// File name of the assembly with Win32 resources to be dumped.
/// </summary>
[Required]
public string MainAssembly
{
get;
set;
}

/// <summary>
/// File name into which to dump the Win32 resources.
/// </summary>
[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);
}
}
Comment thread
MichalStrehovsky marked this conversation as resolved.
}

return true;
}
}

/// <summary>
/// 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.
/// </summary>
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);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,9 @@

using System;

if (OperatingSystem.IsWindows())
{
Program.ValidateWin32Resources();
}

Console.WriteLine("Hello from HelloExe");
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<CLRTestKind>SharedLibrary</CLRTestKind>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>$(DefineConstants);EXCLUDE_WIN32RESOURCES_MAIN</DefineConstants>
<Win32Resource Condition="'$(TargetsWindows)' == 'true'">..\..\SmokeTests\Win32Resources\test.res</Win32Resource>
</PropertyGroup>
<ItemGroup>
<Compile Include="HelloExe.cs" />
<Compile Include="..\..\SmokeTests\Win32Resources\Win32Resources.cs" Link="Win32Resources.cs" />
</ItemGroup>
</Project>
35 changes: 22 additions & 13 deletions src/tests/nativeaot/SmokeTests/Win32Resources/Win32Resources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<char> 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<char> 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;

Expand All @@ -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);
}
Loading