diff --git a/src/coreclr/System.Private.CoreLib/src/System/Environment.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Environment.CoreCLR.cs index ce4ab04249aaa..150348ec16ab5 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Environment.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Environment.CoreCLR.cs @@ -61,9 +61,6 @@ public static extern int ExitCode [MethodImpl(MethodImplOptions.InternalCall)] public static extern void FailFast(string? message, Exception? exception, string? errorMessage); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string[] GetCommandLineArgsNative(); - public static string[] GetCommandLineArgs() { // There are multiple entry points to a hosted app. The host could diff --git a/src/coreclr/classlibnative/bcltype/system.cpp b/src/coreclr/classlibnative/bcltype/system.cpp index 2d517279fad6f..2f1387ddd2fcf 100644 --- a/src/coreclr/classlibnative/bcltype/system.cpp +++ b/src/coreclr/classlibnative/bcltype/system.cpp @@ -90,43 +90,6 @@ FCIMPL0(INT32, SystemNative::GetExitCode) } FCIMPLEND -FCIMPL0(Object*, SystemNative::GetCommandLineArgs) -{ - FCALL_CONTRACT; - - PTRARRAYREF strArray = NULL; - - HELPER_METHOD_FRAME_BEGIN_RET_1(strArray); - - LPWSTR commandLine; - - commandLine = WszGetCommandLine(); - if (commandLine==NULL) - COMPlusThrowOM(); - - DWORD numArgs = 0; - LPWSTR* argv = SegmentCommandLine(commandLine, &numArgs); - if (!argv) - COMPlusThrowOM(); - - _ASSERTE(numArgs > 0); - - strArray = (PTRARRAYREF) AllocateObjectArray(numArgs, g_pStringClass); - // Copy each argument into new Strings. - for(unsigned int i=0; iGetDataPtr())) + i; - SetObjectReference((OBJECTREF*)destData, (OBJECTREF)str); - } - delete [] argv; - - HELPER_METHOD_FRAME_END(); - - return OBJECTREFToObject(strArray); -} -FCIMPLEND - // Return a method info for the method were the exception was thrown FCIMPL1(ReflectMethodObject*, SystemNative::GetMethodFromStackTrace, ArrayBase* pStackTraceUNSAFE) { diff --git a/src/coreclr/classlibnative/bcltype/system.h b/src/coreclr/classlibnative/bcltype/system.h index 27e772be2af07..b4a773a847c39 100644 --- a/src/coreclr/classlibnative/bcltype/system.h +++ b/src/coreclr/classlibnative/bcltype/system.h @@ -43,7 +43,6 @@ class SystemNative static FCDECL1(VOID,SetExitCode,INT32 exitcode); static FCDECL0(INT32, GetExitCode); - static FCDECL0(Object*, GetCommandLineArgs); static FCDECL1(VOID, FailFast, StringObject* refMessageUNSAFE); static FCDECL2(VOID, FailFastWithExitCode, StringObject* refMessageUNSAFE, UINT exitCode); static FCDECL2(VOID, FailFastWithException, StringObject* refMessageUNSAFE, ExceptionObject* refExceptionUNSAFE); diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 7262605554832..d2c3822f9de83 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -103,7 +103,6 @@ FCFuncStart(gEnvironmentFuncs) FCFuncElement("get_TickCount64", SystemNative::GetTickCount64) FCFuncElement("set_ExitCode", SystemNative::SetExitCode) FCFuncElement("get_ExitCode", SystemNative::GetExitCode) - FCFuncElement("GetCommandLineArgsNative", SystemNative::GetCommandLineArgs) FCFuncElementSig("FailFast", &gsig_SM_Str_RetVoid, SystemNative::FailFast) FCFuncElementSig("FailFast", &gsig_SM_Str_Exception_RetVoid, SystemNative::FailFastWithException) diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetCommandLine.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetCommandLine.cs new file mode 100644 index 0000000000000..748967ff53344 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetCommandLine.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +internal static unsafe partial class Interop +{ + internal static partial class Kernel32 + { + [LibraryImport(Libraries.Kernel32, EntryPoint = "GetCommandLineW")] + internal static partial char* GetCommandLine(); + } +} diff --git a/src/libraries/Common/src/Interop/Windows/Shell32/Interop.CommandLineToArgv.cs b/src/libraries/Common/src/Interop/Windows/Shell32/Interop.CommandLineToArgv.cs new file mode 100644 index 0000000000000..ecc73f56005e0 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Shell32/Interop.CommandLineToArgv.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +internal static unsafe partial class Interop +{ + internal static partial class Shell32 + { + [LibraryImport(Libraries.Shell32, EntryPoint = "CommandLineToArgvW")] + internal static partial char** CommandLineToArgv(char* lpCommandLine, int* pNumArgs); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index d0040d5442769..1154e3ccab4b3 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1558,6 +1558,9 @@ Common\Interop\Windows\Kernel32\Interop.GET_FILEEX_INFO_LEVELS.cs + + Common\Interop\Windows\Kernel32\Interop.GetCommandLine.cs + Common\Interop\Windows\Kernel32\Interop.GetComputerName.cs @@ -1822,6 +1825,9 @@ Common\Interop\Windows\Secur32\Interop.GetUserNameExW.cs + + Common\Interop\Windows\Shell32\Interop.CommandLineToArgv.cs + Common\Interop\Windows\Shell32\Interop.SHGetKnownFolderPath.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs index e1ecca767122d..046a8ab06dbed 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs @@ -31,5 +31,13 @@ public static string MachineName [MethodImplAttribute(MethodImplOptions.NoInlining)] // Avoid inlining PInvoke frame into the hot path private static string? GetProcessPath() => Interop.Sys.GetProcessPath(); + + private static string[] GetCommandLineArgsNative() + { + // This is only used for delegate created from native host + + // Consider to use /proc/self/cmdline to get command line + return Array.Empty(); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs index 7ac8ecd5bc1d2..e95f41a312bea 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -180,5 +181,32 @@ public static unsafe long WorkingSet return (long)memoryCounters.WorkingSetSize; } } + + private static unsafe string[] GetCommandLineArgsNative() + { + char* lpCmdLine = Interop.Kernel32.GetCommandLine(); + Debug.Assert(lpCmdLine != null); + + int numArgs = 0; + char** argvW = Interop.Shell32.CommandLineToArgv(lpCmdLine, &numArgs); + if (argvW == null) + { + ThrowHelper.ThrowOutOfMemoryException(); + } + + try + { + string[] result = new string[numArgs]; + for (int i = 0; i < result.Length; i++) + { + result[i] = new string(*(argvW + i)); + } + return result; + } + finally + { + Interop.Kernel32.LocalFree((IntPtr)argvW); + } + } } } diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Environment.GetCommandLineArgs.cs b/src/libraries/System.Runtime.Extensions/tests/System/Environment.GetCommandLineArgs.cs index 695af6b8f2567..dc07a79090975 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Environment.GetCommandLineArgs.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Environment.GetCommandLineArgs.cs @@ -70,5 +70,38 @@ public static int CheckCommandLineArgs(string[] args) return RemoteExecutor.SuccessExitCode; } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void GetCommandLineArgs_Fallback_Returns() + { + if (PlatformDetection.IsNotMonoRuntime + && PlatformDetection.IsNotNativeAot + && PlatformDetection.IsWindows) + { + // Currently fallback command line is only implemented on Windows coreclr + RemoteExecutor.Invoke(CheckCommandLineArgsFallback).Dispose(); + } + } + + public static int CheckCommandLineArgsFallback() + { + string[] oldArgs = Environment.GetCommandLineArgs(); + + // Clear the command line args set for managed entry point + var field = typeof(Environment).GetField("s_commandLineArgs", BindingFlags.Static | BindingFlags.NonPublic); + Assert.NotNull(field); + field.SetValue(null, null); + + string[] args = Environment.GetCommandLineArgs(); + Assert.NotEmpty(args); + + // The native command line should be superset of managed command line + foreach (string arg in oldArgs) + { + Assert.Contains(arg, args); + } + + return RemoteExecutor.SuccessExitCode; + } } }