From 8033c04b7991ddc8ed83306d27b55bce512a926c Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Tue, 28 Sep 2021 20:40:00 -0700 Subject: [PATCH] raise BadPythonDllException (internal, derived from MissingMethodException) instead of confusing TypeLoadException when PythonDLL was not configured properly --- README.rst | 9 ++-- src/embed_tests/TestPythonEngineProperties.cs | 2 +- src/runtime/platform/LibraryLoader.cs | 4 +- src/runtime/pythonengine.cs | 18 ++++--- src/runtime/runtime.cs | 48 +++++++++++++++---- 5 files changed, 60 insertions(+), 21 deletions(-) diff --git a/README.rst b/README.rst index 996bfab27..c0e4229d3 100644 --- a/README.rst +++ b/README.rst @@ -45,10 +45,11 @@ module: Embedding Python in .NET ------------------------ -- You must set `Runtime.PythonDLL` property or `PYTHONNET_PYDLL` environment variable - starting with version 3.0, otherwise you will receive `TypeInitializationException`. - Typical values are `python38.dll` (Windows), `libpython3.8.dylib` (Mac), - `libpython3.8.so` (most other *nix). +- You must set ``Runtime.PythonDLL`` property or ``PYTHONNET_PYDLL`` environment variable + starting with version 3.0, otherwise you will receive ``BadPythonDllException`` + (internal, derived from ``MissingMethodException``) upon calling ``Initialize``. + Typical values are ``python38.dll`` (Windows), ``libpython3.8.dylib`` (Mac), + ``libpython3.8.so`` (most other *nix). - All calls to python should be inside a ``using (Py.GIL()) {/* Your code here */}`` block. - Import python modules using ``dynamic mod = Py.Import("mod")``, then diff --git a/src/embed_tests/TestPythonEngineProperties.cs b/src/embed_tests/TestPythonEngineProperties.cs index 626e3c77f..ca9164a1d 100644 --- a/src/embed_tests/TestPythonEngineProperties.cs +++ b/src/embed_tests/TestPythonEngineProperties.cs @@ -81,7 +81,7 @@ public static void GetPythonPathDefault() public static void GetProgramNameDefault() { PythonEngine.Initialize(); - string s = PythonEngine.PythonHome; + string s = PythonEngine.ProgramName; Assert.NotNull(s); PythonEngine.Shutdown(); diff --git a/src/runtime/platform/LibraryLoader.cs b/src/runtime/platform/LibraryLoader.cs index e361f87e4..78bf48112 100644 --- a/src/runtime/platform/LibraryLoader.cs +++ b/src/runtime/platform/LibraryLoader.cs @@ -111,7 +111,7 @@ public IntPtr Load(string dllToLoad) { var res = WindowsLoader.LoadLibrary(dllToLoad); if (res == IntPtr.Zero) - throw new DllNotFoundException($"Could not load {dllToLoad}", new Win32Exception()); + throw new DllNotFoundException($"Could not load {dllToLoad}.", new Win32Exception()); return res; } @@ -128,7 +128,7 @@ public IntPtr GetFunction(IntPtr hModule, string procedureName) var res = WindowsLoader.GetProcAddress(hModule, procedureName); if (res == IntPtr.Zero) - throw new MissingMethodException($"Failed to load symbol {procedureName}", new Win32Exception()); + throw new MissingMethodException($"Failed to load symbol {procedureName}.", new Win32Exception()); return res; } diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index d7322dcc2..ece70c485 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -86,13 +86,15 @@ public static string ProgramName { get { - IntPtr p = Runtime.Py_GetProgramName(); + IntPtr p = Runtime.TryUsingDll(() => Runtime.Py_GetProgramName()); return UcsMarshaler.PtrToPy3UnicodePy2String(p) ?? ""; } set { Marshal.FreeHGlobal(_programName); - _programName = UcsMarshaler.Py3UnicodePy2StringtoPtr(value); + _programName = Runtime.TryUsingDll( + () => UcsMarshaler.Py3UnicodePy2StringtoPtr(value) + ); Runtime.Py_SetProgramName(_programName); } } @@ -101,14 +103,16 @@ public static string PythonHome { get { - IntPtr p = Runtime.Py_GetPythonHome(); + IntPtr p = Runtime.TryUsingDll(() => Runtime.Py_GetPythonHome()); return UcsMarshaler.PtrToPy3UnicodePy2String(p) ?? ""; } set { // this value is null in the beginning Marshal.FreeHGlobal(_pythonHome); - _pythonHome = UcsMarshaler.Py3UnicodePy2StringtoPtr(value); + _pythonHome = Runtime.TryUsingDll( + () => UcsMarshaler.Py3UnicodePy2StringtoPtr(value) + ); Runtime.Py_SetPythonHome(_pythonHome); } } @@ -117,13 +121,15 @@ public static string PythonPath { get { - IntPtr p = Runtime.Py_GetPath(); + IntPtr p = Runtime.TryUsingDll(() => Runtime.Py_GetPath()); return UcsMarshaler.PtrToPy3UnicodePy2String(p) ?? ""; } set { Marshal.FreeHGlobal(_pythonPath); - _pythonPath = UcsMarshaler.Py3UnicodePy2StringtoPtr(value); + _pythonPath = Runtime.TryUsingDll( + () => UcsMarshaler.Py3UnicodePy2StringtoPtr(value) + ); Runtime.Py_SetPath(_pythonPath); } } diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 318c7b794..a7420f946 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -93,7 +93,6 @@ internal static Version PyVersion } } - /// /// Initialize the runtime... /// @@ -113,7 +112,10 @@ internal static void Initialize(bool initSigs = false, ShutdownMode mode = Shutd } ShutdownMode = mode; - if (Py_IsInitialized() == 0) + bool interpreterAlreadyInitialized = TryUsingDll( + () => Py_IsInitialized() != 0 + ); + if (!interpreterAlreadyInitialized) { Py_InitializeEx(initSigs ? 1 : 0); if (PyEval_ThreadsInitialized() == 0) @@ -787,6 +789,34 @@ internal static unsafe long Refcount(IntPtr op) return *p; } + /// + /// Call specified function, and handle PythonDLL-related failures. + /// + internal static T TryUsingDll(Func op) + { + try + { + return op(); + } + catch (TypeInitializationException loadFailure) + { + var delegatesLoadFailure = loadFailure; + // failure to load Delegates type might have been the cause + // of failure to load some higher-level type + while (delegatesLoadFailure.InnerException is TypeInitializationException nested) + { + delegatesLoadFailure = nested; + } + + if (delegatesLoadFailure.InnerException is BadPythonDllException badDll) + { + throw badDll; + } + + throw; + } + } + /// /// Export of Macro Py_XIncRef. Use XIncref instead. /// Limit this function usage for Testing and Py_Debug builds @@ -2270,10 +2300,6 @@ internal static void SetNoSiteFlag() if (_PythonDll != "__Internal") { dllLocal = loader.Load(_PythonDll); - if (dllLocal == IntPtr.Zero) - { - throw new Exception($"Cannot load {_PythonDll}"); - } } try { @@ -2617,8 +2643,8 @@ static Delegates() } catch (MissingMethodException e) when (libraryHandle == IntPtr.Zero) { - throw new MissingMethodException( - "Did you forget to set Runtime.PythonDLL?" + + throw new BadPythonDllException( + "Runtime.PythonDLL was not set or does not point to a supported Python runtime DLL." + " See https://github.com/pythonnet/pythonnet#embedding-python-in-net", e); } @@ -2889,6 +2915,12 @@ static Delegates() } } + internal class BadPythonDllException : MissingMethodException + { + public BadPythonDllException(string message, Exception innerException) + : base(message, innerException) { } + } + public enum ShutdownMode {