PluginLoadContext.cs 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Reflection;
  5. using System.Runtime.Loader;
  6. namespace GodotPlugins
  7. {
  8. public class PluginLoadContext : AssemblyLoadContext
  9. {
  10. private readonly AssemblyDependencyResolver _resolver;
  11. private readonly ICollection<string> _sharedAssemblies;
  12. private readonly AssemblyLoadContext _mainLoadContext;
  13. public string? AssemblyLoadedPath { get; private set; }
  14. public PluginLoadContext(string pluginPath, ICollection<string> sharedAssemblies,
  15. AssemblyLoadContext mainLoadContext, bool isCollectible)
  16. : base(isCollectible)
  17. {
  18. _resolver = new AssemblyDependencyResolver(pluginPath);
  19. _sharedAssemblies = sharedAssemblies;
  20. _mainLoadContext = mainLoadContext;
  21. if (string.IsNullOrEmpty(AppContext.BaseDirectory))
  22. {
  23. // See https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/AppContext.AnyOS.cs#L17-L35
  24. // but Assembly.Location is unavailable, because we load assemblies from memory.
  25. string? baseDirectory = Path.GetDirectoryName(pluginPath);
  26. if (baseDirectory != null)
  27. {
  28. if (!Path.EndsInDirectorySeparator(baseDirectory))
  29. baseDirectory += Path.DirectorySeparatorChar;
  30. // This SetData call effectively sets AppContext.BaseDirectory
  31. // See https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/AppContext.cs#L21-L25
  32. AppDomain.CurrentDomain.SetData("APP_CONTEXT_BASE_DIRECTORY", baseDirectory);
  33. }
  34. else
  35. {
  36. // TODO: How to log from GodotPlugins? (delegate pointer?)
  37. Console.Error.WriteLine("Failed to set AppContext.BaseDirectory. Dynamic loading of libraries may fail.");
  38. }
  39. }
  40. }
  41. protected override Assembly? Load(AssemblyName assemblyName)
  42. {
  43. if (assemblyName.Name == null)
  44. return null;
  45. if (_sharedAssemblies.Contains(assemblyName.Name))
  46. return _mainLoadContext.LoadFromAssemblyName(assemblyName);
  47. string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
  48. if (assemblyPath != null)
  49. {
  50. AssemblyLoadedPath = assemblyPath;
  51. // Load in memory to prevent locking the file
  52. using var assemblyFile = File.Open(assemblyPath, FileMode.Open, FileAccess.Read, FileShare.Read);
  53. string pdbPath = Path.ChangeExtension(assemblyPath, ".pdb");
  54. if (File.Exists(pdbPath))
  55. {
  56. using var pdbFile = File.Open(pdbPath, FileMode.Open, FileAccess.Read, FileShare.Read);
  57. return LoadFromStream(assemblyFile, pdbFile);
  58. }
  59. return LoadFromStream(assemblyFile);
  60. }
  61. return null;
  62. }
  63. protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
  64. {
  65. string? libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
  66. if (libraryPath != null)
  67. return LoadUnmanagedDllFromPath(libraryPath);
  68. return IntPtr.Zero;
  69. }
  70. }
  71. }