[NUI.Gadget] Support Unload() method to release assembly (#6073)
authorhjhun <36876573+hjhun@users.noreply.github.com>
Wed, 17 Apr 2024 23:21:51 +0000 (08:21 +0900)
committerGitHub <noreply@github.com>
Wed, 17 Apr 2024 23:21:51 +0000 (08:21 +0900)
* [NUI.Gadget] Support Unload() method to release assembly

To release loaded assembly, the Unload() method is added.

Signed-off-by: Hwankyu Jhun <h.jhun@samsung.com>
* Set nullptr to NUIGadgetInfo.NUIGadgetAssembly

Signed-off-by: Hwankyu Jhun <h.jhun@samsung.com>
---------

Signed-off-by: Hwankyu Jhun <h.jhun@samsung.com>
src/Tizen.NUI.Gadget/Tizen.NUI/NUIGadgetAssembly.cs [new file with mode: 0644]
src/Tizen.NUI.Gadget/Tizen.NUI/NUIGadgetInfo.cs
src/Tizen.NUI.Gadget/Tizen.NUI/NUIGadgetManager.cs

diff --git a/src/Tizen.NUI.Gadget/Tizen.NUI/NUIGadgetAssembly.cs b/src/Tizen.NUI.Gadget/Tizen.NUI/NUIGadgetAssembly.cs
new file mode 100644 (file)
index 0000000..9946fe8
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+* Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+*
+* Licensed under the Apache License, Version 2.0 (the License);
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an AS IS BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+using System.IO;
+using System.Reflection;
+using System.Runtime.Loader;
+
+using SystemIO = System.IO;
+
+namespace Tizen.NUI
+{
+    internal class NUIGadgetAssemblyLoadContext : AssemblyLoadContext
+    {
+        public NUIGadgetAssemblyLoadContext() : base(isCollectible: true)
+        {
+        }
+
+        protected override Assembly Load(AssemblyName name)
+        {
+            return null;
+        }
+    }
+
+    internal class NUIGadgetAssembly
+    {
+        private static readonly object _assemblyLock = new object();
+        private readonly string _assemblyPath;
+        private WeakReference _assemblyRef;
+        private Assembly _assembly = null;
+
+        public NUIGadgetAssembly(string assemblyPath) { _assemblyPath = assemblyPath; }
+
+        public void Load()
+        {
+            lock (_assemblyLock)
+            {
+                if (_assembly != null)
+                {
+                    return;
+                }
+
+                Log.Warn("Load(): " + _assemblyPath + " ++");
+                NUIGadgetAssemblyLoadContext context = new NUIGadgetAssemblyLoadContext();
+                _assemblyRef = new WeakReference(context);
+                string directoryPath = SystemIO.Path.GetDirectoryName(_assemblyPath);
+                string fileName = SystemIO.Path.GetFileNameWithoutExtension(_assemblyPath);
+                string nativeImagePath = directoryPath + "/.native_image/" + fileName + ".ni.dll";
+                Log.Debug("NativeImagePath=" + nativeImagePath + ", AssemblyPath=" + _assemblyPath);
+                _assembly = context.LoadFromNativeImagePath(nativeImagePath, _assemblyPath);
+                Log.Warn("Load(): " + _assemblyPath + " --");
+            }
+        }
+
+        public bool IsLoaded { get { return _assembly != null; } }
+
+        public NUIGadget CreateInstance(string className)
+        {
+            lock (_assemblyLock)
+            {
+                return (NUIGadget)_assembly?.CreateInstance(className);
+            }
+        }
+
+        public void Unload()
+        {
+            lock (_assemblyLock)
+            {
+                if (_assembly == null)
+                {
+                    return;
+                }
+
+                Log.Warn("Unload(): " + _assemblyPath + " ++");
+                if (_assemblyRef.IsAlive)
+                {
+                    (_assemblyRef.Target as NUIGadgetAssemblyLoadContext).Unload();
+                }
+
+                _assembly = null;
+                Log.Warn("Unload(): " + _assemblyPath + " --");
+            }
+        }
+    }
+}
\ No newline at end of file
index fda1346..d25f520 100755 (executable)
@@ -87,6 +87,8 @@ namespace Tizen.NUI
 
         internal Assembly Assembly { get; set; }
 
+        internal NUIGadgetAssembly NUIGadgetAssembly { get; set; }
+
         internal static NUIGadgetInfo CreateNUIGadgetInfo(string packageId)
         {
             Interop.PackageManagerInfo.ErrorCode errorCode = Interop.PackageManagerInfo.PackageInfoGet(packageId, out IntPtr handle);
index 84b98e0..05aa174 100755 (executable)
@@ -142,16 +142,63 @@ namespace Tizen.NUI
         /// <since_tizen> 10 </since_tizen>
         public static void Load(string resourceType)
         {
+            Load(resourceType, true);
+        }
+
+        /// <summary>
+        /// Loads an assembly of the NUIGadget.
+        /// </summary>
+        /// <param name="resourceType">The resource type of the NUIGadget package.</param>
+        /// <param name="useDefaultContext">The flag if ture, use a default load context. Otherwise, use a new load context.</param>
+        /// <exception cref="ArgumentException">Thrown when failed because of a invalid argument.</exception>
+        /// <exception cref="InvalidOperationException">Thrown when failed because of an invalid operation.</exception>
+        /// <since_tizen> 10 </since_tizen>
+        public static void Load(string resourceType, bool useDefaultContext)
+        {
             if (string.IsNullOrEmpty(resourceType))
             {
                 throw new ArgumentException("Invalid argument");
             }
 
             NUIGadgetInfo info = Find(resourceType);
-            Load(info);
+            Load(info, useDefaultContext);
+        }
+
+        /// <summary>
+        /// Unloads the loaded assembly of the NUIGadget.
+        /// </summary>
+        /// <param name="resourceType">The resource type of the NUIGadget package.</param>
+        /// <exception cref="ArgumentException">Thrown when failed because of a invalid argument.</exception>
+        /// <since_tizen> 10 </since_tizen>
+        public static void Unload(string resourceType)
+        {
+            if (string.IsNullOrEmpty(resourceType))
+            {
+                throw new ArgumentException("Invalid argument");
+            }
+
+            NUIGadgetInfo info = Find(resourceType);
+            Unload(info);
+        }
+
+        private static void Unload(NUIGadgetInfo info)
+        {
+            if (info == null)
+            {
+                throw new ArgumentException("Invalid argument");
+            }
+
+            lock (info)
+            {
+                if (info.NUIGadgetAssembly != null && info.NUIGadgetAssembly.IsLoaded)
+                {
+                    info.NUIGadgetAssembly.Unload();
+                    info.NUIGadgetAssembly = null;
+                }
+            }
         }
 
-        private static void Load(NUIGadgetInfo info)
+        private static void Load(NUIGadgetInfo info, bool useDefaultContext)
         {
             if (info == null)
             {
@@ -162,12 +209,25 @@ namespace Tizen.NUI
             {
                 lock (info)
                 {
-                    if (info.Assembly == null)
+                    if (useDefaultContext)
                     {
+                        if (info.Assembly == null)
+                        {
 
-                        Log.Warn("NUIGadgetAssembly.Load(): " + info.ResourcePath + info.ExecutableFile + " ++");
-                        info.Assembly = Assembly.Load(SystemIO.Path.GetFileNameWithoutExtension(info.ExecutableFile));
-                        Log.Warn("NUIGadgetAssembly.Load(): " + info.ResourcePath + info.ExecutableFile + " --");
+                            Log.Warn("NUIGadget.Load(): " + info.ResourcePath + info.ExecutableFile + " ++");
+                            info.Assembly = Assembly.Load(SystemIO.Path.GetFileNameWithoutExtension(info.ExecutableFile));
+                            Log.Warn("NUIGadget.Load(): " + info.ResourcePath + info.ExecutableFile + " --");
+                        }
+                    }
+                    else
+                    {
+                        if (info.NUIGadgetAssembly == null)
+                        {
+                            Log.Warn("NUIGadgetAssembly.Load(): " + info.ResourcePath + info.ExecutableFile + " ++");
+                            info.NUIGadgetAssembly = new NUIGadgetAssembly(info.ResourcePath + info.ExecutableFile);
+                            info.NUIGadgetAssembly.Load();
+                            Log.Warn("NUIGadgetAssembly.Load(): " + info.ResourcePath + info.ExecutableFile + " --");
+                        }
                     }
                 }
             }
@@ -192,15 +252,30 @@ namespace Tizen.NUI
         /// <since_tizen> 10 </since_tizen>
         public static NUIGadget Add(string resourceType, string className)
         {
+            return Add(resourceType, className, true);
+        }
+
+        /// <summary>
+        /// Adds a NUIGadget to the NUIGadgetManager.
+        /// </summary>
+        /// <param name="resourceType">The resource type of the NUIGadget package.</param>
+        /// <param name="className">The class name of the NUIGadget.</param>
+        /// <param name="useDefaultContext">The flag it true, use a default context. Otherwise, use a new load context.</param>
+        /// <returns>The NUIGadget object.</returns>
+        /// <exception cref="ArgumentException">Thrown when failed because of a invalid argument.</exception>
+        /// <exception cref="InvalidOperationException">Thrown when failed because of an invalid operation.</exception>
+        /// <since_tizen> 10 </since_tizen>
+        public static NUIGadget Add(string resourceType, string className, bool useDefaultContext)
+        {
             if (string.IsNullOrEmpty(resourceType) || string.IsNullOrEmpty(className))
             {
                 throw new ArgumentException("Invalid argument");
             }
 
             NUIGadgetInfo info = Find(resourceType);
-            Load(info);
+            Load(info, useDefaultContext);
 
-            NUIGadget gadget = info.Assembly.CreateInstance(className, true) as NUIGadget;
+            NUIGadget gadget = useDefaultContext ? info.Assembly.CreateInstance(className, true) as NUIGadget : info.NUIGadgetAssembly.CreateInstance(className);
             if (gadget == null)
             {
                 throw new InvalidOperationException("Failed to create instance. className: " + className);