csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
181 lines
6.9 KiB
181 lines
6.9 KiB
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using Avalonia.Utilities;
|
|
using Mono.Cecil;
|
|
using Mono.Cecil.Cil;
|
|
using Mono.Collections.Generic;
|
|
using XamlX.TypeSystem;
|
|
|
|
namespace Avalonia.Build.Tasks
|
|
{
|
|
public static partial class XamlCompilerTaskExecutor
|
|
{
|
|
interface IResource : IFileSource
|
|
{
|
|
string Uri { get; }
|
|
string Name { get; }
|
|
void Remove();
|
|
|
|
}
|
|
|
|
interface IResourceGroup
|
|
{
|
|
string Name { get; }
|
|
IEnumerable<IResource> Resources { get; }
|
|
}
|
|
|
|
class EmbeddedResources : IResourceGroup
|
|
{
|
|
private readonly AssemblyDefinition _asm;
|
|
public string Name => "EmbeddedResource";
|
|
|
|
public IEnumerable<IResource> Resources => _asm.MainModule.Resources.OfType<EmbeddedResource>()
|
|
.Select(r => new WrappedResource(_asm, r)).ToList();
|
|
|
|
public EmbeddedResources(AssemblyDefinition asm)
|
|
{
|
|
_asm = asm;
|
|
}
|
|
class WrappedResource : IResource
|
|
{
|
|
private readonly AssemblyDefinition _asm;
|
|
private readonly EmbeddedResource _res;
|
|
|
|
public WrappedResource(AssemblyDefinition asm, EmbeddedResource res)
|
|
{
|
|
_asm = asm;
|
|
_res = res;
|
|
}
|
|
|
|
public string Uri => $"resm:{Name}?assembly={_asm.Name.Name}";
|
|
public string Name => _res.Name;
|
|
public string FilePath => Name;
|
|
public byte[] FileContents => _res.GetResourceData();
|
|
|
|
public void Remove() => _asm.MainModule.Resources.Remove(_res);
|
|
}
|
|
}
|
|
|
|
class AvaloniaResources : IResourceGroup
|
|
{
|
|
private readonly AssemblyDefinition _asm;
|
|
Dictionary<string, AvaloniaResource> _resources = new Dictionary<string, AvaloniaResource>();
|
|
private EmbeddedResource _embedded;
|
|
public AvaloniaResources(AssemblyDefinition asm, string projectDir)
|
|
{
|
|
_asm = asm;
|
|
_embedded = ((EmbeddedResource)asm.MainModule.Resources.FirstOrDefault(r =>
|
|
r.ResourceType == ResourceType.Embedded && r.Name == "!AvaloniaResources"));
|
|
if (_embedded == null)
|
|
return;
|
|
using (var stream = _embedded.GetResourceStream())
|
|
{
|
|
var br = new BinaryReader(stream);
|
|
var index = AvaloniaResourcesIndexReaderWriter.Read(new MemoryStream(br.ReadBytes(br.ReadInt32())));
|
|
var baseOffset = stream.Position;
|
|
foreach (var e in index)
|
|
{
|
|
stream.Position = e.Offset + baseOffset;
|
|
_resources[e.Path] = new AvaloniaResource(this, projectDir, e.Path, br.ReadBytes(e.Size));
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Save()
|
|
{
|
|
if (_embedded != null)
|
|
{
|
|
_asm.MainModule.Resources.Remove(_embedded);
|
|
_embedded = null;
|
|
}
|
|
|
|
if (_resources.Count == 0)
|
|
return;
|
|
|
|
_embedded = new EmbeddedResource("!AvaloniaResources", ManifestResourceAttributes.Public,
|
|
AvaloniaResourcesIndexReaderWriter.Create(_resources.ToDictionary(x => x.Key,
|
|
x => x.Value.FileContents)));
|
|
_asm.MainModule.Resources.Add(_embedded);
|
|
}
|
|
|
|
public string Name => "AvaloniaResources";
|
|
public IEnumerable<IResource> Resources => _resources.Values.ToList();
|
|
|
|
class AvaloniaResource : IResource
|
|
{
|
|
private readonly AvaloniaResources _grp;
|
|
private readonly byte[] _data;
|
|
|
|
public AvaloniaResource(AvaloniaResources grp,
|
|
string projectDir,
|
|
string name, byte[] data)
|
|
{
|
|
_grp = grp;
|
|
_data = data;
|
|
Name = name;
|
|
FilePath = Path.Combine(projectDir, name.TrimStart('/'));
|
|
Uri = $"avares://{grp._asm.Name.Name}/{name.TrimStart('/')}";
|
|
}
|
|
public string Uri { get; }
|
|
public string Name { get; }
|
|
public string FilePath { get; }
|
|
public byte[] FileContents => _data;
|
|
|
|
public void Remove() => _grp._resources.Remove(Name);
|
|
}
|
|
}
|
|
|
|
static void CopyDebugDocument(MethodDefinition method, MethodDefinition copyFrom)
|
|
{
|
|
if (!copyFrom.DebugInformation.HasSequencePoints)
|
|
return;
|
|
var dbg = method.DebugInformation;
|
|
|
|
dbg.Scope = new ScopeDebugInformation(method.Body.Instructions.First(), method.Body.Instructions.First())
|
|
{
|
|
End = new InstructionOffset(),
|
|
Import = new ImportDebugInformation()
|
|
};
|
|
dbg.SequencePoints.Add(new SequencePoint(method.Body.Instructions.First(),
|
|
copyFrom.DebugInformation.SequencePoints.First().Document)
|
|
{
|
|
StartLine = 0xfeefee,
|
|
EndLine = 0xfeefee
|
|
});
|
|
|
|
}
|
|
|
|
|
|
private static bool MatchThisCall(Collection<Instruction> instructions, int idx)
|
|
{
|
|
var i = instructions[idx];
|
|
// A "normal" way of passing `this` to a static method:
|
|
|
|
// ldarg.0
|
|
// call void [Avalonia.Markup.Xaml]Avalonia.Markup.Xaml.AvaloniaXamlLoader::Load(object)
|
|
|
|
if (i.OpCode == OpCodes.Ldarg_0 || (i.OpCode == OpCodes.Ldarg && i.Operand?.Equals(0) == true))
|
|
return true;
|
|
|
|
/* F# way of using `this` in constructor emits a monstrosity like this:
|
|
IL_01c7: ldarg.0
|
|
IL_01c8: ldfld class [FSharp.Core]Microsoft.FSharp.Core.FSharpRef`1<class FVim.Cursor> FVim.Cursor::this
|
|
IL_01cd: call instance !0 class [FSharp.Core]Microsoft.FSharp.Core.FSharpRef`1<class FVim.Cursor>::get_contents()
|
|
IL_01d2: call !!0 [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives/IntrinsicFunctions::CheckThis<class FVim.Cursor>(!!0)
|
|
IL_01d7: call void [Avalonia.Markup.Xaml]Avalonia.Markup.Xaml.AvaloniaXamlLoader::Load(object)
|
|
|
|
We check for the previous call to be Microsoft.FSharp.Core.LanguagePrimitives/IntrinsicFunctions::CheckThis
|
|
since it actually returns `this`
|
|
*/
|
|
if (i.OpCode == OpCodes.Call
|
|
&& i.Operand is GenericInstanceMethod gim
|
|
&& gim.Name == "CheckThis"
|
|
&& gim.DeclaringType.FullName == "Microsoft.FSharp.Core.LanguagePrimitives/IntrinsicFunctions")
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
}
|
|
|