diff --git a/src/Common/src/Interop/Unix/System.Private.CoreLib.Native/Interop.Environment.cs b/src/Common/src/Interop/Unix/System.Private.CoreLib.Native/Interop.Environment.cs index 040d1ba901c..b429c01c15a 100644 --- a/src/Common/src/Interop/Unix/System.Private.CoreLib.Native/Interop.Environment.cs +++ b/src/Common/src/Interop/Unix/System.Private.CoreLib.Native/Interop.Environment.cs @@ -7,14 +7,9 @@ internal static partial class Interop { - private static class Libraries - { - internal const string SystemPrivateCoreLibNative = "System.Private.CoreLib.Native"; - } - internal unsafe partial class Sys { - [DllImport(Libraries.SystemPrivateCoreLibNative)] + [DllImport(Interop.Libraries.SystemPrivateCoreLibNative)] internal static unsafe extern int GetEnvironmentVariable(byte* name, out IntPtr result); } } diff --git a/src/Common/src/Interop/Unix/System.Private.CoreLib.Native/Interop.Libraries.cs b/src/Common/src/Interop/Unix/System.Private.CoreLib.Native/Interop.Libraries.cs new file mode 100644 index 00000000000..0737bd1df41 --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Private.CoreLib.Native/Interop.Libraries.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +internal static partial class Interop +{ + internal static class Libraries + { + internal const string SystemPrivateCoreLibNative = "System.Private.CoreLib.Native"; + } +} diff --git a/src/Common/src/Interop/Unix/System.Private.CoreLib.Native/Interop.Write.cs b/src/Common/src/Interop/Unix/System.Private.CoreLib.Native/Interop.Write.cs new file mode 100644 index 00000000000..cab0a2f3b72 --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Private.CoreLib.Native/Interop.Write.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + /// + /// Writes the specified buffer to the provided open file descriptor + /// + /// The file descriptor to try and write to + /// The data to attempt to write + /// The amount of data to write, in bytes + /// + /// Returns the number of bytes written on success; otherwise, returns -1 and sets errno + /// + [DllImport(Interop.Libraries.SystemPrivateCoreLibNative, SetLastError = true)] + internal static unsafe extern int Write2(int fd, byte* buffer, int bufferSize); + } +} diff --git a/src/Common/src/Interop/Unix/libc/Interop.FileDescriptors.cs b/src/Common/src/Interop/Unix/libc/Interop.FileDescriptors.cs new file mode 100644 index 00000000000..4eeec2ddbf9 --- /dev/null +++ b/src/Common/src/Interop/Unix/libc/Interop.FileDescriptors.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +internal static partial class Interop +{ + internal static partial class Sys + { + internal static class FileDescriptors + { + internal const int STDIN_FILENO = 0; + internal const int STDOUT_FILENO = 1; + internal const int STDERR_FILENO = 2; + } + } +} diff --git a/src/Common/src/Interop/Windows/mincore/Interop.Console.cs b/src/Common/src/Interop/Windows/mincore/Interop.Console.cs new file mode 100644 index 00000000000..1e4526a9011 --- /dev/null +++ b/src/Common/src/Interop/Windows/mincore/Interop.Console.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + private static class Libraries + { + internal const string Process = "api-ms-win-core-processenvironment-l1-1-0.dll"; + internal const string Console = "api-ms-win-core-console-l1-1-0.dll"; + } + + internal static unsafe partial class mincore + { + [DllImport("Libraries.Process")] + internal static extern IntPtr GetStdHandle(int nStdHandle); + + [DllImport("Libraries.Console", EntryPoint = "WriteConsoleW")] + internal static unsafe extern bool WriteConsole(IntPtr hConsoleOutput, byte* lpBuffer, int nNumberOfCharsToWrite, out int lpNumberOfCharsWritten, IntPtr lpReservedMustBeNull); + } +} diff --git a/src/Common/src/Interop/Windows/mincore/Interop.HandleTypes.cs b/src/Common/src/Interop/Windows/mincore/Interop.HandleTypes.cs new file mode 100644 index 00000000000..4076d0e3eb5 --- /dev/null +++ b/src/Common/src/Interop/Windows/mincore/Interop.HandleTypes.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +internal partial class Interop +{ + internal partial class mincore + { + internal partial class HandleTypes + { + internal const int INVALID_HANDLE_VALUE = -1; + internal const int STD_ERROR_HANDLE = -12; + } + } +} diff --git a/src/Native/Common/pal_utilities.h b/src/Native/Common/pal_utilities.h new file mode 100644 index 00000000000..e78f68d73a9 --- /dev/null +++ b/src/Native/Common/pal_utilities.h @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include +#include +#include +#include +#include + +/** +* Cast a positive value typed as a signed integer to the +* appropriately sized unsigned integer type. +* +* We use this when we've already ensured that the value is positive, +* but we don't want to cast to a specific unsigned type as that could +* inadvertently defeat the compiler's narrowing conversion warnings +* (which we treat as error). +*/ +template +inline typename std::make_unsigned::type UnsignedCast(T value) +{ + assert(value >= 0); + return static_cast::type>(value); +} + diff --git a/src/Native/System.Private.CoreLib.Native/CMakeLists.txt b/src/Native/System.Private.CoreLib.Native/CMakeLists.txt index b76b3964a80..cfd25e88964 100644 --- a/src/Native/System.Private.CoreLib.Native/CMakeLists.txt +++ b/src/Native/System.Private.CoreLib.Native/CMakeLists.txt @@ -3,6 +3,7 @@ project(System.Private.CoreLib.Native) set(NATIVE_SOURCES pal_environment.cpp pal_memory.cpp + pal_io.cpp ) add_library(System.Private.CoreLib.Native diff --git a/src/Native/System.Private.CoreLib.Native/pal_io.cpp b/src/Native/System.Private.CoreLib.Native/pal_io.cpp new file mode 100644 index 00000000000..04fed95e131 --- /dev/null +++ b/src/Native/System.Private.CoreLib.Native/pal_io.cpp @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include +#include +#include +#include +#include +#include +#include "pal_utilities.h" + +extern "C" int32_t Write2(int32_t fd, const void* buffer, int32_t bufferSize) +{ + assert(buffer != nullptr || bufferSize == 0); + assert(bufferSize >= 0); + + if (bufferSize < 0) + { + errno = ERANGE; + return -1; + } + + ssize_t count = write(fd, buffer, UnsignedCast(bufferSize)); + assert(count >= -1 && count <= bufferSize); + return static_cast(count); +} + diff --git a/src/Native/configure.cmake b/src/Native/configure.cmake index 81fc4eeccc9..bb318957174 100644 --- a/src/Native/configure.cmake +++ b/src/Native/configure.cmake @@ -3,6 +3,8 @@ include(CheckStructHasMember) include(CheckCXXSourceCompiles) include(CheckCXXSourceRuns) +add_compile_options(-I${CMAKE_CURRENT_SOURCE_DIR}/Common) + if (NOT WIN32) include_directories(SYSTEM /usr/local/include) endif () diff --git a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 64a2f5d8711..02370513c25 100644 --- a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -480,7 +480,7 @@ - + @@ -492,7 +492,7 @@ - + @@ -500,6 +500,7 @@ + diff --git a/src/System.Private.DeveloperExperience.Console/src/Internal/DeveloperExperience/ConsolePal.Unix.cs b/src/System.Private.DeveloperExperience.Console/src/Internal/DeveloperExperience/ConsolePal.Unix.cs new file mode 100644 index 00000000000..2be4652d6cc --- /dev/null +++ b/src/System.Private.DeveloperExperience.Console/src/Internal/DeveloperExperience/ConsolePal.Unix.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Internal.DeveloperExperience +{ + internal static class ConsolePal + { + internal unsafe static void WriteError(string errorMessage) + { + byte[] errorMessageAsBytes = Interop.StringHelper.GetBytesFromUTF8string(errorMessage); + fixed (byte* pBuffer = errorMessageAsBytes) + { + Interop.Sys.Write2(Interop.Sys.FileDescriptors.STDERR_FILENO, pBuffer, errorMessageAsBytes.Length); + } + + // Write new line + byte newLine = (byte) '\n'; + Interop.Sys.Write2(Interop.Sys.FileDescriptors.STDERR_FILENO, &newLine, 1); + + } + } +} \ No newline at end of file diff --git a/src/System.Private.DeveloperExperience.Console/src/Internal/DeveloperExperience/ConsolePal.Windows.cs b/src/System.Private.DeveloperExperience.Console/src/Internal/DeveloperExperience/ConsolePal.Windows.cs new file mode 100644 index 00000000000..f9ed5eacb77 --- /dev/null +++ b/src/System.Private.DeveloperExperience.Console/src/Internal/DeveloperExperience/ConsolePal.Windows.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Internal.DeveloperExperience +{ + internal static class ConsolePal + { + private static IntPtr s_consoleErrorHandle = Interop.mincore.GetStdHandle(Interop.mincore.HandleTypes.STD_ERROR_HANDLE); + + internal unsafe static void WriteError(string errorMessage) + { + if (s_consoleErrorHandle == new IntPtr(Interop.mincore.HandleTypes.INVALID_HANDLE_VALUE)) + { + // ensure we have a valid handle before writing to it + return; + } + + fixed (char *pBuffer = errorMessage) + { + int numberOfCharsWritten; + Interop.mincore.WriteConsole(s_consoleErrorHandle, (byte*)pBuffer, errorMessage.Length, out numberOfCharsWritten, IntPtr.Zero); + } + + // Write new line + fixed (char* pBuffer = "\r\n") + { + int numberOfCharsWritten; + Interop.mincore.WriteConsole(s_consoleErrorHandle, (byte*)pBuffer, 2, out numberOfCharsWritten, IntPtr.Zero); + } + } + } +} \ No newline at end of file diff --git a/src/System.Private.DeveloperExperience.Console/src/Internal/DeveloperExperience/DeveloperExperienceConnector.cs b/src/System.Private.DeveloperExperience.Console/src/Internal/DeveloperExperience/DeveloperExperienceConnector.cs new file mode 100644 index 00000000000..c1aa191357b --- /dev/null +++ b/src/System.Private.DeveloperExperience.Console/src/Internal/DeveloperExperience/DeveloperExperienceConnector.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using global::System; +using global::System.Diagnostics; + +namespace Internal.DeveloperExperience +{ + public static class DeveloperExperienceConnectorConsole + { + // This method is targeted by the MainMethodInjector transform. This method exists in various DeveloperExperience.*.dll's. + // The MainMethodInjector chooses an appropriate one (or none) depending on the build configuration and app structure. + // + // The Console DeveloperExperience is chosen if the main exe has a reference to System.Console. This is for internal Microsoft use only. + // It should remain small enough that we don't bother shutting it off on retail builds. + public static void Initialize() + { + DeveloperExperience.Default = new DeveloperExperienceConsole(); + return; + } + } +} + + diff --git a/src/System.Private.DeveloperExperience.Console/src/Internal/DeveloperExperience/DeveloperExperienceConsole.cs b/src/System.Private.DeveloperExperience.Console/src/Internal/DeveloperExperience/DeveloperExperienceConsole.cs new file mode 100644 index 00000000000..17121740b18 --- /dev/null +++ b/src/System.Private.DeveloperExperience.Console/src/Internal/DeveloperExperience/DeveloperExperienceConsole.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using global::System; +using global::System.Diagnostics; +using global::Internal.StackTraceGenerator; + +namespace Internal.DeveloperExperience +{ + internal sealed class DeveloperExperienceConsole : DeveloperExperience + { + public sealed override void WriteLine(String s) + { + ConsolePal.WriteError(s); + } + + public sealed override String CreateStackTraceString(IntPtr ip, bool includeFileInfo) + { + String s = Internal.StackTraceGenerator.StackTraceGenerator.CreateStackTraceString(ip, includeFileInfo); + if (s != null) + return s; + return base.CreateStackTraceString(ip, includeFileInfo); + } + + public sealed override void TryGetSourceLineInfo(IntPtr ip, out string fileName, out int lineNumber, out int columnNumber) + { + Internal.StackTraceGenerator.StackTraceGenerator.TryGetSourceLineInfo(ip, out fileName, out lineNumber, out columnNumber); + // we take whatever data StackTraceGenerator can get (none/partial/all). No reason to fall-back because the base-type + // never finds anything. + } + } +} + diff --git a/src/System.Private.DeveloperExperience.Console/src/System.Private.DeveloperExperience.Console.csproj b/src/System.Private.DeveloperExperience.Console/src/System.Private.DeveloperExperience.Console.csproj new file mode 100644 index 00000000000..3518511d02d --- /dev/null +++ b/src/System.Private.DeveloperExperience.Console/src/System.Private.DeveloperExperience.Console.csproj @@ -0,0 +1,71 @@ + + + + + System.Private.DeveloperExperience.Console + Library + {F9EF39E7-C8E4-4776-A952-FEF7A1FC2D3B} + true + + v4.5 + Portable + .NETPortable + Profile7 + .NET Portable Subset + false + + + + + x86 + + + x86 + + + x64 + + + x64 + + + arm + + + arm + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + +