diff --git a/DllImportGenerator/Ancillary.Interop/Ancillary.Interop.csproj b/DllImportGenerator/Ancillary.Interop/Ancillary.Interop.csproj
index e9063db196b..642e04fe9e5 100644
--- a/DllImportGenerator/Ancillary.Interop/Ancillary.Interop.csproj
+++ b/DllImportGenerator/Ancillary.Interop/Ancillary.Interop.csproj
@@ -5,6 +5,7 @@
8.0
System.Runtime.InteropServices
enable
+ true
diff --git a/DllImportGenerator/Ancillary.Interop/MarshalEx.cs b/DllImportGenerator/Ancillary.Interop/MarshalEx.cs
index 95be96d8685..9c3ad649573 100644
--- a/DllImportGenerator/Ancillary.Interop/MarshalEx.cs
+++ b/DllImportGenerator/Ancillary.Interop/MarshalEx.cs
@@ -25,5 +25,79 @@ public static void SetHandle(SafeHandle safeHandle, IntPtr handle)
{
typeof(SafeHandle).GetMethod("SetHandle", BindingFlags.NonPublic | BindingFlags.Instance)!.Invoke(safeHandle, new object[] { handle });
}
+
+ ///
+ /// Set the last platform invoke error on the thread
+ ///
+ public static void SetLastWin32Error(int error)
+ {
+ typeof(Marshal).GetMethod("SetLastWin32Error", BindingFlags.NonPublic | BindingFlags.Static)!.Invoke(null, new object[] { error });
+ }
+
+ ///
+ /// Get the last system error on the current thread (errno on Unix, GetLastError on Windows)
+ ///
+ public static unsafe int GetLastSystemError()
+ {
+ // Would be internal call that handles getting the last error for the thread using the PAL
+
+ if (OperatingSystem.IsWindows())
+ {
+ return Kernel32.GetLastError();
+ }
+ else if (OperatingSystem.IsMacOS())
+ {
+ return *libc.__error();
+ }
+ else if (OperatingSystem.IsLinux())
+ {
+ return *libc.__errno_location();
+ }
+
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Set the last system error on the current thread (errno on Unix, SetLastError on Windows)
+ ///
+ public static unsafe void SetLastSystemError(int error)
+ {
+ // Would be internal call that handles setting the last error for the thread using the PAL
+
+ if (OperatingSystem.IsWindows())
+ {
+ Kernel32.SetLastError(error);
+ }
+ else if (OperatingSystem.IsMacOS())
+ {
+ *libc.__error() = error;
+ }
+ else if (OperatingSystem.IsLinux())
+ {
+ *libc.__errno_location() = error;
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class Kernel32
+ {
+ [DllImport(nameof(Kernel32))]
+ public static extern void SetLastError(int error);
+
+ [DllImport(nameof(Kernel32))]
+ public static extern int GetLastError();
+ }
+
+ private class libc
+ {
+ [DllImport(nameof(libc))]
+ internal static unsafe extern int* __errno_location();
+
+ [DllImport(nameof(libc))]
+ internal static unsafe extern int* __error();
+ }
}
}
diff --git a/DllImportGenerator/DllImportGenerator.IntegrationTests/SetLastErrorTests.cs b/DllImportGenerator/DllImportGenerator.IntegrationTests/SetLastErrorTests.cs
new file mode 100644
index 00000000000..6533889e2b2
--- /dev/null
+++ b/DllImportGenerator/DllImportGenerator.IntegrationTests/SetLastErrorTests.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Runtime.InteropServices;
+
+using Xunit;
+
+namespace DllImportGenerator.IntegrationTests
+{
+ partial class NativeExportsNE
+ {
+ public partial class SetLastError
+ {
+ [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "set_error", SetLastError = true)]
+ public static partial void SetError(int error, byte shouldSetError);
+
+ [GeneratedDllImport(nameof(NativeExportsNE), EntryPoint = "set_error_return_string", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.LPWStr)]
+ public static partial string SetError_NonBlittableSignature(int error, [MarshalAs(UnmanagedType.U1)] bool shouldSetError, [MarshalAs(UnmanagedType.LPWStr)] string errorString);
+ }
+ }
+
+ public class SetLastErrorTests
+ {
+ [Theory]
+ [InlineData(0)]
+ [InlineData(2)]
+ [InlineData(-5)]
+ public void LastWin32Error_HasExpectedValue(int error)
+ {
+ string errorString = error.ToString();
+ string ret = NativeExportsNE.SetLastError.SetError_NonBlittableSignature(error, shouldSetError: true, errorString);
+ Assert.Equal(error, Marshal.GetLastWin32Error());
+ Assert.Equal(errorString, ret);
+
+ // Clear the last error
+ MarshalEx.SetLastWin32Error(0);
+
+ NativeExportsNE.SetLastError.SetError(error, shouldSetError: 1);
+ Assert.Equal(error, Marshal.GetLastWin32Error());
+ }
+
+ [Fact]
+ public void ClearPreviousError()
+ {
+ int error = 100;
+ MarshalEx.SetLastWin32Error(error);
+
+ // Don't actually set the error in the native call. SetLastError=true should clear any existing error.
+ string errorString = error.ToString();
+ string ret = NativeExportsNE.SetLastError.SetError_NonBlittableSignature(error, shouldSetError: false, errorString);
+ Assert.Equal(0, Marshal.GetLastWin32Error());
+ Assert.Equal(errorString, ret);
+
+ MarshalEx.SetLastWin32Error(error);
+
+ // Don't actually set the error in the native call. SetLastError=true should clear any existing error.
+ NativeExportsNE.SetLastError.SetError(error, shouldSetError: 0);
+ Assert.Equal(0, Marshal.GetLastWin32Error());
+ }
+ }
+}
diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/CompileFails.cs b/DllImportGenerator/DllImportGenerator.UnitTests/CompileFails.cs
index 51464147487..50e430b22de 100644
--- a/DllImportGenerator/DllImportGenerator.UnitTests/CompileFails.cs
+++ b/DllImportGenerator/DllImportGenerator.UnitTests/CompileFails.cs
@@ -41,8 +41,7 @@ public static IEnumerable