| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,151 @@ | |||
| 1 | + using Python.Runtime; | ||
| 2 | + using System; | ||
| 3 | + using System.Diagnostics; | ||
| 4 | + using System.Reflection; | ||
| 5 | + | ||
| 6 | + // | ||
| 7 | + // The code we'll test. All that really matters is | ||
| 8 | + // using GIL { Python.Exec(pyScript); } | ||
| 9 | + // but the rest is useful for debugging. | ||
| 10 | + // | ||
| 11 | + // What matters in the python code is gc.collect and clr.AddReference. | ||
| 12 | + // | ||
| 13 | + // Note that the language version is 2.0, so no $"foo{bar}" syntax. | ||
| 14 | + // | ||
| 15 | + static class PythonRunner | ||
| 16 | + { | ||
| 17 | + static readonly Action<IntPtr> XIncref; | ||
| 18 | + static readonly Action<IntPtr> XDecref; | ||
| 19 | + | ||
| 20 | + static PythonRunner() | ||
| 21 | + { | ||
| 22 | + const BindingFlags flags = BindingFlags.Static | BindingFlags.NonPublic; | ||
| 23 | + MethodInfo incMethod = typeof(Runtime).GetMethod("XIncref", flags); | ||
| 24 | + MethodInfo decMethod = typeof(Runtime).GetMethod("XDecref", flags); | ||
| 25 | + | ||
| 26 | + XIncref = (Action<IntPtr>)Delegate.CreateDelegate(typeof(Action<IntPtr>), incMethod); | ||
| 27 | + XDecref = (Action<IntPtr>)Delegate.CreateDelegate(typeof(Action<IntPtr>), decMethod); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + public static void RunPython() | ||
| 31 | + { | ||
| 32 | + AppDomain.CurrentDomain.DomainUnload += OnDomainUnload; | ||
| 33 | + string name = AppDomain.CurrentDomain.FriendlyName; | ||
| 34 | + Console.WriteLine(string.Format("[{0} in .NET] In PythonRunner.RunPython", name)); | ||
| 35 | + PythonEngine.Initialize(mode: ShutdownMode.Reload); | ||
| 36 | + using (Py.GIL()) | ||
| 37 | + { | ||
| 38 | + try | ||
| 39 | + { | ||
| 40 | + var pyScript = string.Format("import clr\n" | ||
| 41 | + + "print('[{0} in python] imported clr')\n" | ||
| 42 | + + "clr.AddReference('System')\n" | ||
| 43 | + + "print('[{0} in python] allocated a clr object')\n" | ||
| 44 | + + "import gc\n" | ||
| 45 | + + "gc.collect()\n" | ||
| 46 | + + "print('[{0} in python] collected garbage')\n", | ||
| 47 | + name); | ||
| 48 | + PythonEngine.Exec(pyScript); | ||
| 49 | + } | ||
| 50 | + catch (Exception e) | ||
| 51 | + { | ||
| 52 | + Console.WriteLine(string.Format("[{0} in .NET] Caught exception: {1}", name, e)); | ||
| 53 | + } | ||
| 54 | + } | ||
| 55 | + PythonEngine.BeginAllowThreads(); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + | ||
| 59 | + private static IntPtr _state; | ||
| 60 | + | ||
| 61 | + public static void InitPython(ShutdownMode mode) | ||
| 62 | + { | ||
| 63 | + PythonEngine.Initialize(mode: mode); | ||
| 64 | + _state = PythonEngine.BeginAllowThreads(); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + public static void ShutdownPython() | ||
| 68 | + { | ||
| 69 | + PythonEngine.EndAllowThreads(_state); | ||
| 70 | + PythonEngine.Shutdown(); | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + public static void ShutdownPythonCompletely() | ||
| 74 | + { | ||
| 75 | + PythonEngine.EndAllowThreads(_state); | ||
| 76 | + PythonEngine.ShutdownMode = ShutdownMode.Normal; | ||
| 77 | + PythonEngine.Shutdown(); | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + public static IntPtr GetTestObject() | ||
| 81 | + { | ||
| 82 | + try | ||
| 83 | + { | ||
| 84 | + Type type = typeof(Python.EmbeddingTest.Domain.MyClass); | ||
| 85 | + string code = string.Format(@" | ||
| 86 | + import clr | ||
| 87 | + clr.AddReference('{0}') | ||
| 88 | + | ||
| 89 | + from Python.EmbeddingTest.Domain import MyClass | ||
| 90 | + obj = MyClass() | ||
| 91 | + obj.Method() | ||
| 92 | + obj.StaticMethod() | ||
| 93 | + ", Assembly.GetExecutingAssembly().FullName); | ||
| 94 | + | ||
| 95 | + using (Py.GIL()) | ||
| 96 | + using (var scope = Py.CreateScope()) | ||
| 97 | + { | ||
| 98 | + scope.Exec(code); | ||
| 99 | + using (PyObject obj = scope.Get("obj")) | ||
| 100 | + { | ||
| 101 | + Debug.Assert(obj.AsManagedObject(type).GetType() == type); | ||
| 102 | + // We only needs its Python handle | ||
| 103 | + XIncref(obj.Handle); | ||
| 104 | + return obj.Handle; | ||
| 105 | + } | ||
| 106 | + } | ||
| 107 | + } | ||
| 108 | + catch (Exception e) | ||
| 109 | + { | ||
| 110 | + Debug.WriteLine(e); | ||
| 111 | + throw; | ||
| 112 | + } | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + public static void RunTestObject(IntPtr handle) | ||
| 116 | + { | ||
| 117 | + using (Py.GIL()) | ||
| 118 | + { | ||
| 119 | + | ||
| 120 | + using (PyObject obj = new PyObject(handle)) | ||
| 121 | + { | ||
| 122 | + obj.InvokeMethod("Method"); | ||
| 123 | + obj.InvokeMethod("StaticMethod"); | ||
| 124 | + } | ||
| 125 | + } | ||
| 126 | + } | ||
| 127 | + | ||
| 128 | + public static void ReleaseTestObject(IntPtr handle) | ||
| 129 | + { | ||
| 130 | + using (Py.GIL()) | ||
| 131 | + { | ||
| 132 | + XDecref(handle); | ||
| 133 | + } | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + static void OnDomainUnload(object sender, EventArgs e) | ||
| 137 | + { | ||
| 138 | + Console.WriteLine(string.Format("[{0} in .NET] unloading", AppDomain.CurrentDomain.FriendlyName)); | ||
| 139 | + } | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + | ||
| 143 | + namespace Python.EmbeddingTest.Domain | ||
| 144 | + { | ||
| 145 | + [Serializable] | ||
| 146 | + public class MyClass | ||
| 147 | + { | ||
| 148 | + public void Method() { } | ||
| 149 | + public static void StaticMethod() { } | ||
| 150 | + } | ||
| 151 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -65,9 +65,13 @@ | |||
| 65 | 65 | <PropertyGroup Condition="$(Configuration.Contains('Release'))"> | |
| 66 | 66 | <DefineConstants Condition="'$(CustomDefineConstants)' == ''">$(DefineConstants)</DefineConstants> | |
| 67 | 67 | </PropertyGroup> | |
| 68 | + <ItemGroup> | ||
| 69 | + <Compile Remove="DomainCode.cs" /> | ||
| 70 | + </ItemGroup> | ||
| 68 | 71 | ||
| 69 | 72 | <ItemGroup> | |
| 70 | 73 | <None Include="..\pythonnet.snk" /> | |
| 74 | + <None Include="DomainCode.cs" /> | ||
| 71 | 75 | <None Remove="packages.config" /> | |
| 72 | 76 | </ItemGroup> | |
| 73 | 77 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,9 @@ | |||
| 1 | 1 | using System; | |
| 2 | 2 | using System.CodeDom.Compiler; | |
| 3 | + using System.Collections.Generic; | ||
| 4 | + using System.Diagnostics; | ||
| 5 | + using System.IO; | ||
| 6 | + using System.Linq; | ||
| 3 | 7 | using System.Reflection; | |
| 4 | 8 | using NUnit.Framework; | |
| 5 | 9 | using Python.Runtime; | |
@@ -68,46 +72,40 @@ public static void DomainReloadAndGC() | |||
| 68 | 72 | PythonEngine.Shutdown(); | |
| 69 | 73 | } | |
| 70 | 74 | ||
| 71 | - // | ||
| 72 | - // The code we'll test. All that really matters is | ||
| 73 | - // using GIL { Python.Exec(pyScript); } | ||
| 74 | - // but the rest is useful for debugging. | ||
| 75 | - // | ||
| 76 | - // What matters in the python code is gc.collect and clr.AddReference. | ||
| 77 | - // | ||
| 78 | - // Note that the language version is 2.0, so no $"foo{bar}" syntax. | ||
| 79 | - // | ||
| 80 | - const string TestCode = @" | ||
| 81 | - using Python.Runtime; | ||
| 82 | - using System; | ||
| 83 | - class PythonRunner { | ||
| 84 | - public static void RunPython() { | ||
| 85 | - AppDomain.CurrentDomain.DomainUnload += OnDomainUnload; | ||
| 86 | - string name = AppDomain.CurrentDomain.FriendlyName; | ||
| 87 | - Console.WriteLine(string.Format(""[{0} in .NET] In PythonRunner.RunPython"", name)); | ||
| 88 | - PythonEngine.Initialize(mode: ShutdownMode.Reload); | ||
| 89 | - using (Py.GIL()) { | ||
| 90 | - try { | ||
| 91 | - var pyScript = string.Format(""import clr\n"" | ||
| 92 | - + ""print('[{0} in python] imported clr')\n"" | ||
| 93 | - + ""clr.AddReference('System')\n"" | ||
| 94 | - + ""print('[{0} in python] allocated a clr object')\n"" | ||
| 95 | - + ""import gc\n"" | ||
| 96 | - + ""gc.collect()\n"" | ||
| 97 | - + ""print('[{0} in python] collected garbage')\n"", | ||
| 98 | - name); | ||
| 99 | - PythonEngine.Exec(pyScript); | ||
| 100 | - } catch(Exception e) { | ||
| 101 | - Console.WriteLine(string.Format(""[{0} in .NET] Caught exception: {1}"", name, e)); | ||
| 102 | - } | ||
| 103 | - } | ||
| 104 | - PythonEngine.BeginAllowThreads(); | ||
| 105 | - } | ||
| 106 | - static void OnDomainUnload(object sender, EventArgs e) { | ||
| 107 | - System.Console.WriteLine(string.Format(""[{0} in .NET] unloading"", AppDomain.CurrentDomain.FriendlyName)); | ||
| 108 | - } | ||
| 109 | - }"; | ||
| 75 | + [Test] | ||
| 76 | + public static void CrossDomainObject() | ||
| 77 | + { | ||
| 78 | + IntPtr handle; | ||
| 79 | + Type type = typeof(Proxy); | ||
| 80 | + Assembly assembly = BuildAssembly("test_domain_reload"); | ||
| 81 | + { | ||
| 82 | + AppDomain domain = CreateDomain("test_domain_reload"); | ||
| 83 | + var theProxy = (Proxy)domain.CreateInstanceAndUnwrap( | ||
| 84 | + type.Assembly.FullName, | ||
| 85 | + type.FullName); | ||
| 86 | + theProxy.InitAssembly(assembly.Location); | ||
| 87 | + theProxy.Call("InitPython", ShutdownMode.Reload); | ||
| 88 | + handle = (IntPtr)theProxy.Call("GetTestObject"); | ||
| 89 | + theProxy.Call("ShutdownPython"); | ||
| 90 | + AppDomain.Unload(domain); | ||
| 91 | + } | ||
| 110 | 92 | ||
| 93 | + { | ||
| 94 | + AppDomain domain = CreateDomain("test_domain_reload"); | ||
| 95 | + var theProxy = (Proxy)domain.CreateInstanceAndUnwrap( | ||
| 96 | + type.Assembly.FullName, | ||
| 97 | + type.FullName); | ||
| 98 | + theProxy.InitAssembly(assembly.Location); | ||
| 99 | + theProxy.Call("InitPython", ShutdownMode.Reload); | ||
| 100 | + | ||
| 101 | + // handle refering a clr object created in previous domain, | ||
| 102 | + // it should had been deserialized and became callable agian. | ||
| 103 | + theProxy.Call("RunTestObject", handle); | ||
| 104 | + theProxy.Call("ShutdownPythonCompletely"); | ||
| 105 | + AppDomain.Unload(domain); | ||
| 106 | + } | ||
| 107 | + Assert.IsTrue(Runtime.Runtime.Py_IsInitialized() == 0); | ||
| 108 | + } | ||
| 111 | 109 | ||
| 112 | 110 | /// <summary> | |
| 113 | 111 | /// Build an assembly out of the source code above. | |
@@ -119,15 +117,19 @@ static void OnDomainUnload(object sender, EventArgs e) { | |||
| 119 | 117 | static Assembly BuildAssembly(string assemblyName) | |
| 120 | 118 | { | |
| 121 | 119 | var provider = CodeDomProvider.CreateProvider("CSharp"); | |
| 122 | - | ||
| 123 | 120 | var compilerparams = new CompilerParameters(); | |
| 124 | - compilerparams.ReferencedAssemblies.Add("Python.Runtime.dll"); | ||
| 121 | + var assemblies = from assembly in AppDomain.CurrentDomain.GetAssemblies() | ||
| 122 | + where !assembly.IsDynamic | ||
| 123 | + select assembly.Location; | ||
| 124 | + compilerparams.ReferencedAssemblies.AddRange(assemblies.ToArray()); | ||
| 125 | + | ||
| 125 | 126 | compilerparams.GenerateExecutable = false; | |
| 126 | 127 | compilerparams.GenerateInMemory = false; | |
| 127 | - compilerparams.IncludeDebugInformation = false; | ||
| 128 | + compilerparams.IncludeDebugInformation = true; | ||
| 128 | 129 | compilerparams.OutputAssembly = assemblyName; | |
| 129 | 130 | ||
| 130 | - var results = provider.CompileAssemblyFromSource(compilerparams, TestCode); | ||
| 131 | + var dir = Path.GetDirectoryName(new StackTrace(true).GetFrame(0).GetFileName()); | ||
| 132 | + var results = provider.CompileAssemblyFromFile(compilerparams, Path.Combine(dir, "DomainCode.cs")); | ||
| 131 | 133 | if (results.Errors.HasErrors) | |
| 132 | 134 | { | |
| 133 | 135 | var errors = new System.Text.StringBuilder("Compiler Errors:\n"); | |
@@ -168,6 +170,13 @@ public void RunPython() | |||
| 168 | 170 | ||
| 169 | 171 | Console.WriteLine("[Proxy] Leaving RunPython"); | |
| 170 | 172 | } | |
| 173 | + | ||
| 174 | + public object Call(string methodName, params object[] args) | ||
| 175 | + { | ||
| 176 | + var pythonrunner = theAssembly.GetType("PythonRunner"); | ||
| 177 | + var method = pythonrunner.GetMethod(methodName); | ||
| 178 | + return method.Invoke(null, args); | ||
| 179 | + } | ||
| 171 | 180 | } | |
| 172 | 181 | ||
| 173 | 182 | /// <summary> | |
@@ -178,26 +187,11 @@ static void RunAssemblyAndUnload(Assembly assembly, string assemblyName) | |||
| 178 | 187 | { | |
| 179 | 188 | Console.WriteLine($"[Program.Main] === creating domain for assembly {assembly.FullName}"); | |
| 180 | 189 | ||
| 181 | - // Create the domain. Make sure to set PrivateBinPath to a relative | ||
| 182 | - // path from the CWD (namely, 'bin'). | ||
| 183 | - // See https://stackoverflow.com/questions/24760543/createinstanceandunwrap-in-another-domain | ||
| 184 | - var currentDomain = AppDomain.CurrentDomain; | ||
| 185 | - var domainsetup = new AppDomainSetup() | ||
| 186 | - { | ||
| 187 | - ApplicationBase = currentDomain.SetupInformation.ApplicationBase, | ||
| 188 | - ConfigurationFile = currentDomain.SetupInformation.ConfigurationFile, | ||
| 189 | - LoaderOptimization = LoaderOptimization.SingleDomain, | ||
| 190 | - PrivateBinPath = "." | ||
| 191 | - }; | ||
| 192 | - var domain = AppDomain.CreateDomain( | ||
| 193 | - $"My Domain {assemblyName}", | ||
| 194 | - currentDomain.Evidence, | ||
| 195 | - domainsetup); | ||
| 196 | - | ||
| 190 | + AppDomain domain = CreateDomain(assemblyName); | ||
| 197 | 191 | // Create a Proxy object in the new domain, where we want the | |
| 198 | 192 | // assembly (and Python .NET) to reside | |
| 199 | - Type type = typeof(Proxy); | ||
| 200 | 193 | System.IO.Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); | |
| 194 | + Type type = typeof(Proxy); | ||
| 201 | 195 | var theProxy = (Proxy)domain.CreateInstanceAndUnwrap( | |
| 202 | 196 | type.Assembly.FullName, | |
| 203 | 197 | type.FullName); | |
@@ -214,13 +208,34 @@ static void RunAssemblyAndUnload(Assembly assembly, string assemblyName) | |||
| 214 | 208 | try | |
| 215 | 209 | { | |
| 216 | 210 | Console.WriteLine($"[Program.Main] The Proxy object is valid ({theProxy}). Unexpected domain unload behavior"); | |
| 211 | + Assert.Fail($"{theProxy} should be invlaid now"); | ||
| 217 | 212 | } | |
| 218 | 213 | catch (AppDomainUnloadedException) | |
| 219 | 214 | { | |
| 220 | 215 | Console.WriteLine("[Program.Main] The Proxy object is not valid anymore, domain unload complete."); | |
| 221 | 216 | } | |
| 222 | 217 | } | |
| 223 | 218 | ||
| 219 | + private static AppDomain CreateDomain(string name) | ||
| 220 | + { | ||
| 221 | + // Create the domain. Make sure to set PrivateBinPath to a relative | ||
| 222 | + // path from the CWD (namely, 'bin'). | ||
| 223 | + // See https://stackoverflow.com/questions/24760543/createinstanceandunwrap-in-another-domain | ||
| 224 | + var currentDomain = AppDomain.CurrentDomain; | ||
| 225 | + var domainsetup = new AppDomainSetup() | ||
| 226 | + { | ||
| 227 | + ApplicationBase = currentDomain.SetupInformation.ApplicationBase, | ||
| 228 | + ConfigurationFile = currentDomain.SetupInformation.ConfigurationFile, | ||
| 229 | + LoaderOptimization = LoaderOptimization.SingleDomain, | ||
| 230 | + PrivateBinPath = "." | ||
| 231 | + }; | ||
| 232 | + var domain = AppDomain.CreateDomain( | ||
| 233 | + $"My Domain {name}", | ||
| 234 | + currentDomain.Evidence, | ||
| 235 | + domainsetup); | ||
| 236 | + return domain; | ||
| 237 | + } | ||
| 238 | + | ||
| 224 | 239 | /// <summary> | |
| 225 | 240 | /// Resolves the assembly. Why doesn't this just work normally? | |
| 226 | 241 | /// </summary> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | 4 | namespace Python.Runtime | |
| 5 | 5 | { | |
| 6 | + [Serializable] | ||
| 6 | 7 | internal class CLRObject : ManagedType | |
| 7 | 8 | { | |
| 8 | 9 | internal object inst; | |
@@ -23,7 +24,7 @@ internal CLRObject(object ob, IntPtr tp) | |||
| 23 | 24 | } | |
| 24 | 25 | } | |
| 25 | 26 | ||
| 26 | - GCHandle gc = AllocGCHandle(); | ||
| 27 | + GCHandle gc = AllocGCHandle(TrackTypes.Wrapper); | ||
| 27 | 28 | Marshal.WriteIntPtr(py, ObjectOffset.magic(tp), (IntPtr)gc); | |
| 28 | 29 | tpHandle = tp; | |
| 29 | 30 | pyHandle = py; | |
@@ -68,5 +69,19 @@ internal static IntPtr GetInstHandle(object ob) | |||
| 68 | 69 | CLRObject co = GetInstance(ob); | |
| 69 | 70 | return co.pyHandle; | |
| 70 | 71 | } | |
| 72 | + | ||
| 73 | + protected override void OnSave() | ||
| 74 | + { | ||
| 75 | + base.OnSave(); | ||
| 76 | + Runtime.XIncref(pyHandle); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + protected override void OnLoad() | ||
| 80 | + { | ||
| 81 | + base.OnLoad(); | ||
| 82 | + GCHandle gc = AllocGCHandle(TrackTypes.Wrapper); | ||
| 83 | + Marshal.WriteIntPtr(pyHandle, ObjectOffset.magic(tpHandle), (IntPtr)gc); | ||
| 84 | + Runtime.XDecref(pyHandle); | ||
| 85 | + } | ||
| 71 | 86 | } | |
| 72 | 87 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments