diff --git a/hotReloadCrashRepro/App.config b/hotReloadCrashRepro/App.config
new file mode 100644
index 000000000..731f6de6c
--- /dev/null
+++ b/hotReloadCrashRepro/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hotReloadCrashRepro/Program.cs b/hotReloadCrashRepro/Program.cs
new file mode 100644
index 000000000..1b9345d81
--- /dev/null
+++ b/hotReloadCrashRepro/Program.cs
@@ -0,0 +1,139 @@
+using System;
+using System.CodeDom.Compiler;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace hotReloadCrashRepro
+{
+ class Program
+ {
+ ///
+ /// Args goes as follows:
+ /// 0: The full path to theAssembly.cs
+ ///
+ ///
+ static void Main(string[] args)
+ {
+ string pathToTheAssembly = "";
+ try
+ {
+ // Defaults if args are not specified (standard location when
+ // building with Visual Studio 2017, using the x64 configuration)
+ pathToTheAssembly = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
+ @"..\..\..\theAssembly.cs");
+ }
+ catch (Exception)
+ {
+ }
+
+ if (args.Length > 0)
+ {
+ pathToTheAssembly = args[0];
+ }
+
+ // The exception is thrown on the second call to Py_Finalize
+ //
+ // First time through, on the Python side we litter some objects
+ // that Python figures someone still has a reference to, so it
+ // keeps them around -- leak!
+ //
+ // Second time through, Python gc looks at the leaked objects and calls
+ // tp_traverse on them. But the tp_traverse handler is C# code that got
+ // destroyed in the domain unload -- crash!)
+ Assembly theCompiledAssembly = null;
+ for(int i = 0; i < 2; ++i) {
+ // Create the domain
+ System.Console.WriteLine(string.Format("[Program.Main] ===Pass #{0}===",i));
+ System.Console.WriteLine(string.Format("[Program.Main] Creating the domain \"My Domain {0}\"",i));
+ var domain = AppDomain.CreateDomain(string.Format("My Domain {0}",i));
+
+ // Build the assembly only once (we reuse the same assembly)
+ if (i == 0)
+ {
+ System.Console.WriteLine("[Program.Main] Building the assembly");
+
+ // The assembly is compiled as a dll in the same directory as the Program executable
+ theCompiledAssembly = BuildAssembly(pathToTheAssembly, "TheCompiledAssembly.dll");
+ }
+
+ // Create a Proxy object in the new domain, where we want the
+ // assembly (and Python .NET) to reside
+ Type type = typeof(Proxy);
+ var theProxy = (Proxy)domain.CreateInstanceAndUnwrap(
+ type.Assembly.FullName,
+ type.FullName);
+
+ // From now on use the Proxy to call into the new assembly
+ theProxy.InitAssembly(theCompiledAssembly.Location);
+ theProxy.RunPython();
+
+ System.Console.WriteLine("[Program.Main] Before Domain Unload");
+ AppDomain.Unload(domain);
+ System.Console.WriteLine("[Program.Main] After Domain Unload");
+
+ // Validate that the assembly does not exist anymore
+ try
+ {
+ System.Console.WriteLine(string.Format("[Program.Main] The Proxy object is valid ({0}). Unexpected domain unload behavior",theProxy));
+ }
+ catch (Exception)
+ {
+ System.Console.WriteLine("[Program.Main] The Proxy object is not valid anymore, domain unload complete.");
+ }
+ }
+ }
+
+ public class Proxy : MarshalByRefObject
+ {
+ static Assembly theAssembly = null;
+
+ public void InitAssembly(string assemblyPath)
+ {
+ System.Console.WriteLine(string.Format("[Proxy ] In InitAssembly"));
+
+ theAssembly = Assembly.LoadFile(assemblyPath);
+ var pythonrunner = theAssembly.GetType("PythonRunner");
+ var initMethod = pythonrunner.GetMethod("Init");
+ initMethod.Invoke(null, new object[] {});
+ }
+ public void RunPython()
+ {
+ System.Console.WriteLine(string.Format("[Proxy ] In RunPython"));
+
+ // Call into the new assembly. Will execute Python code
+ var pythonrunner = theAssembly.GetType("PythonRunner");
+ var runPythonMethod = pythonrunner.GetMethod("RunPython");
+ runPythonMethod.Invoke(null, new object[] { });
+ }
+ }
+
+ static System.Reflection.Assembly BuildAssembly(string pathToTheAssembly, string outputAssemblyName)
+ {
+ var provider = CodeDomProvider.CreateProvider("CSharp");
+ var compilerparams = new CompilerParameters(new string [] {"Python.Runtime.dll"});
+
+ compilerparams.GenerateExecutable = false;
+ compilerparams.GenerateInMemory = false;
+ compilerparams.IncludeDebugInformation = true;
+ compilerparams.OutputAssembly = outputAssemblyName;
+
+ var results =
+ provider.CompileAssemblyFromFile(compilerparams, pathToTheAssembly);
+ if (results.Errors.HasErrors) {
+ StringBuilder errors = new StringBuilder("Compiler Errors :\r\n");
+ foreach (CompilerError error in results.Errors )
+ {
+ errors.AppendFormat("Line {0},{1}\t: {2}\n",
+ error.Line, error.Column, error.ErrorText);
+ }
+ throw new Exception(errors.ToString());
+ } else {
+ return results.CompiledAssembly;
+ }
+ }
+ }
+}
diff --git a/hotReloadCrashRepro/Properties/AssemblyInfo.cs b/hotReloadCrashRepro/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..d1483c93e
--- /dev/null
+++ b/hotReloadCrashRepro/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("hotReloadCrashRepro")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("HP Inc.")]
+[assembly: AssemblyProduct("hotReloadCrashRepro")]
+[assembly: AssemblyCopyright("Copyright © HP Inc. 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("22642faa-c1aa-403e-97f7-6503790b6fe5")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/hotReloadCrashRepro/README.txt b/hotReloadCrashRepro/README.txt
new file mode 100644
index 000000000..3965c566a
--- /dev/null
+++ b/hotReloadCrashRepro/README.txt
@@ -0,0 +1,42 @@
+This project will reproduce the Python exception on second domain unload.
+Make sure you use a version of Python .NET that calls PythonEngine.Shutdown() on DomainReload (starting with commit ###################)
+
+How to repro:
+
+1. Open hotReloadCrashRepro.csproj in Visual Studio 2017
+2. Compile using the same platform as Python.Runtime.dll (e.g. x64)
+3. Copy Python.Runtime.dll in the directory where hotReloadCrashRepro.exe is located (e.g. bin\x64\Debug)
+4. Run "hotReloadCrashRepro.exe full_path_to_theAssembly.cs
+ e.g. hotReloadCrashRepro.exe "D:\projects\pythonnet\hotReloadCrashRepro\theAssembly.cs"
+
+The expected output is:
+
+[Program.Main] ===Pass #0===
+[Program.Main] Creating the domain "My Domain 0"
+[Program.Main] Building the assembly
+[Proxy ] In InitAssembly
+[theAssembly ] PythonRunner.Init current domain = My Domain 0
+[Proxy ] In RunPython
+[theAssembly ] In PythonRunner.RunPython
+[Python ] Done
+[Program.Main] Before Domain Unload
+[theAssembly ] In OnDomainUnload current domain = My Domain 0
+[Program.Main] After Domain Unload
+[Program.Main] The Proxy object is not valid anymore, domain unload complete.
+[Program.Main] ===Pass #1===
+[Program.Main] Creating the domain "My Domain 1"
+[Proxy ] In InitAssembly
+[theAssembly ] PythonRunner.Init current domain = My Domain 1
+[Proxy ] In RunPython
+[theAssembly ] In PythonRunner.RunPython
+[Python ] Done
+[Program.Main] Before Domain Unload
+[theAssembly ] In OnDomainUnload current domain = My Domain 1
+
+Unhandled Exception: System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain.
+ at Python.Runtime.Runtime.Py_Finalize()
+ at Python.Runtime.Runtime.Shutdown()
+ at Python.Runtime.PythonEngine.Shutdown()
+ at Python.Runtime.PythonEngine.OnDomainUnload(Object sender, EventArgs e)
+[Program.Main] After Domain Unload
+[Program.Main] The Proxy object is not valid anymore, domain unload complete.
\ No newline at end of file
diff --git a/hotReloadCrashRepro/hotReloadCrashRepro.csproj b/hotReloadCrashRepro/hotReloadCrashRepro.csproj
new file mode 100644
index 000000000..e5a1bd5b7
--- /dev/null
+++ b/hotReloadCrashRepro/hotReloadCrashRepro.csproj
@@ -0,0 +1,72 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {22642FAA-C1AA-403E-97F7-6503790B6FE5}
+ Exe
+ hotReloadCrashRepro
+ hotReloadCrashRepro
+ v4.6.1
+ 512
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE
+ full
+ x64
+ prompt
+ MinimumRecommendedRules.ruleset
+ true
+
+
+ bin\x64\Release\
+ TRACE
+ true
+ pdbonly
+ x64
+ prompt
+ MinimumRecommendedRules.ruleset
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hotReloadCrashRepro/theAssembly.cs b/hotReloadCrashRepro/theAssembly.cs
new file mode 100644
index 000000000..fa090ca6d
--- /dev/null
+++ b/hotReloadCrashRepro/theAssembly.cs
@@ -0,0 +1,44 @@
+using System;
+using Python.Runtime;
+using System.Reflection;
+
+public class DummyClass
+{
+ static public DummyClass instance = new DummyClass();
+ public static DummyClass DummyMethod()
+ {
+ return instance;
+ }
+}
+
+class PythonRunner
+{
+ static public void Init()
+ {
+ System.Console.WriteLine(string.Format("[theAssembly ] PythonRunner.Init current domain = {0}",AppDomain.CurrentDomain.FriendlyName));
+
+ // Register to domain unload
+ AppDomain.CurrentDomain.DomainUnload += OnDomainUnload;
+ }
+
+ private static void OnDomainUnload(object sender, EventArgs e)
+ {
+ System.Console.WriteLine(string.Format("[theAssembly ] In OnDomainUnload current domain = {0}",AppDomain.CurrentDomain.FriendlyName));
+ }
+
+ public static void RunPython() {
+ System.Console.WriteLine("[theAssembly ] In PythonRunner.RunPython");
+ using (Py.GIL()) {
+ try {
+ var pyScript =
+ "import clr\n" +
+ "clr.AddReference('System') \n" +
+ "print('[Python ] Done')\n";
+
+ PythonEngine.Exec(pyScript);
+ } catch(Exception e) {
+ System.Console.WriteLine(string.Format("Caught exception: {0}",e));
+ }
+ }
+ }
+}
diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj
index 1fea78082..e2f7b0839 100644
--- a/src/runtime/Python.Runtime.csproj
+++ b/src/runtime/Python.Runtime.csproj
@@ -1,5 +1,5 @@
-
+
Debug
AnyCPU
@@ -9,7 +9,7 @@
Python.Runtime
bin\Python.Runtime.xml
bin\
- v4.0
+ v4.7.1
1591
..\..\
@@ -32,45 +32,54 @@
PYTHON2;PYTHON27;UCS4
true
pdbonly
+ PYTHON2;PYTHON27;UCS2
+ false
PYTHON3;PYTHON36;UCS4
true
pdbonly
+ false
true
PYTHON2;PYTHON27;UCS4;TRACE;DEBUG
false
full
+ false
true
PYTHON3;PYTHON36;UCS4;TRACE;DEBUG
false
full
+ false
PYTHON2;PYTHON27;UCS2
true
pdbonly
+ false
PYTHON3;PYTHON36;UCS2
true
pdbonly
+ false
true
PYTHON2;PYTHON27;UCS2;TRACE;DEBUG
false
full
+ false
true
PYTHON3;PYTHON36;UCS2;TRACE;DEBUG
false
full
+ false
diff --git a/src/runtime/clrobject.cs b/src/runtime/clrobject.cs
index fb3d0e0d7..502677655 100644
--- a/src/runtime/clrobject.cs
+++ b/src/runtime/clrobject.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Runtime.InteropServices;
namespace Python.Runtime
diff --git a/src/runtime/importhook.cs b/src/runtime/importhook.cs
index bc9ac5eee..4ba68ac9d 100644
--- a/src/runtime/importhook.cs
+++ b/src/runtime/importhook.cs
@@ -76,6 +76,13 @@ internal static void Shutdown()
{
Runtime.XDecref(py_clr_module);
Runtime.XDecref(root.pyHandle);
+
+ // Re-install the original import function
+ IntPtr dict = Runtime.PyImport_GetModuleDict();
+ IntPtr mod = Runtime.IsPython3
+ ? Runtime.PyImport_ImportModule("builtins")
+ : Runtime.PyDict_GetItemString(dict, "__builtin__");
+ Runtime.PyObject_SetAttrString(mod, "__import__", py_import);
Runtime.XDecref(py_import);
}
}
diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs
index a23c7ac79..badeba826 100644
--- a/src/runtime/pythonengine.cs
+++ b/src/runtime/pythonengine.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -145,6 +145,19 @@ public static void Initialize(bool setSysArgv = true)
Initialize(Enumerable.Empty(), setSysArgv: setSysArgv);
}
+ ///
+ /// On Domain Unload Event Handler
+ ///
+ ///
+ /// Performs necessary tasks (shutdown) when the current app domain
+ /// gets unloaded, leaving the engine, the runtime and the Python
+ /// interpreter in consistent states
+ ///
+ private static void OnDomainUnload(object sender, EventArgs e)
+ {
+ Shutdown();
+ }
+
///
/// Initialize Method
///
@@ -158,6 +171,9 @@ public static void Initialize(IEnumerable args, bool setSysArgv = true)
{
if (!initialized)
{
+ // Make sure we shut down properly on app domain reload
+ System.AppDomain.CurrentDomain.DomainUnload += OnDomainUnload;
+
// Creating the delegateManager MUST happen before Runtime.Initialize
// is called. If it happens afterwards, DelegateManager's CodeGenerator
// throws an exception in its ctor. This exception is eaten somehow
diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs
index b4ddc7f7e..d0792476e 100644
--- a/src/runtime/runtime.cs
+++ b/src/runtime/runtime.cs
@@ -5,55 +5,121 @@
namespace Python.Runtime
{
- [SuppressUnmanagedCodeSecurity]
- internal static class NativeMethods
+ internal static class OSType
{
-#if MONO_LINUX || MONO_OSX
-#if NETSTANDARD
- private static int RTLD_NOW = 0x2;
-#if MONO_LINUX
- private static int RTLD_GLOBAL = 0x100;
- private static IntPtr RTLD_DEFAULT = IntPtr.Zero;
- private const string NativeDll = "libdl.so";
- public static IntPtr LoadLibrary(string fileName)
+ public static bool IsLinux
{
- return dlopen($"lib{fileName}.so", RTLD_NOW | RTLD_GLOBAL);
+ get
+ {
+ return RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+ }
}
-#elif MONO_OSX
- private static int RTLD_GLOBAL = 0x8;
- private const string NativeDll = "/usr/lib/libSystem.dylib"
- private static IntPtr RTLD_DEFAULT = new IntPtr(-2);
- public static IntPtr LoadLibrary(string fileName)
+ public static bool IsWindows
{
- return dlopen($"lib{fileName}.dylib", RTLD_NOW | RTLD_GLOBAL);
+ get
+ {
+ return RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ }
}
-#endif
+
+ public static bool IsOSX
+ {
+ get
+ {
+ return RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
+ }
+ }
+ }
+
+ [SuppressUnmanagedCodeSecurity]
+ internal static class NativeMethods
+ {
+ private static IntPtr RTLD_DEFAULT
+ {
+ get
+ {
+ if (OSType.IsWindows)
+ {
+ // calling this does not make sense on Windows
+ throw new Exception();
+ }
+ if (OSType.IsOSX)
+ {
+ return new IntPtr(-2);
+ }
+ return IntPtr.Zero;
+ }
+ }
+
+ private const string linuxNativeDll = "libdl.so";
+
+#if NETSTANDARD
+ private const string osxNativeDLL = "/usr/lib/libSystem.dylib";
#else
+ private const string osxNativeDll = "__Internal";
+#endif
+
private static int RTLD_NOW = 0x2;
+
+ private static int RTLD_GLOBAL
+ {
+ get
+ {
+ if (OSType.IsWindows)
+ {
+ // calling this does not make sense on Windows
+ throw new Exception();
+ }
+ if (OSType.IsOSX)
+ {
+ return 0x8;
+ }
+ return 0x100;
+ }
+ }
+
private static int RTLD_SHARED = 0x20;
-#if MONO_OSX
- private static IntPtr RTLD_DEFAULT = new IntPtr(-2);
- private const string NativeDll = "__Internal";
-#elif MONO_LINUX
- private static IntPtr RTLD_DEFAULT = IntPtr.Zero;
- private const string NativeDll = "libdl.so";
-#endif
public static IntPtr LoadLibrary(string fileName)
{
+ if (OSType.IsWindows)
+ {
+ return LoadLibrary_win(fileName);
+ }
+
+#if NETSTANDARD
+ if (IsOSX)
+ {
+ string file = $"lib{fileName}.dylib";
+ }
+ else
+ {
+ string file = $"lib{fileName}.so";
+ }
+ return dlopen(file, RTLD_NOW | RTLD_GLOBAL);
+#else
return dlopen(fileName, RTLD_NOW | RTLD_SHARED);
- }
#endif
-
+ }
public static void FreeLibrary(IntPtr handle)
{
+ if (OSType.IsWindows)
+ {
+ FreeLibrary_win(handle);
+ return;
+ }
dlclose(handle);
}
public static IntPtr GetProcAddress(IntPtr dllHandle, string name)
{
+ if (OSType.IsWindows)
+ {
+ return GetProcAddress_win(dllHandle, name);
+ }
+
// look in the exe if dllHandle is NULL
if (dllHandle == IntPtr.Zero)
{
@@ -70,30 +136,88 @@ public static IntPtr GetProcAddress(IntPtr dllHandle, string name)
}
return res;
}
+
+ public static IntPtr dlopen(String fileName, int flags)
+ {
+ if (OSType.IsWindows)
+ {
+ // shouldn't be calling this function on Windows
+ throw new Exception();
+ }
+ if (OSType.IsOSX) { return dlopen_mac(fileName, flags); }
+ return dlopen_linux(fileName, flags);
+ }
+
+ private static IntPtr dlsym(IntPtr handle, String symbol)
+ {
+ if (OSType.IsWindows)
+ {
+ // shouldn't be calling this function on Windows
+ throw new Exception();
+ }
+ if (OSType.IsOSX) { return dlsym_mac(handle, symbol); }
+ return dlsym_linux(handle, symbol);
+ }
+
+ private static int dlclose(IntPtr handle)
+ {
+ if (OSType.IsWindows)
+ {
+ // shouldn't be calling this function on Windows
+ throw new Exception();
+ }
+ if (OSType.IsOSX) { return dlclose_mac(handle); }
+ return dlclose_linux(handle);
+ }
+
+ private static IntPtr dlerror()
+ {
+ if (OSType.IsWindows)
+ {
+ // shouldn't be calling this function on Windows
+ throw new Exception();
+ }
+ if (OSType.IsOSX) { return dlerror_mac(); }
+ return dlerror_linux();
+ }
- [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
- public static extern IntPtr dlopen(String fileName, int flags);
+ // ------------- Linux ----------------
+ [DllImport(linuxNativeDll, EntryPoint = "dlopen", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+ public static extern IntPtr dlopen_linux(String fileName, int flags);
- [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
- private static extern IntPtr dlsym(IntPtr handle, String symbol);
+ [DllImport(linuxNativeDll, EntryPoint = "dlsym", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+ private static extern IntPtr dlsym_linux(IntPtr handle, String symbol);
- [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl)]
- private static extern int dlclose(IntPtr handle);
+ [DllImport(linuxNativeDll, EntryPoint = "dlclose", CallingConvention = CallingConvention.Cdecl)]
+ private static extern int dlclose_linux(IntPtr handle);
- [DllImport(NativeDll, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr dlerror();
-#else // Windows
- private const string NativeDll = "kernel32.dll";
+ [DllImport(linuxNativeDll, EntryPoint = "dlerror", CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr dlerror_linux();
- [DllImport(NativeDll)]
- public static extern IntPtr LoadLibrary(string dllToLoad);
+ // ------------- Mac -----------------
+ [DllImport(osxNativeDll, EntryPoint = "dlopen", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+ public static extern IntPtr dlopen_mac(String fileName, int flags);
- [DllImport(NativeDll)]
- public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
+ [DllImport(osxNativeDll, EntryPoint = "dlsym", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+ private static extern IntPtr dlsym_mac(IntPtr handle, String symbol);
- [DllImport(NativeDll)]
- public static extern bool FreeLibrary(IntPtr hModule);
-#endif
+ [DllImport(osxNativeDll, EntryPoint = "dlclose", CallingConvention = CallingConvention.Cdecl)]
+ private static extern int dlclose_mac(IntPtr handle);
+
+ [DllImport(osxNativeDll, EntryPoint = "dlerror", CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr dlerror_mac();
+
+ //#else // Windows
+ private const string winNativeDll = "kernel32.dll";
+
+ [DllImport(winNativeDll, EntryPoint = "LoadLibrary")]
+ public static extern IntPtr LoadLibrary_win(string dllToLoad);
+
+ [DllImport(winNativeDll, EntryPoint = "GetProcAddress")]
+ public static extern IntPtr GetProcAddress_win(IntPtr hModule, string procedureName);
+
+ [DllImport(winNativeDll, EntryPoint = "FreeLibrary")]
+ public static extern bool FreeLibrary_win(IntPtr hModule);
}
///
@@ -154,12 +278,9 @@ public class Runtime
#else
#error You must define one of PYTHON34 to PYTHON37 or PYTHON27
#endif
+ // TODO : ideally find a way to do this without having to rename the dylib on Mac/Linux
+ internal const string dllBase = "Packages/com.unity.scripting.python/Editor/bin/python" + _pyver;
-#if MONO_LINUX || MONO_OSX // Linux/macOS use dotted version string
- internal const string dllBase = "python" + _pyversion;
-#else // Windows
- internal const string dllBase = "python" + _pyver;
-#endif
#if PYTHON_WITH_PYDEBUG
internal const string dllWithPyDebug = "d";
@@ -324,13 +445,14 @@ internal static void Initialize()
dllLocal = NativeMethods.LoadLibrary(_PythonDll);
}
_PyObject_NextNotImplemented = NativeMethods.GetProcAddress(dllLocal, "_PyObject_NextNotImplemented");
-
-#if !(MONO_LINUX || MONO_OSX)
- if (dllLocal != IntPtr.Zero)
+
+ if (!(OSType.IsLinux || OSType.IsOSX))
{
- NativeMethods.FreeLibrary(dllLocal);
+ if (dllLocal != IntPtr.Zero)
+ {
+ NativeMethods.FreeLibrary(dllLocal);
+ }
}
-#endif
// Initialize modules that depend on the runtime class.
AssemblyManager.Initialize();
@@ -354,6 +476,23 @@ internal static void Shutdown()
Exceptions.Shutdown();
ImportHook.Shutdown();
Py_Finalize();
+
+ // Now unload the Python library from memory and load it again, providing a
+ // fresh interpreter. This prevents a crash (exception) on second domain reload
+ // https://stackoverflow.com/questions/2445536/unload-a-dll-loaded-using-dllimport
+ if (_PythonDll != "__Internal")
+ {
+ IntPtr dllLocal = NativeMethods.LoadLibrary(_PythonDll);
+
+ // Twice: a first one for the call to LoadLibrary above,
+ // a second one for the original call to LoadLibrary (should result in unloading the Python library from memory)
+ NativeMethods.FreeLibrary(dllLocal);
+ NativeMethods.FreeLibrary(dllLocal);
+
+ // Here the Python library is supposed to be unloaded.
+ // Load it again in order to get a fresh interpreter
+ NativeMethods.LoadLibrary(_PythonDll);
+ }
}
// called *without* the GIL acquired by clr._AtExit