.NET Main函数参数解释过程和特殊规则

开发者福利!热门AI工具限时免费用 购周边即赠Coding Plan Lite,Claude Code、Cursor等20+工具畅享,效率翻倍! 阅读详情
最近正在准备一个小型开发工具的发布工作(具体的内容请容我先卖个关子,等发布之后我会详细给大家介绍的)。在使用时不经意中发现,使用.NET开发的命令行工具的对引号和反斜杠 /和一般命令行程序有着不同的解释规则。举例来说,如果你在命令行下输入:
 
C:/> tool “C:/Program Files/”
 

实际上传入的参数是C:/Program Files” 。其实这里不仔细看可能发现不了问题。在原来的命令行中,第一个双引号代表一个参数的开始/结束,后面/”因为.NET的解释规则不同,代表实际的双引号,而非参数的开始/结束,因此最后的结果会多出一个双引号,并且缺少一个反斜杠。

内部,CLR使用CommandLineToArgvW来分析程序的命令行分离出各个参数,这个函数有着特殊的解释规则:
1.     2n个反斜杠后面跟一个双引号代表n个反斜杠
2.     2n+1个反斜杠后面跟一个双引号代表n个反斜杠加一个
3.     N个反斜杠后面不跟双引号直接代表n个反斜杠
这个规则比较绕,用例子的方式来解释就是:
命令行参数
实际参数
“C:/Program Files/”
C:/Program Files”
“C:/Program Files//”
C:/Program Files/
“C:/Program Files///”
C:/Program Files/”

因此,正确的方式是第二个,也就是”C:/Program Files//”

 
事实上,CLR内部并没有直接调用CommandLineToArgvw,而是直接实现了一个有着同等功能的函数SegmentCommandLine。在Rotor的源代码中可以找到它的实现,位于sscli20/clr/src/utilcode/util.cppCLR的主函数_CorExeMain在执行主函数之前会调用CorCommandLine::SetArgvW,这个函数会调用SegmentCommandLine来分析命令行(经过简化):
 
// Set argvw from command line
/* static */

HRESULT CorCommandLine::SetArgvW(LPCWSTR lpCommandLine)

{
 

    HRESULT hr = S_OK;

if(!m_ArgvW) {

    // 分析命令行

        m_ArgvW = SegmentCommandLine(lpCommandLine, &m_NumArgs);

 

        // CLR特有的命令行处理,主要是和ClickOnce有关的

        if (m_ArgvW)

            hr = ParseCor();

        else

            hr = E_OUTOFMEMORY;

    }

 

    return hr;

}
 
 
真正在执行Main主函数的时候,ClassLoader::RunMain函数则会调用CorCommandLine::GetArgvW获得之前分析得到的参数列表,并创建对应的托管String数组并传递给Main(经过简化):
/* static */

HRESULT ClassLoader::RunMain(MethodDesc *pFD ,

                             short numSkipArgs,
                             INT32 *piRetVal,

                             PTRARRAYREF *stringArgs /*=NULL*/)

{
 

    wzArgs = CorCommandLine::GetArgvW(&cCommandArgs);

 

    // 创建一个托管数组

    StrArgArray = (PTRARRAYREF) AllocateObjectArray((cCommandArgs - numSkipArgs), g_pStringClass);

 

    // 创建对应的托管字符串并赋给托管数组的每个元素

    for( arg = numSkipArgs; arg < cCommandArgs; arg++) {

        STRINGREF sref = COMString::NewString(wzArgs[arg]);

        StrArgArray->SetAt(arg-numSkipArgs, (OBJECTREF) sref);

    }

 
MethodDescCallSite threadStart(pFD); // 准备调用MethodDesc指向的主函数(EntryPoint)
 

    ARG_SLOT stackVar = ObjToArgSlot(StrArgArray); // 将数组元素转为函数参数

 

*piRetVal = (INT32)threadStart.Call_RetArgSlot(&stackVar); // 调用主函数(EntryPoint

 

    return hr;

}
 
而最关键的SegmentCommandLine函数代码则如下:
 
//---------------------------------------------------------------------

// Splits a command line into argc/argv lists, using the VC7 parsing rules.

//

// This functions interface mimics the CommandLineToArgvW api.

//
// If function fails, returns NULL.
//

// If function suceeds, call delete [] on return pointer when done.

//
//---------------------------------------------------------------------

LPWSTR *SegmentCommandLine(LPCWSTR lpCmdLine, DWORD *pNumArgs)

{

    STATIC_CONTRACT_NOTHROW;

    STATIC_CONTRACT_GC_NOTRIGGER;

    STATIC_CONTRACT_FAULT;

 
 

    *pNumArgs = 0;

 

    int nch = (int)wcslen(lpCmdLine);

 

    // Calculate the worstcase storage requirement. (One pointer for

    // each argument, plus storage for the arguments themselves.)

    int cbAlloc = (nch+1)*sizeof(LPWSTR) + sizeof(WCHAR)*(nch + 1);

    LPWSTR pAlloc = new (nothrow) WCHAR[cbAlloc / sizeof(WCHAR)];

    if (!pAlloc)

        return NULL;

 

    LPWSTR *argv = (LPWSTR*) pAlloc; // We store the argv pointers in the first halt

    LPWSTR pdst = (LPWSTR)( ((BYTE*)pAlloc) + sizeof(LPWSTR)*(nch+1) ); // A running pointer to second half to store arguments

    LPCWSTR psrc = lpCmdLine;

    WCHAR   c;

    BOOL    inquote;

    BOOL    copychar;

    int     numslash;

 

    // First, parse the program name (argv[0]). Argv[0] is parsed under

    // special rules. Anything up to the first whitespace outside a quoted

    // subtring is accepted. Backslashes are treated as normal characters.

    argv[ (*pNumArgs)++ ] = pdst;

    inquote = FALSE;

    do {

        if (*psrc == L'"' )

        {

            inquote = !inquote;
            c = *psrc++;
            continue;

        }

        *pdst++ = *psrc;

 

        c = *psrc++;

 

    } while ( (c != L'/0' && (inquote || (c != L' ' && c != L'/t'))) );

 

    if ( c == L'/0' ) {

        psrc--;

    } else {

        *(pdst-1) = L'/0';

    }

 

    inquote = FALSE;

 
 
 

    /* loop on each argument */

    for(;;)

    {

        if ( *psrc )

        {

            while (*psrc == L' ' || *psrc == L'/t')

            {
                ++psrc;
            }

        }

 

        if (*psrc == L'/0')

            break;              /* end of args */
 

        /* scan an argument */

        argv[ (*pNumArgs)++ ] = pdst;

 

        /* loop through scanning one argument */

        for (;;)

        {

            copychar = 1;

            /* Rules: 2N backslashes + " ==> N backslashes and begin/end quote

               2N+1 backslashes + " ==> N backslashes + literal "

               N backslashes ==> N backslashes */

            numslash = 0;
            while (*psrc == L'//')
            {

                /* count number of backslashes for use below */

                ++psrc;
                ++numslash;
            }
            if (*psrc == L'"')
            {

                /* if 2N backslashes before, start/end quote, otherwise

                   copy literally */
                if (numslash % 2 == 0)
                {
                    if (inquote)
                    {

                        if (psrc[1] == L'"')

                        {
                            psrc++;    /* Double quote inside quoted string */
                        }
                        else
                        {

                            /* skip first quote char and copy second */

                            copychar = 0;
                        }
                    }
                    else
                    {
                        copychar = 0;       /* don't copy quote */
                    }
                    inquote = !inquote;
                }
                numslash /= 2;          /* divide numslash by two */
            }

   

            /* copy slashes */
            while (numslash--)
            {
                *pdst++ = L'//';
            }

   

            /* if at end of arg, break loop */

            if (*psrc == L'/0' || (!inquote && (*psrc == L' ' || *psrc == L'/t')))

                break;

   

            /* copy character into argument */

            if (copychar)
            {
                *pdst++ = *psrc;
            }
            ++psrc;

        }

 

        /* null-terminate the argument */

 

        *pdst++ = L'/0';          /* terminate string */

    }

 

    /* We put one last argument in -- a null ptr */

    argv[ (*pNumArgs) ] = NULL;

 

    _ASSERTE((BYTE*)pdst <= (BYTE*)pAlloc + cbAlloc);

    return argv;

}
有关CLR执行Main函数执行过程的更多有关内容我会在下篇Rotor源码研究中详细解释,敬请关注。


Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1852691


main函数的两个参数 通过简单的程序,展示了main函数的两个参数的意义 立即下载

相关推荐

VirtualBox增强功能安装全指南:从下载到配置复制粘贴功能

本文详细介绍了VirtualBox增强功能的完整安装与配置流程,重点解决了虚拟机与主机间复制粘贴、文件拖放等核心协作难题。通过分步指南故障排查,帮助用户在不同操作系统(如Windows、Linux)中实现无缝集成,提升虚拟化工作效率与体验。

weixin_29206669的博客 379

.NET】了解过入口函数 Main() 吗?带你用批处理玩转 Main 函数

简介 我们知道,新建一个控制台应用程序的时候,IDE 会同时创建一个默认的类 Program.cs,该类会包含一个 Main 方法。我们在直接 F5 的时候就会立即执行里面的语句。但是,你自己有认真了解过它吗。   Main方法是 C# 控制台应用程序 Windows 窗体应用程序的入口点(也称切入点)。应用程序启动时,Main方法是第一个调用的方法。程序中只能,只允许有一个入口点,如果...

u013938578的博客 1714

DAP-Link DIY复刻指南

DAP-Link是ARM开源的一个项目,主要用于代码下载以及调试,支持SWDJTAG调试接口,对于开发嵌入式的同学相信都或多或少有所耳闻。除了DAP-Link之外还有很多同类型的调试器,如ST-LinkJ-Link,但是这些都是不开源的,无法复刻或进行二次开发。但DAP-Link不同,完全开源,不用担心侵权问题,且随着开源社区大家的共同努力,DAP-Link经过了不断的迭代,先如今已取代ARM之前推出的CMSIS-DAP开源项目。

爱出名的狗腿子 1万+

.net知识学习方法系列(十一)Main方法的参数与返回值

好多知识,如果我们要多问点为什么,可能就能得到更多,了解更多,今天就看一下Main方法的参数返回值。 Main方法有一个参数是string[],有一个返回值是int,作用是什么呢? 参数,是一个方法调用别一个方法时传入的变量,Main方法又是一个程序的入口方法,这上参数一定是别的程序调用时传进来的,看这样一个程序: 被调程序代码: 1usingSystem; 2usingSys...

weixin_30674525的博客 171

.NET 6 中 C# 控制台应用模板生成的顶级语句变化

.NET 6 中 C# 控制台应用模板生成的顶级语句变化

weixin_46870692的博客 2312

.NET8修改Main入口

有小伙伴问: .NET托管入口Main函数可以修改成别的函数,用来作为程序的入口吗? 答案:当然是可以的。这也算是.NET里面非常简单的骚操了。本篇来用最新的.NET8演示下,如何修改Main入口。

寒冰屋的专栏 496
上一篇: 可扩展能力还是够用就好?
下一篇: Calling Convention的总结
softstars
博客等级 码龄19年 90粉丝 5213原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值