Bläddra i källkod

Dynamically module loader, PoC

0x0001F36D 5 år sedan
förälder
incheckning
1c4babd2af

+ 7 - 10
Yuuna.sln

@@ -10,12 +10,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yuuna", "src\Yuuna\Yuuna.cs
 		{FCA784A1-546A-47E1-B5C1-3CF6A44FB3A7} = {FCA784A1-546A-47E1-B5C1-3CF6A44FB3A7}
 	EndProjectSection
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yuuna.Common", "src\Yuuna.Common\Yuuna.Common.csproj", "{FCA784A1-546A-47E1-B5C1-3CF6A44FB3A7}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yuuna.Faker", "src\Yuuna.Faker\Yuuna.Faker.csproj", "{B38575D1-8904-4358-921A-11F3ED6FEAA8}"
-	ProjectSection(ProjectDependencies) = postProject
-		{FCA784A1-546A-47E1-B5C1-3CF6A44FB3A7} = {FCA784A1-546A-47E1-B5C1-3CF6A44FB3A7}
-	EndProjectSection
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yuuna.Common", "src\Yuuna.Common\Yuuna.Common.csproj", "{FCA784A1-546A-47E1-B5C1-3CF6A44FB3A7}"
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7AD85507-85E8-46E4-9A36-A9DFD2FC3513}"
 	ProjectSection(SolutionItems) = preProject
@@ -23,6 +18,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
 		.gitignore = .gitignore
 	EndProjectSection
 EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yuuna.Faker", "src\Yuuna.Faker\Yuuna.Faker.csproj", "{4F6BFA3D-FC78-4087-BC09-E6E4D3F487EE}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -41,10 +38,10 @@ Global
 		{FCA784A1-546A-47E1-B5C1-3CF6A44FB3A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{FCA784A1-546A-47E1-B5C1-3CF6A44FB3A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{FCA784A1-546A-47E1-B5C1-3CF6A44FB3A7}.Release|Any CPU.Build.0 = Release|Any CPU
-		{B38575D1-8904-4358-921A-11F3ED6FEAA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{B38575D1-8904-4358-921A-11F3ED6FEAA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{B38575D1-8904-4358-921A-11F3ED6FEAA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{B38575D1-8904-4358-921A-11F3ED6FEAA8}.Release|Any CPU.Build.0 = Release|Any CPU
+		{4F6BFA3D-FC78-4087-BC09-E6E4D3F487EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{4F6BFA3D-FC78-4087-BC09-E6E4D3F487EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{4F6BFA3D-FC78-4087-BC09-E6E4D3F487EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{4F6BFA3D-FC78-4087-BC09-E6E4D3F487EE}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 79 - 14
src/Yuuna.Contracts/Modules/ModuleBase.cs

@@ -5,14 +5,38 @@ namespace Yuuna.Contracts.Modules
 {
     using System;
     using System.Collections.Immutable;
-
+    using System.Reflection;
+    using System.Runtime.Loader;
     using Yuuna.Contracts.Optimization;
     using Yuuna.Contracts.Patterns;
     using Yuuna.Contracts.Semantics;
     using Yuuna.Contracts.TextSegmention;
-    
-    public abstract class ModuleBase
+
+    /// <summary>
+    /// 模組狀態。
+    /// </summary>
+    public enum ModuleStatus
+    {
+        /// <summary>
+        /// 未初始化。
+        /// </summary>
+        Uninitialized,
+        /// <summary>
+        /// 初始化失敗。
+        /// </summary>
+        FailToInitialize,
+        /// <summary>
+        /// 初始化完成。
+        /// </summary>
+        Initialized,
+    }
+
+    public abstract class ModuleBase 
     {
+        private const BindingFlags FLAGS = (BindingFlags)52;
+
+        protected virtual string ModuleName { get; }
+
         private readonly PatternFactory _patternfactory;
         
         internal IPatternSet Patterns => this._patternfactory;
@@ -20,23 +44,64 @@ namespace Yuuna.Contracts.Modules
         public ModuleBase()
         {
             this._patternfactory = new PatternFactory(this);
+            this.ModuleName = this.GetType().Name;
+            this.Status = ModuleStatus.Uninitialized;
         }
 
-      
-
-        internal bool Initialize(ITextSegmenter textSegmenter, IGroupManager groupManager)
+        /// <summary>
+        /// 模組名稱。
+        /// </summary>
+        public string Name
         {
-            try
+            get
             {
-                this.BuildPatterns(groupManager, this._patternfactory);
-                textSegmenter.Load(groupManager);
-                this.AfterInitialize();
-                return true;
+                var t = this.GetType();
+                var getMethod = t.GetProperty(nameof(this.ModuleName), FLAGS).GetGetMethod(true);
+                if (!getMethod.GetBaseDefinition().Equals(getMethod))
+                {
+                    try
+                    {
+                        var test = this.ModuleName;
+                        if (!string.IsNullOrWhiteSpace(test))
+                        {
+                            return test;
+                        }
+                    }
+                    catch
+                    {
+                    }
+                    return t.Name;
+                }
+                else
+                    return this.ModuleName;
             }
-            catch (Exception e)
+        }
+
+        /// <summary>
+        /// 表示模組是否已初始化。
+        /// </summary>
+        public ModuleStatus Status { get; private set; }
+
+        /// <summary>
+        /// 初始化模組
+        /// </summary>
+        /// <param name="textSegmenter"></param>
+        /// <param name="groupManager"></param>
+        internal void Initialize(ITextSegmenter textSegmenter, IGroupManager groupManager)
+        {
+            if (this.Status.Equals(ModuleStatus.Uninitialized))
             {
-                Console.WriteLine(e);
-                return false;
+                try
+                {
+                    this.BuildPatterns(groupManager, this._patternfactory);
+                    textSegmenter.Load(groupManager);
+                    this.Status = ModuleStatus.Initialized;
+                    this.AfterInitialize();
+                }
+                catch //(Exception e)
+                {
+                    this.Status = ModuleStatus.FailToInitialize;
+                }
             }
         }
 

+ 8 - 0
src/Yuuna.Faker/Fake.cs

@@ -10,9 +10,17 @@ namespace TestPlugin
     using Yuuna.Contracts.Modules;
     using Yuuna.Contracts.Semantics;
     using Yuuna.Common.Utilities;
+    using Newtonsoft.Json;
 
     public sealed class Fake : ModuleBase
     {
+        public Fake()
+        {
+            this.ModuleName = JsonConvert.DeserializeAnonymousType(@"{ ""Name"": ""Fake模組"" }", new { Name = default(string) }).Name;
+        }
+
+        protected override string ModuleName { get; }
+
         protected override void BuildPatterns(IGroupManager g, IPatternBuilder p)
         {
             g.Define("open").AppendOrCreate(new[] { "打開", "開" });

+ 10 - 1
src/Yuuna.Faker/Yuuna.Faker.csproj

@@ -1,14 +1,23 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <OutputType>Library</OutputType>
     <TargetFramework>netcoreapp3.0</TargetFramework>
     <ApplicationIcon />
     <StartupObject />
+    <RunPostBuildEvent>Always</RunPostBuildEvent>
   </PropertyGroup>
 
+  <ItemGroup>
+    <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
+  </ItemGroup>
+
   <ItemGroup>
     <ProjectReference Include="..\Yuuna.Contracts\Yuuna.Contracts.csproj" />
   </ItemGroup>
 
+  <Target Name="PostBuild" AfterTargets="PostBuildEvent">
+    <Exec Command="copy $(TargetPath) &quot;$(SolutionDir)src\Yuuna\bin\Debug\netcoreapp3.0\modules\M&quot;" />
+  </Target>
+
 </Project>

+ 17 - 9
src/Yuuna/EntryPoint.cs

@@ -4,12 +4,8 @@
 namespace Yuuna
 {
     using System;
-    using System.Collections.Generic;
-    using System.Collections.Immutable;
-    using System.Linq;
     using System.Text;
 
-    using TestPlugin;
 
     using Yuuna.Contracts.Optimization;
     using Yuuna.Contracts.Modules;
@@ -19,16 +15,28 @@ namespace Yuuna
     using Yuuna.TextSegmention;
     using Yuuna.Common.Utilities;
     using System.Threading.Tasks;
+    using System.Reflection;
 
     internal class EntryPoint
     {
         public static void Main(string[] args)
         {
-
-            Send("關門");
-
+            var mc = new ModuleCollection();
+             
+            Console.WriteLine(mc._modules[mc.Names[0]].Name);
+            Console.ReadKey();
+
+            mc._modules[mc.Names[0]].Unload();
+            Console.WriteLine(mc._modules[mc.Names[0]].Name);
+            Console.ReadKey();
+
+            mc._modules[mc.Names[0]].Reload();
+            Console.WriteLine(mc._modules[mc.Names[0]].Name);
+            Console.ReadKey();
+            //Send("打開門");
             return;
-            // Send("打開門"); Send("開燈");
+            
+            Send("開燈");
             try
             {
                 var stt = SpeechRecognizer.Create("secret.json");
@@ -66,7 +74,7 @@ namespace Yuuna
         public static void Send(string text)
         {
             var segmenter = new JiebaTextSegmenter() as ITextSegmenter;
-            var allPlugins = new ModuleBase[] { new Fake() };
+            var allPlugins = new ModuleBase[] { /*new Fake()*/ };
             foreach (var item in allPlugins)
             {
                 item.Initialize(segmenter, new GroupManager());

+ 41 - 0
src/Yuuna/ModuleCollection.cs

@@ -0,0 +1,41 @@
+// Author: Orlys
+// Github: https://github.com/Orlys
+
+namespace Yuuna
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using Yuuna.Contracts.Modules;
+    using System.Runtime.Loader;
+    using System.IO;
+    using System.Linq.Expressions;
+    using System.Reflection;
+     
+
+    public class ModuleCollection
+    {
+        public string[] Names => this._modules.Keys.ToArray();
+
+
+        public readonly Dictionary<string, ModuleDomain> _modules;
+         
+        public ModuleCollection()
+        {
+            this._modules = new Dictionary<string, ModuleDomain>();
+            var modulesFolder = new DirectoryInfo("./modules");
+            if (!modulesFolder.Exists)
+            {
+                modulesFolder.Create();
+            }
+            else
+            {
+                foreach (var moduleFolder in modulesFolder.EnumerateDirectories())
+                {
+                    if (ModuleDomain.TryCreate(moduleFolder, out var domain))
+                        this._modules.Add(domain.Name, domain);
+                }
+            }
+        }
+    }
+}

+ 109 - 0
src/Yuuna/ModuleDomain.cs

@@ -0,0 +1,109 @@
+// Author: Orlys
+// Github: https://github.com/Orlys
+
+namespace Yuuna
+{
+    using System;
+    using Yuuna.Contracts.Modules;
+    using Yuuna.Contracts.TextSegmention;
+    using System.Runtime.Loader;
+    using Yuuna.Contracts.Semantics;
+    using System.Linq.Expressions;
+    using System.IO;
+    using System.Linq;
+
+    public sealed class ModuleDomain 
+    { 
+        private volatile ModuleBase _inst;
+        private volatile AssemblyLoadContext _loader; 
+        private readonly DirectoryInfo _moduleFolder;
+
+        public string Name => this._inst?.Name;
+        private ModuleDomain(DirectoryInfo moduleFolder, AssemblyLoadContext loader, ModuleBase inst)
+        {
+            this._moduleFolder = moduleFolder;
+            this._loader = loader;
+            this._inst = inst; 
+        }
+
+        public static bool TryCreate(DirectoryInfo moduleFolder, out ModuleDomain domain)
+        {
+            if( LoadSingleModuleType(moduleFolder, out var loader, out var inst))
+            {
+                domain = new ModuleDomain(moduleFolder, loader, inst);
+                return true;
+            }
+            domain = null;
+            return false;
+        }
+
+        private static bool LoadSingleModuleType(DirectoryInfo moduleFolder, out AssemblyLoadContext loader, out ModuleBase inst)
+        {
+            loader = new AssemblyLoadContext(moduleFolder.Name, true);
+
+            //var refs = new HashSet<AssemblyName>();
+            foreach (var file in moduleFolder.EnumerateFiles("*.dll", SearchOption.AllDirectories))
+            {
+                try
+                {
+                    /*var a = */
+                    loader.LoadFromAssemblyPath(file.FullName);
+                    //foreach (var refA in a.GetReferencedAssemblies()) 
+                    //    refs.Add(refA); 
+                }
+                catch
+                {
+                }
+            }
+
+            var type  = loader.Assemblies
+                .SelectMany(x => x.GetTypes())
+                .SingleOrDefault(x =>
+                    x.IsSubclassOf(typeof(ModuleBase)) &&
+                    x.IsPublic &&
+                    !x.IsAbstract);
+
+            inst = null;
+            if (type is null)
+            {
+                loader.Unload();
+                loader = null;
+            }
+            else
+            {
+                try
+                {
+                    inst = Activator.CreateInstance(type) as ModuleBase;
+                }
+                catch  
+                { 
+                } 
+            }
+            return inst is ModuleBase;
+        }
+
+        internal void Initialize(ITextSegmenter textSegmenter, IGroupManager groupManager)
+        {
+            this._inst.Initialize(textSegmenter, groupManager);
+        } 
+
+        public void Reload()
+        {
+            if(LoadSingleModuleType(this._moduleFolder, out var loader, out var inst))
+            {
+                this.Unload();
+                this._loader = loader;
+                this._inst = inst;
+            }
+        }
+
+        public void Unload()
+        {
+            (this._inst as IDisposable)?.Dispose();
+            this._inst = null;
+
+            this._loader?.Unload();
+            this._loader = null; 
+        }
+    }
+}

+ 52 - 0
src/Yuuna/TypeHelper.cs

@@ -0,0 +1,52 @@
+// Author: Orlys
+// Github: https://github.com/Orlys
+
+namespace Yuuna
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Collections.Immutable;
+    using System.Reflection;
+
+    public static class TypeHelper
+    { 
+        public static bool ValidateAssemblyFile(string assemblyFile, out AssemblyName assemblyName)
+        {
+            try
+            {
+                assemblyName = AssemblyName.GetAssemblyName(assemblyFile);
+                return true;
+            }
+            catch
+            {
+            }
+            assemblyName = null;
+            return false;
+        }
+
+        public static IImmutableList<Type> ScanTypes(Assembly assembly)
+        {
+            var types = new List<Type>();
+            var set = new HashSet<Assembly>();
+            var stack = new Stack<Assembly>();
+            stack.Push(assembly);
+
+            while (stack.Count > 0)
+            {
+                var asm = stack.Pop();
+
+                if (asm.IsDynamic) continue;
+
+                types.AddRange(asm.DefinedTypes);
+
+                foreach (var other in asm.GetReferencedAssemblies())
+                {
+                    if (set.Add(asm))
+                        stack.Push(Assembly.Load(other));
+                }
+            }
+
+            return types.ToImmutableArray();
+        }
+    }
+}

+ 0 - 1
src/Yuuna/Yuuna.csproj

@@ -7,7 +7,6 @@
 
   <ItemGroup>
     <ProjectReference Include="..\Yuuna.Contracts\Yuuna.Contracts.csproj" />
-    <ProjectReference Include="..\Yuuna.Faker\Yuuna.Faker.csproj" />
   </ItemGroup>
 
   <ItemGroup>