11using System ;
22using System . CodeDom . Compiler ;
3+ using System . Collections . Generic ;
4+ using System . Diagnostics ;
5+ using System . IO ;
6+ using System . Linq ;
37using System . Reflection ;
48using NUnit . Framework ;
59using Python . Runtime ;
@@ -68,46 +72,40 @@ public static void DomainReloadAndGC()
6872 PythonEngine . Shutdown ( ) ;
6973 }
7074
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+ }
11092
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+ }
111109
112110 /// <summary>
113111 /// Build an assembly out of the source code above.
@@ -119,15 +117,19 @@ static void OnDomainUnload(object sender, EventArgs e) {
119117 static Assembly BuildAssembly ( string assemblyName )
120118 {
121119 var provider = CodeDomProvider . CreateProvider ( "CSharp" ) ;
122-
123120 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+
125126 compilerparams . GenerateExecutable = false ;
126127 compilerparams . GenerateInMemory = false ;
127- compilerparams . IncludeDebugInformation = false ;
128+ compilerparams . IncludeDebugInformation = true ;
128129 compilerparams . OutputAssembly = assemblyName ;
129130
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" ) ) ;
131133 if ( results . Errors . HasErrors )
132134 {
133135 var errors = new System . Text . StringBuilder ( "Compiler Errors:\n " ) ;
@@ -168,6 +170,13 @@ public void RunPython()
168170
169171 Console . WriteLine ( "[Proxy] Leaving RunPython" ) ;
170172 }
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+ }
171180 }
172181
173182 /// <summary>
@@ -178,26 +187,11 @@ static void RunAssemblyAndUnload(Assembly assembly, string assemblyName)
178187 {
179188 Console . WriteLine ( $ "[Program.Main] === creating domain for assembly { assembly . FullName } ") ;
180189
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 ) ;
197191 // Create a Proxy object in the new domain, where we want the
198192 // assembly (and Python .NET) to reside
199- Type type = typeof ( Proxy ) ;
200193 System . IO . Directory . SetCurrentDirectory ( AppDomain . CurrentDomain . BaseDirectory ) ;
194+ Type type = typeof ( Proxy ) ;
201195 var theProxy = ( Proxy ) domain . CreateInstanceAndUnwrap (
202196 type . Assembly . FullName ,
203197 type . FullName ) ;
@@ -214,13 +208,34 @@ static void RunAssemblyAndUnload(Assembly assembly, string assemblyName)
214208 try
215209 {
216210 Console . WriteLine ( $ "[Program.Main] The Proxy object is valid ({ theProxy } ). Unexpected domain unload behavior") ;
211+ Assert . Fail ( $ "{ theProxy } should be invlaid now") ;
217212 }
218213 catch ( AppDomainUnloadedException )
219214 {
220215 Console . WriteLine ( "[Program.Main] The Proxy object is not valid anymore, domain unload complete." ) ;
221216 }
222217 }
223218
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+
224239 /// <summary>
225240 /// Resolves the assembly. Why doesn't this just work normally?
226241 /// </summary>
0 commit comments