diff --git a/Src/IronPython.Modules/_ctypes/_ctypes.cs b/Src/IronPython.Modules/_ctypes/_ctypes.cs index 4191cf01b..c383005fa 100644 --- a/Src/IronPython.Modules/_ctypes/_ctypes.cs +++ b/Src/IronPython.Modules/_ctypes/_ctypes.cs @@ -164,6 +164,8 @@ public static int CopyComPointer(object src, object dest) { throw new NotImplementedException("CopyComPointer"); } +#nullable enable + public static string FormatError() { return FormatError(get_last_error()); } @@ -172,6 +174,15 @@ public static string FormatError(int errorCode) { return new Win32Exception(errorCode).Message; } + private static string FormatError(int errorCode, string? fileName) { + string msg = FormatError(errorCode); + // error codes: https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes + if (errorCode is not (< 0 or >= 8200 or 34 or 106 or 317 or 718)) { + msg = msg.Replace("%1", $"'{fileName}'"); + } + return msg; + } + [SupportedOSPlatform("windows"), PythonHidden(PlatformsAttribute.PlatformFamily.Unix)] public static void FreeLibrary(int handle) { FreeLibrary(new IntPtr(handle)); @@ -187,13 +198,17 @@ public static void FreeLibrary(IntPtr handle) { NativeFunctions.FreeLibrary(handle); } -#nullable enable - private static object LoadDLL(string? library, int mode) { if (library is not null && library.IndexOf((char)0) != -1) throw PythonOps.ValueError("embedded null byte"); IntPtr res = NativeFunctions.LoadDLL(library, mode); if (res == IntPtr.Zero) { - throw PythonOps.OSError($"cannot load library {library}"); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + int code = NativeFunctions.GetLastError(); + string msg = FormatError(code, library); + throw PythonExceptions.CreateThrowable(PythonExceptions.OSError, 0, msg, null, code); + } + + throw PythonOps.OSError("cannot load library '{0}'", library); } return res.ToPython(); diff --git a/Tests/modules/type_related/test_ctypes.py b/Tests/modules/type_related/test_ctypes.py index 2d2fa30d6..1967efdf8 100644 --- a/Tests/modules/type_related/test_ctypes.py +++ b/Tests/modules/type_related/test_ctypes.py @@ -10,8 +10,9 @@ from array import array import sys import gc +import unittest -from iptest import IronPythonTestCase, is_cli, big, myint, run_test +from iptest import IronPythonTestCase, is_posix, is_cli, big, myint, run_test class CTypesTest(IronPythonTestCase): export_error_msg = "Existing exports of data: object cannot be re-sized" if is_cli else "cannot resize an array that is exporting buffers" @@ -219,4 +220,15 @@ class TestU(Structure): self.assertEqual(TestU(-(1 << 64)).x, 0) self.assertEqual(TestU(-(1 << 64) - 1).x, 0x7fffffffffffffff) + @unittest.skipIf(is_posix, 'Windows specific test') + def test_loadlibrary_error(self): + with self.assertRaises(OSError) as cm: + windll.LoadLibrary(__file__) + + self.assertEqual(cm.exception.errno, 8) + self.assertEqual(cm.exception.winerror, 193) + self.assertIn(" is not a valid Win32 application", cm.exception.strerror) + self.assertIsNone(cm.exception.filename) + self.assertIsNone(cm.exception.filename2) + run_test(__name__) diff --git a/WhatsNewInPython30.md b/WhatsNewInPython30.md index d43645eb8..d17f50329 100644 --- a/WhatsNewInPython30.md +++ b/WhatsNewInPython30.md @@ -102,7 +102,7 @@ Changes To Exceptions - [x] [PEP 3110][]: Catching exceptions. You must now use `except SomeException as variable` instead of `except SomeException, variable`. Moreover, the variable is explicitly deleted when the `except` block is left. - [x] [PEP 3134][]: Exception chaining. There are two cases: implicit chaining and explicit chaining. Implicit chaining happens when an exception is raised in an `except` or `finally` handler block. This usually happens due to a bug in the handler block; we call this a secondary exception. In this case, the original exception (that was being handled) is saved as the `__context__` attribute of the secondary exception. Explicit chaining is invoked with this syntax: `raise SecondaryException() from primary_exception` (where `primary_exception` is any expression that produces an exception object, probably an exception that was previously caught). In this case, the primary exception is stored on the `__cause__` attribute of the secondary exception. The traceback printed when an unhandled exception occurs walks the chain of `__cause__` and `__context__` attributes and prints a separate traceback for each component of the chain, with the primary exception at the top. (Java users may recognize this behavior.) - [x] [PEP 3134][]: Exception objects now store their traceback as the `__traceback__` attribute. This means that an exception object now contains all the information pertaining to an exception, and there are fewer reasons to use `sys.exc_info()` (though the latter is not removed). -- [ ] A few exception messages are improved when Windows fails to load an extension module. For example, error code 193 is now `%1 is not a valid Win32 application`. Strings now deal with non-English locales. +- [x] A few exception messages are improved when Windows fails to load an extension module. For example, error code 193 is now `%1 is not a valid Win32 application`. Strings now deal with non-English locales. Operators And Special Methods ===============