| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Reflection;
- using System.Runtime.CompilerServices;
- using System.Runtime.InteropServices;
- using System.Threading;
- using Avalonia.Platform;
- using Avalonia.Platform.Interop;
- using Avalonia.Utilities;
- namespace Avalonia.Shared.PlatformSupport
- {
- static class StandardRuntimePlatformServices
- {
- public static void Register(Assembly assembly = null)
- {
- var standardPlatform = new StandardRuntimePlatform();
- AssetLoader.RegisterResUriParsers();
- AvaloniaLocator.CurrentMutable
- .Bind<IRuntimePlatform>().ToConstant(standardPlatform)
- .Bind<IAssetLoader>().ToConstant(new AssetLoader(assembly))
- .Bind<IDynamicLibraryLoader>().ToConstant(
- #if __IOS__
- new IOSLoader()
- #else
- RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
- ? (IDynamicLibraryLoader)new Win32Loader()
- : new UnixLoader()
- #endif
- );
- }
- }
-
-
- internal partial class StandardRuntimePlatform : IRuntimePlatform
- {
- public IDisposable StartSystemTimer(TimeSpan interval, Action tick)
- {
- return new Timer(_ => tick(), null, interval, interval);
- }
- public IUnmanagedBlob AllocBlob(int size) => new UnmanagedBlob(this, size);
-
- class UnmanagedBlob : IUnmanagedBlob
- {
- private readonly StandardRuntimePlatform _plat;
- private IntPtr _address;
- private readonly object _lock = new object();
- #if DEBUG
- private static readonly List<string> Backtraces = new List<string>();
- private static Thread GCThread;
- private readonly string _backtrace;
- private static readonly object _btlock = new object();
- class GCThreadDetector
- {
- ~GCThreadDetector()
- {
- GCThread = Thread.CurrentThread;
- }
- }
- [MethodImpl(MethodImplOptions.NoInlining)]
- static void Spawn() => new GCThreadDetector();
-
- static UnmanagedBlob()
- {
- Spawn();
- GC.WaitForPendingFinalizers();
- }
-
- #endif
-
- public UnmanagedBlob(StandardRuntimePlatform plat, int size)
- {
- if (size <= 0)
- throw new ArgumentException("Positive number required", nameof(size));
- _plat = plat;
- _address = plat.Alloc(size);
- GC.AddMemoryPressure(size);
- Size = size;
- #if DEBUG
- _backtrace = Environment.StackTrace;
- lock (_btlock)
- Backtraces.Add(_backtrace);
- #endif
- }
- void DoDispose()
- {
- lock (_lock)
- {
- if (!IsDisposed)
- {
- #if DEBUG
- lock (_btlock)
- Backtraces.Remove(_backtrace);
- #endif
- _plat?.Free(_address, Size);
- GC.RemoveMemoryPressure(Size);
- IsDisposed = true;
- _address = IntPtr.Zero;
- Size = 0;
- }
- }
- }
- public void Dispose()
- {
- #if DEBUG
- if (Thread.CurrentThread.ManagedThreadId == GCThread?.ManagedThreadId)
- {
- lock (_lock)
- {
- if (!IsDisposed)
- {
- Console.Error.WriteLine("Native blob disposal from finalizer thread\nBacktrace: "
- + Environment.StackTrace
- + "\n\nBlob created by " + _backtrace);
- }
- }
- }
- #endif
- DoDispose();
- GC.SuppressFinalize(this);
- }
- ~UnmanagedBlob()
- {
- #if DEBUG
- Console.Error.WriteLine("Undisposed native blob created by " + _backtrace);
- #endif
- DoDispose();
- }
- public IntPtr Address => IsDisposed ? throw new ObjectDisposedException("UnmanagedBlob") : _address;
- public int Size { get; private set; }
- public bool IsDisposed { get; private set; }
- }
-
-
-
- #if NET462 || NETCOREAPP2_0
- [DllImport("libc", SetLastError = true)]
- private static extern IntPtr mmap(IntPtr addr, IntPtr length, int prot, int flags, int fd, IntPtr offset);
- [DllImport("libc", SetLastError = true)]
- private static extern int munmap(IntPtr addr, IntPtr length);
- [DllImport("libc", SetLastError = true)]
- private static extern long sysconf(int name);
- private bool? _useMmap;
- private bool UseMmap
- => _useMmap ?? ((_useMmap = GetRuntimeInfo().OperatingSystem == OperatingSystemType.Linux)).Value;
-
- IntPtr Alloc(int size)
- {
- if (UseMmap)
- {
- var rv = mmap(IntPtr.Zero, new IntPtr(size), 3, 0x22, -1, IntPtr.Zero);
- if (rv.ToInt64() == -1 || (ulong) rv.ToInt64() == 0xffffffff)
- {
- var errno = Marshal.GetLastWin32Error();
- throw new Exception("Unable to allocate memory: " + errno);
- }
- return rv;
- }
- else
- return Marshal.AllocHGlobal(size);
- }
- void Free(IntPtr ptr, int len)
- {
- if (UseMmap)
- {
- if (munmap(ptr, new IntPtr(len)) == -1)
- {
- var errno = Marshal.GetLastWin32Error();
- throw new Exception("Unable to free memory: " + errno);
- }
- }
- else
- Marshal.FreeHGlobal(ptr);
- }
- #else
- IntPtr Alloc(int size) => Marshal.AllocHGlobal(size);
- void Free(IntPtr ptr, int len) => Marshal.FreeHGlobal(ptr);
- #endif
-
- }
-
- internal class IOSLoader : IDynamicLibraryLoader
- {
- IntPtr IDynamicLibraryLoader.LoadLibrary(string dll)
- {
- throw new PlatformNotSupportedException();
- }
- IntPtr IDynamicLibraryLoader.GetProcAddress(IntPtr dll, string proc, bool optional)
- {
- throw new PlatformNotSupportedException();
- }
- }
-
- public class AssetLoader : IAssetLoader
- {
- private const string AvaloniaResourceName = "!AvaloniaResources";
- private static readonly Dictionary<string, AssemblyDescriptor> AssemblyNameCache
- = new Dictionary<string, AssemblyDescriptor>();
- private AssemblyDescriptor _defaultResmAssembly;
- /// <summary>
- /// Initializes a new instance of the <see cref="AssetLoader"/> class.
- /// </summary>
- /// <param name="assembly">
- /// The default assembly from which to load resm: assets for which no assembly is specified.
- /// </param>
- public AssetLoader(Assembly assembly = null)
- {
- if (assembly == null)
- assembly = Assembly.GetEntryAssembly();
- if (assembly != null)
- _defaultResmAssembly = new AssemblyDescriptor(assembly);
- }
- /// <summary>
- /// Sets the default assembly from which to load assets for which no assembly is specified.
- /// </summary>
- /// <param name="assembly">The default assembly.</param>
- public void SetDefaultAssembly(Assembly assembly)
- {
- _defaultResmAssembly = new AssemblyDescriptor(assembly);
- }
- /// <summary>
- /// Checks if an asset with the specified URI exists.
- /// </summary>
- /// <param name="uri">The URI.</param>
- /// <param name="baseUri">
- /// A base URI to use if <paramref name="uri"/> is relative.
- /// </param>
- /// <returns>True if the asset could be found; otherwise false.</returns>
- public bool Exists(Uri uri, Uri baseUri = null)
- {
- return GetAsset(uri, baseUri) != null;
- }
- /// <summary>
- /// Opens the asset with the requested URI.
- /// </summary>
- /// <param name="uri">The URI.</param>
- /// <param name="baseUri">
- /// A base URI to use if <paramref name="uri"/> is relative.
- /// </param>
- /// <returns>A stream containing the asset contents.</returns>
- /// <exception cref="FileNotFoundException">
- /// The asset could not be found.
- /// </exception>
- public Stream Open(Uri uri, Uri baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1;
- /// <summary>
- /// Opens the asset with the requested URI and returns the asset stream and the
- /// assembly containing the asset.
- /// </summary>
- /// <param name="uri">The URI.</param>
- /// <param name="baseUri">
- /// A base URI to use if <paramref name="uri"/> is relative.
- /// </param>
- /// <returns>
- /// The stream containing the resource contents together with the assembly.
- /// </returns>
- /// <exception cref="FileNotFoundException">
- /// The asset could not be found.
- /// </exception>
- public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri baseUri = null)
- {
- var asset = GetAsset(uri, baseUri);
- if (asset == null)
- {
- throw new FileNotFoundException($"The resource {uri} could not be found.");
- }
- return (asset.GetStream(), asset.Assembly);
- }
- public Assembly GetAssembly(Uri uri, Uri baseUri)
- {
- if (!uri.IsAbsoluteUri && baseUri != null)
- uri = new Uri(baseUri, uri);
- return GetAssembly(uri).Assembly;
- }
- /// <summary>
- /// Gets all assets of a folder and subfolders that match specified uri.
- /// </summary>
- /// <param name="uri">The URI.</param>
- /// <param name="baseUri">Base URI that is used if <paramref name="uri"/> is relative.</param>
- /// <returns>All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset</returns>
- public IEnumerable<Uri> GetAssets(Uri uri, Uri baseUri)
- {
- if (uri.IsAbsoluteUri && uri.Scheme == "resm")
- {
- var assembly = GetAssembly(uri);
- return assembly?.Resources.Where(x => x.Key.Contains(uri.AbsolutePath))
- .Select(x =>new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ??
- Enumerable.Empty<Uri>();
- }
- uri = EnsureAbsolute(uri, baseUri);
- if (uri.Scheme == "avares")
- {
- var (asm, path) = GetResAsmAndPath(uri);
- if (asm == null)
- {
- throw new ArgumentException(
- "No default assembly, entry assembly or explicit assembly specified; " +
- "don't know where to look up for the resource, try specifying assembly explicitly.");
- }
- if (asm?.AvaloniaResources == null)
- return Enumerable.Empty<Uri>();
- path = path.TrimEnd('/') + '/';
- return asm.AvaloniaResources.Where(r => r.Key.StartsWith(path))
- .Select(x => new Uri($"avares://{asm.Name}{x.Key}"));
- }
- return Enumerable.Empty<Uri>();
- }
- private Uri EnsureAbsolute(Uri uri, Uri baseUri)
- {
- if (uri.IsAbsoluteUri)
- return uri;
- if(baseUri == null)
- throw new ArgumentException($"Relative uri {uri} without base url");
- if (!baseUri.IsAbsoluteUri)
- throw new ArgumentException($"Base uri {baseUri} is relative");
- if (baseUri.Scheme == "resm")
- throw new ArgumentException(
- $"Relative uris for 'resm' scheme aren't supported; {baseUri} uses resm");
- return new Uri(baseUri, uri);
- }
-
- private IAssetDescriptor GetAsset(Uri uri, Uri baseUri)
- {
- if (uri.IsAbsoluteUri && uri.Scheme == "resm")
- {
- var asm = GetAssembly(uri) ?? GetAssembly(baseUri) ?? _defaultResmAssembly;
- if (asm == null)
- {
- throw new ArgumentException(
- "No default assembly, entry assembly or explicit assembly specified; " +
- "don't know where to look up for the resource, try specifying assembly explicitly.");
- }
- IAssetDescriptor rv;
- var resourceKey = uri.AbsolutePath;
- asm.Resources.TryGetValue(resourceKey, out rv);
- return rv;
- }
- uri = EnsureAbsolute(uri, baseUri);
- if (uri.Scheme == "avares")
- {
- var (asm, path) = GetResAsmAndPath(uri);
- if (asm.AvaloniaResources == null)
- return null;
- asm.AvaloniaResources.TryGetValue(path, out var desc);
- return desc;
- }
- throw new ArgumentException($"Unsupported url type: " + uri.Scheme, nameof(uri));
- }
- private (AssemblyDescriptor asm, string path) GetResAsmAndPath(Uri uri)
- {
- var asm = GetAssembly(uri.Authority);
- return (asm, uri.AbsolutePath);
- }
-
- private AssemblyDescriptor GetAssembly(Uri uri)
- {
- if (uri != null)
- {
- if (!uri.IsAbsoluteUri)
- return null;
- if (uri.Scheme == "avares")
- return GetResAsmAndPath(uri).asm;
- if (uri.Scheme == "resm")
- {
- var qs = ParseQueryString(uri);
- string assemblyName;
- if (qs.TryGetValue("assembly", out assemblyName))
- {
- return GetAssembly(assemblyName);
- }
- }
- }
- return null;
- }
- private AssemblyDescriptor GetAssembly(string name)
- {
- if (name == null)
- throw new ArgumentNullException(nameof(name));
- AssemblyDescriptor rv;
- if (!AssemblyNameCache.TryGetValue(name, out rv))
- {
- var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
- var match = loadedAssemblies.FirstOrDefault(a => a.GetName().Name == name);
- if (match != null)
- {
- AssemblyNameCache[name] = rv = new AssemblyDescriptor(match);
- }
- else
- {
- // iOS does not support loading assemblies dynamically!
- //
- #if __IOS__
- throw new InvalidOperationException(
- $"Assembly {name} needs to be referenced and explicitly loaded before loading resources");
- #else
- name = Uri.UnescapeDataString(name);
- AssemblyNameCache[name] = rv = new AssemblyDescriptor(Assembly.Load(name));
- #endif
- }
- }
- return rv;
- }
- private Dictionary<string, string> ParseQueryString(Uri uri)
- {
- return uri.Query.TrimStart('?')
- .Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)
- .Select(p => p.Split('='))
- .ToDictionary(p => p[0], p => p[1]);
- }
- private interface IAssetDescriptor
- {
- Stream GetStream();
- Assembly Assembly { get; }
- }
- private class AssemblyResourceDescriptor : IAssetDescriptor
- {
- private readonly Assembly _asm;
- private readonly string _name;
- public AssemblyResourceDescriptor(Assembly asm, string name)
- {
- _asm = asm;
- _name = name;
- }
- public Stream GetStream()
- {
- return _asm.GetManifestResourceStream(_name);
- }
- public Assembly Assembly => _asm;
- }
-
- private class AvaloniaResourceDescriptor : IAssetDescriptor
- {
- private readonly int _offset;
- private readonly int _length;
- public Assembly Assembly { get; }
- public AvaloniaResourceDescriptor(Assembly asm, int offset, int length)
- {
- _offset = offset;
- _length = length;
- Assembly = asm;
- }
-
- public Stream GetStream()
- {
- return new SlicedStream(Assembly.GetManifestResourceStream(AvaloniaResourceName), _offset, _length);
- }
- }
-
- class SlicedStream : Stream
- {
- private readonly Stream _baseStream;
- private readonly int _from;
- public SlicedStream(Stream baseStream, int from, int length)
- {
- Length = length;
- _baseStream = baseStream;
- _from = from;
- _baseStream.Position = from;
- }
- public override void Flush()
- {
- }
- public override int Read(byte[] buffer, int offset, int count)
- {
- return _baseStream.Read(buffer, offset, (int)Math.Min(count, Length - Position));
- }
- public override long Seek(long offset, SeekOrigin origin)
- {
- if (origin == SeekOrigin.Begin)
- Position = offset;
- if (origin == SeekOrigin.End)
- Position = _from + Length + offset;
- if (origin == SeekOrigin.Current)
- Position = Position + offset;
- return Position;
- }
- public override void SetLength(long value) => throw new NotSupportedException();
- public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
- public override bool CanRead => true;
- public override bool CanSeek => _baseStream.CanRead;
- public override bool CanWrite => false;
- public override long Length { get; }
- public override long Position
- {
- get => _baseStream.Position - _from;
- set => _baseStream.Position = value + _from;
- }
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- _baseStream.Dispose();
- }
- public override void Close() => _baseStream.Close();
- }
- private class AssemblyDescriptor
- {
- public AssemblyDescriptor(Assembly assembly)
- {
- Assembly = assembly;
- if (assembly != null)
- {
- Resources = assembly.GetManifestResourceNames()
- .ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n));
- Name = assembly.GetName().Name;
- using (var resources = assembly.GetManifestResourceStream(AvaloniaResourceName))
- {
- if (resources != null)
- {
- Resources.Remove(AvaloniaResourceName);
- var indexLength = new BinaryReader(resources).ReadInt32();
- var index = AvaloniaResourcesIndexReaderWriter.Read(new SlicedStream(resources, 4, indexLength));
- var baseOffset = indexLength + 4;
- AvaloniaResources = index.ToDictionary(r => "/" + r.Path.TrimStart('/'), r => (IAssetDescriptor)
- new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size));
- }
- }
- }
- }
- public Assembly Assembly { get; }
- public Dictionary<string, IAssetDescriptor> Resources { get; }
- public Dictionary<string, IAssetDescriptor> AvaloniaResources { get; }
- public string Name { get; }
- }
-
- public static void RegisterResUriParsers()
- {
- if (!UriParser.IsKnownScheme("avares"))
- UriParser.Register(new GenericUriParser(
- GenericUriParserOptions.GenericAuthority |
- GenericUriParserOptions.NoUserInfo |
- GenericUriParserOptions.NoPort |
- GenericUriParserOptions.NoQuery |
- GenericUriParserOptions.NoFragment), "avares", -1);
- }
- }
- }
|