3.4 外部命令的处理过程
之所以有外部命令和内部命令之分,一个原因是为了编程上的方便。在当前的实现中,所有内部命令都是在SHELL所在的源文件内实现的,而外部命令,则统一以EXTCMD.CPP为接口。实现的时候,用户可以通过另外的模块实现功能部分,然后只需要在EXTCMD.CPP文件当中做一个简单的修改即可。另外的一个因素是所有内部命令都是以函数的形式实现的,即内部命令的运行上下文与Shell共享,但外部命令的实现则是通过创建单独的核心线程实现的,有自己的线程上下文。
为了实现外部命令,特定义如下的数据结构。
__BEGIN_DEFINE_OBJECT(__EXTERNAL_COMMAND) LPSTR lpszCmdName; LPSTR lpszHelpInfo; BOOL bBackground; DWORD (*ExtCmdHandler)(LPVOID); __END_DEFINE_OBJECT()
其中,lpszCmdName是外部命令的命令字符串,lpszHelpInfo是外部命令的帮助信息,通过执行help命令,可把外部命令的相关帮助信息打印出来。bBackground变量是一个指示变量,告诉操作系统,该外部命令是在后台执行还是在前台执行。若是在后台执行,则操作系统创建外部命令的执行线程,然后就返回用户界面(Shell线程),这时候,用户界面仍然正常响应用户需求。若是在前台执行,则操作系统会创建外部命令的执行线程后,将一直等待外部命令执行结束,然后才返回Shell,这个过程,用户只能与外部命令提供的用户接口进行交互。ExtCmdHandler则是具体的外部命令入口点,可以看出,该函数的原型是与线程入口点的函数原型相匹配的。
定义如下数组,来管理所有的外部命令。
__EXTERNAL_COMMAND ExtCmdArray[ ]={ {"extcmd1", "The first external command.", TRUE,ExtCmd1}, {NULL,NULL,FALSE,NULL} };
在实现外部命令的时候,只需要在上述数组中加入对应的内容,就可以被系统识别。这时候,在命令提示符下,只需输入外部命令字符串,外部命令就可以得到执行。另外,通过执行help命令,可以打印出外部命令的帮助信息。
对于外部命令的处理,是由DoExternalCmd函数完成的,该函数被DoCommand函数调用(参见上面内部命令实现的描述),代码如下。
BOOL DoExternalCmd(LPSTR lpszCmd) { DWORD wIndex =0x0000; DWORD i =0; LPSTR lpszParam =NULL; __KERNEL_THREAD_OBJECT* lpExtCmd=NULL; BOOL bResult =FALSE; //If find the correct command object,then //This flag set to TRUE. BYTE tmpBuffer[36]; while((' ' !=lpszCmd[wIndex]) && lpszCmd[wIndex] && (wIndex < 32)) { tmpBuffer[wIndex]=lpszCmd[wIndex]; wIndex++; } tmpBuffer[wIndex]=0; while(ExtCmdArray[i].lpszCmdName) { if(StrCmp(&tmpBuffer[0],ExtCmdArray[i].lpszCmdName)) //Find the correct external command entry. { // //Handle external command here. // if(tmpBuffer[wIndex+1]) //Have parameters. { lpszParam= (LPSTR)KMemAlloc(StrLen(&tmp Buffer[wIndex+1]+1),KMEM_SIZE_TYPE_ANY); if(NULL==lpszParam) //Can not allocate memory. { bResult=FALSE; break; } StrCpy(lpszParam,&tmpBuffer[wIndex+1]); } lpExtCmd=KernelThread Manager .CreateKernelThread( (__COMMON_OBJECT*) &Kernel Thread Manager, 0L, KERNEL_THREAD_STATUS_READY, PRIORITY_LEVEL_NORMAL, ExtCmdArray[i].ExtCmdHandler, lpszParam, NULL); if(NULL==lpExtCmd) //Can not create thread. { bResult=FALSE; break; } if(!ExtCmdArray[i].bBackground) //Should run in foreground. { DeviceInputManager.SetFocusThread( (__COMMON_OBJECT *)&Device InputManager, (__COMMON_ OBJECT*) lpExtCmd); //Set focus. lpExtCmd->WaitForThisObject ((__COMMON_ OBJECT*)lpExtCmd); //Blocking. } bResult=TRUE; //Set the flag. break; } } return bResult; }
与内部命令处理类似,首先从命令行中分离出具体的外部命令,然后根据外部命令字符串匹配外部命令列表,若找不到匹配的项,则返回FALSE,从而导致DefaultHandler被调用(参考DoCommand函数)。若匹配成功,则进一步判断,用户是否输入了作用于该外部命令的参数。若有额外的参数,则该函数申请一块内存,并把额外的参数复制到申请的内存中。然后调用CreateKernelThread函数,创建一个线程,线程的入口点就是用户提供的外部命令处理函数。需要注意的是,在存在额外参数的情况下,由于DoExternalCmd函数申请了内存,然后把该内存传递到了新创建的函数,因此这块内存的释放应该是外部命令处理函数的工作。若外部命令处理函数不释放参数占用的内存,则可能会引起内存泄漏。
在完成外部命令执行线程的创建后,会进一步判断该外部命令是否需要在后台执行。若是后台执行程序,则DoExternalCmd函数直接返回,若是非后台执行程序,则DoExternalCmd函数需要等待外部命令线程的执行,进入阻塞状态。这种情况下,仍把当前的输入焦点设置为外部命令执行线程,以便外部命令执行线程能够接受用户输入。