From 10a2f8f661bcf616bce5f132db8603dd4b235a13 Mon Sep 17 00:00:00 2001 From: Tony Roberts Date: Thu, 10 Apr 2014 13:50:57 +0100 Subject: [PATCH 1/5] Reference the assembly loader in the Main method to stop it being optimized away, and don't reload assemblies once they're already loaded. --- pythonnet/src/console/pythonconsole.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pythonnet/src/console/pythonconsole.cs b/pythonnet/src/console/pythonconsole.cs index 2184c8730..6c54c471e 100644 --- a/pythonnet/src/console/pythonconsole.cs +++ b/pythonnet/src/console/pythonconsole.cs @@ -9,6 +9,7 @@ using System; using System.Reflection; +using System.Collections.Generic; using Python.Runtime; namespace Python.Runtime { @@ -19,6 +20,9 @@ private PythonConsole() {} [STAThread] public static int Main(string[] args) { + // reference the static assemblyLoader to stop it being optimized away + AssemblyLoader a = assemblyLoader; + string [] cmd = Environment.GetCommandLineArgs(); PythonEngine.Initialize(); @@ -31,16 +35,26 @@ public static int Main(string[] args) { // Register a callback function to load embedded assmeblies. // (Python.Runtime.dll is included as a resource) private sealed class AssemblyLoader { + Dictionary loadedAssemblies; + public AssemblyLoader() { + loadedAssemblies = new Dictionary(); + AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { String resourceName = new AssemblyName(args.Name).Name + ".dll"; + if (loadedAssemblies.ContainsKey(resourceName)) { + return loadedAssemblies[resourceName]; + } + // looks for the assembly from the resources and load it using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) { if (stream != null) { Byte[] assemblyData = new Byte[stream.Length]; stream.Read(assemblyData, 0, assemblyData.Length); - return Assembly.Load(assemblyData); + Assembly assembly = Assembly.Load(assemblyData); + loadedAssemblies[resourceName] = assembly; + return assembly; } } From 2b11631a5cc9c08b0ff3359d041ee5ade1fc605f Mon Sep 17 00:00:00 2001 From: Tony Roberts Date: Fri, 11 Apr 2014 11:01:37 +0100 Subject: [PATCH 2/5] make sure the GIL is released in ThreadTest --- pythonnet/src/testing/threadtest.cs | 54 +++++++++++++++++------------ 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/pythonnet/src/testing/threadtest.cs b/pythonnet/src/testing/threadtest.cs index a31fd9768..caad9fcf4 100644 --- a/pythonnet/src/testing/threadtest.cs +++ b/pythonnet/src/testing/threadtest.cs @@ -39,35 +39,43 @@ public class ThreadTest { public static string CallEchoString(string arg) { IntPtr gs = PythonEngine.AcquireLock(); - if (module == null) { - module = PythonEngine.ModuleFromString("tt", testmod); + try { + if (module == null) { + module = PythonEngine.ModuleFromString("tt", testmod); + } + PyObject func = module.GetAttr("echostring"); + PyString parg = new PyString(arg); + PyObject temp = func.Invoke(parg); + string result = (string)temp.AsManagedObject(typeof(String)); + func.Dispose(); + parg.Dispose(); + temp.Dispose(); + return result; + } + finally { + PythonEngine.ReleaseLock(gs); } - PyObject func = module.GetAttr("echostring"); - PyString parg = new PyString(arg); - PyObject temp = func.Invoke(parg); - string result = (string)temp.AsManagedObject(typeof(String)); - func.Dispose(); - parg.Dispose(); - temp.Dispose(); - PythonEngine.ReleaseLock(gs); - return result; } public static string CallEchoString2(string arg) { IntPtr gs = PythonEngine.AcquireLock(); - if (module == null) { - module = PythonEngine.ModuleFromString("tt", testmod); - } + try { + if (module == null) { + module = PythonEngine.ModuleFromString("tt", testmod); + } - PyObject func = module.GetAttr("echostring2"); - PyString parg = new PyString(arg); - PyObject temp = func.Invoke(parg); - string result = (string)temp.AsManagedObject(typeof(String)); - func.Dispose(); - parg.Dispose(); - temp.Dispose(); - PythonEngine.ReleaseLock(gs); - return result; + PyObject func = module.GetAttr("echostring2"); + PyString parg = new PyString(arg); + PyObject temp = func.Invoke(parg); + string result = (string)temp.AsManagedObject(typeof(String)); + func.Dispose(); + parg.Dispose(); + temp.Dispose(); + return result; + } + finally { + PythonEngine.ReleaseLock(gs); + } } From dfdcfe7ed082ea3249a4fc72242c39abc9026506 Mon Sep 17 00:00:00 2001 From: Tony Roberts Date: Fri, 11 Apr 2014 11:37:10 +0100 Subject: [PATCH 3/5] Use the assembly short name to find the embedded Python.Runtime (other assemblies may reference it with the full name). --- pythonnet/src/console/pythonconsole.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pythonnet/src/console/pythonconsole.cs b/pythonnet/src/console/pythonconsole.cs index 6c54c471e..26fc756d5 100644 --- a/pythonnet/src/console/pythonconsole.cs +++ b/pythonnet/src/console/pythonconsole.cs @@ -41,7 +41,8 @@ public AssemblyLoader() { loadedAssemblies = new Dictionary(); AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { - String resourceName = new AssemblyName(args.Name).Name + ".dll"; + string shortName = args.Name.Split(',')[0]; + String resourceName = shortName + ".dll"; if (loadedAssemblies.ContainsKey(resourceName)) { return loadedAssemblies[resourceName]; From b65fa30c7b21cce263ed9ada982bd47bc18eea04 Mon Sep 17 00:00:00 2001 From: Tony Roberts Date: Fri, 11 Apr 2014 14:37:19 +0100 Subject: [PATCH 4/5] Use Assembly.Load(Byte[]) instead of Assembly.LoadFrom. --- pythonnet/src/runtime/assemblymanager.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pythonnet/src/runtime/assemblymanager.cs b/pythonnet/src/runtime/assemblymanager.cs index b07cde1c2..291301466 100644 --- a/pythonnet/src/runtime/assemblymanager.cs +++ b/pythonnet/src/runtime/assemblymanager.cs @@ -30,6 +30,7 @@ internal class AssemblyManager { static ResolveEventHandler rhandler; static Dictionary probed; static List assemblies; + static Dictionary loadedAssemblies; internal static List pypath; private AssemblyManager() {} @@ -46,6 +47,7 @@ internal static void Initialize() { probed = new Dictionary(32); //generics = new Dictionary>(); assemblies = new List(16); + loadedAssemblies = new Dictionary(); pypath = new List(16); AppDomain domain = AppDomain.CurrentDomain; @@ -202,7 +204,19 @@ public static Assembly LoadAssemblyPath(string name) { string path = FindAssembly(name); Assembly assembly = null; if (path != null) { - try { assembly = Assembly.LoadFrom(path); } + if (loadedAssemblies.ContainsKey(path)) { + return loadedAssemblies[path]; + } + // Avoid using Assembly.LoadFrom as referenced assemblies that exist + // in the same path will be loaded directly from there, rather than + // using other versions already loaded. This is a problem if there + // is a Python.Runtime.dll in the same folder as the assembly being + // loaded, as that will result in two instances being loaded. + try { + byte[] bytes = System.IO.File.ReadAllBytes(path); + assembly = Assembly.Load(bytes); + loadedAssemblies[path] = assembly; + } catch {} } return assembly; From 56bebfab89f72f0177e93e45a356c944da15327f Mon Sep 17 00:00:00 2001 From: Tony Roberts Date: Fri, 11 Apr 2014 15:09:19 +0100 Subject: [PATCH 5/5] add C:\Python to PATH --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index b20604dce..6bf286fbd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,6 +16,7 @@ install: - ps: (new-object net.webclient).DownloadFile('https://raw.github.com/pypa/pip/master/contrib/get-pip.py', 'C:\get-pip.py') # appveyor has python 2.7.6 x86 preinstalled, but in the wrong directory, this works around this - ps: if ($env:pythonurl -eq "http://www.python.org/ftp/python/2.7.6/python-2.7.6.msi") {mi c:\python27 c:\python} + - set PATH=C:\Python;%PATH% - C:\Python\python.exe c:\get-pip.py - C:\Python\Scripts\pip.exe install wheel