`
lovnet
  • 浏览: 6871045 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

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

阅读更多
最近正在准备一个小型开发工具的发布工作(具体的内容请容我先卖个关子,等发布之后我会详细给大家介绍的)。在使用时不经意中发现,使用.NET开发的命令行工具的对引号和反斜杠 \和一般命令行程序有着不同的解释规则。举例来说,如果你在命令行下输入:
C:\> tool “C:\Program Files\”

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

内部,CLR使用CommandLineToArgvW来分析程序的命令行分离出各个参数,这个函数有着特殊的解释规则:
<!--[if !supportLists]-->1. <!--[endif]-->2n个反斜杠后面跟一个双引号代表n个反斜杠
<!--[if !supportLists]-->2. <!--[endif]-->2n+1个反斜杠后面跟一个双引号代表n个反斜杠加一个
<!--[if !supportLists]-->3. <!--[endif]-->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);

}

MethodDescCallSitethreadStart(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

LPWSTRpdst = (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源码研究中详细解释,敬请关注。
分享到:
评论

相关推荐

    ASP.Net各种概念和程序编写方法

    它基于.NET Framework,并提供了丰富的功能和工具来简化Web开发过程。在本文中,我们将深入探讨ASP.NET的一些核心概念和编程方法。 首先,让我们关注面向对象编程(OOP)的基本特性:封装、继承和多态。封装是OOP的...

    C#.Net基础7 基础入门知识 函数 范例 ppt

    1、我们在Main()函数中,调用Test()函数,我们管Main()函数称之为调用者, 管Test()函数称之为被调用者。 如果被调用者想要得到调用者的值: 1)、传递参数。 2)、使用静态字段来模拟全局变量。 如果调用者想要得到被...

    asp.net命名规范.doc

    3. **优先使用.NET库函数和公共函数**:除非有特殊需求,否则应优先使用.NET框架提供的库函数及项目中的公共函数。 - 这有助于提高代码的一致性和可维护性。 - 减少对外部方法(如Windows核心动态链接库)的依赖,...

    C#基础教程(.NET编程语言)

    - **基本结构**:一个C#控制台应用程序通常包含`class`和`static void Main()`方法。 - **输出语句**:使用`Console.WriteLine`方法来输出文本。 ##### 3.3 运行程序 - **编译**:使用Visual Studio或命令行工具...

    经典.net面试题目

    在 .NET 中,委托是一种特殊的类型,可以将一个方法作为参数传递给另一个方法。事件是一种特殊的委托,用于处理用户交互事件。 五、override 和重载的区别 override 和重载是两个不同的概念: 1. 重载:方法的...

    asp.net常见的面试题目(有答案)

    - **业务逻辑层**:实现业务规则和流程,处理业务逻辑。 - **表示层**:与用户交互,展示数据和接收用户输入。 这样的分层提高了系统的可维护性和可扩展性,使得各层职责更加清晰,有利于团队协作。 #### 12. 类的...

    129 .net面试题

    - **业务逻辑层** (Business Logic Layer): 包含业务逻辑处理,可以进一步细分为业务规则层和业务表观层。 - **表示层** (Presentation Layer): 用户界面,负责与用户的交互。 - **优点**: - 易于维护和扩展。 -...

    .NET面试题

    - **CLS(Common Language Specification)**:通用语言规范,定义了一组所有.NET语言都必须遵循的规则,确保不同语言编写的代码可以互操作。 - **CLR(Common Language Runtime)**:公共语言运行库,是.NET框架的...

    .net常见面试题

    可以进一步细分为业务表观层(负责与表示层通信)和业务规则层(处理复杂的业务逻辑)。 - **表示层**: 负责与用户交互,如显示数据、接收用户输入等。 **分层的好处**: - **分工明确**: 不同层次关注不同的职责...

Global site tag (gtag.js) - Google Analytics