`
happmaoo
  • 浏览: 4336287 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

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

阅读更多
<iframe align="top" marginwidth="0" marginheight="0" src="http://www.zealware.com/46860.html" frameborder="0" width="468" scrolling="no" height="60"></iframe>
最近正在准备一个小型开发工具的发布工作(具体的内容请容我先卖个关子,等发布之后我会详细给大家介绍的)。在使用时不经意中发现,使用.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

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

return argv;

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


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


分享到:
评论

相关推荐

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

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

    ASP.NET面试题

    protected : 保护成员,该类内部和继承类中可以访问。 public : 公共成员,完全公开,没有访问限制。 internal: 在同一命名空间内可以访问。 2 .列举ASP.NET 页面之间传递值的几种方式。 答. 1.使用QueryString,...

    ASP.NET面试题大全

    protected : 保护成员,该类内部和继承类中可以访问。 public : 公共成员,完全公开,没有访问限制。 internal: 在同一命名空间内可以访问。 2 .列举ASP.NET 页面之间传递值的几种方式。 答. (1).使用...

    -C++参考大全(第四版) (2010 年度畅销榜

    6.9 传统的与现代的函数参数声明 第7章 结构、联合、枚举和用户定义的类型 7.1 结构 7.2 结构数组 7.3 向函数传递结构 7.4 结构指针 7.5 结构中的数组和结构 7.6 位域 7.7 联合 7.8 枚举 7.9 用sizeof来保证可移植...

    UNIX高级编程 计算机科学丛书

    7.2 main 函数 7.3 进程终止 7.3.1 exit和_exit函数 7.3.2 atexit函数 7.4 命令行参数 7.5 环境表 7.6 C程序的存储空间布局 7.7 共享库 7.8 存储器分配 7.9 环境变量 7.10 setjmp 和longjmp函数 7.10.1 自动, 寄存器...

    net学习笔记及其他代码应用

    30.简要谈一下您对微软.NET 构架下remoting和webservice两项技术的理解以及实际中的应用。 答:WS主要是可利用HTTP,穿透防火墙。而Remoting可以利用TCP/IP,二进制传送提高效率。 31.公司要求开发一个继承System....

    万能makefile写法详解,一步一步写一个实用的makefile

    2-此时,根据依赖规则,由于command.h的文件戳改了,要重新生成main.o和main.d文件。 3-先调用gcc -c main.c -o main.o生成main.o, 再调用gcc -MM main.c &gt; main.d重新生成main.d。 此时main.d的依赖文件里增加了...

    c#学习笔记.txt

    在 C 或 C 中与委托最接近的是函数指针,但函数指针只能引用静态函数,而委托可以同时引用静态方法和实例方法。在后一种情况中,委托不仅存储对方法入口点的引用,还存储对调用其方法的对象的引用。与 C 函数指针...

    Visual C++ 2005入门经典--源代码及课后练习答案

     本书系编程语言先驱者Ivor Horton的经典之作,是学习C++编程最畅销的图书品种之一,不仅涵盖了Visual C++ .NET编程知识,还全面介绍了标准C++语言和.NET C++/CLI。本书延续了Ivor Horton讲解编程语言的独特方法,...

    C#最新面试题(127道)word格式

    3. 一列数的规则如下: 1、1、2、3、5、8、13、21、34...... 求第30位数是多少, 用递归算法实现。 答:public class MainClass { public static void Main() { Console.WriteLine(Foo(30)); } public static ...

    C#微软培训资料

    10.3 构造函数和析构函数 .119 10.4 小 结 .122 第十一章 方 法 .124 11.1 方法的声明.124 11.2 方法中的参数.125 11.3 静态和非静态的方法.129 11.4 方法的重载.130 11.5 操作符重载.134 11.6 小 ...

    ExtAspNet_v2.3.2_dll

    ExtAspNet是一组专业的Asp.net控件库,拥有原生的AJAX支持和丰富的UI效果, 目标是创建没有ViewState,没有JavaScript,没有CSS,没有UpdatePanel,没有WebServices的Web应用程序。 支持的浏览器: IE 7.0+, Firefox...

    ExtAspNet v2.2.1 (2009-4-1) 值得一看

    -在2009-03-03 v1.3.0曾经提到这个兼容问题,并有这样的规则,如果Asp.net的按钮AJAX提交,必须设置UseSubmitBehavior="false" --也就是说生成的input的type不能是"submit",而这个限制在有些情况下是不可原谅的...

    java 面试题 总结

    封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。 4. 多态性: 多...

Global site tag (gtag.js) - Google Analytics