手寫JAVA虛擬機(二)——實現java命令行


  查看手寫JAVA虛擬機系列可以進我的博客園主頁查看。

  我們知道,我們編譯.java並運行.class文件時,需要一些java命令,如最簡單的helloworld程序。

  

  這里的程序最好不要加包名,因為加了包名的話編譯和運行需要有所改動。

  看這里的命令。javac為編譯命令,我們知道java的特點是一次編譯,到處運行。這里的編譯指的就是javac,對於java程序即.java文件,先要用javac編譯成字節碼。然后將字節碼(.class文件)放到java虛擬機中運行,即上圖中的java HelloWorld,java虛擬機把字節碼翻譯成對應機器上的機器指令,再由機器來執行具體的機器指令。也就是說java程序員是直接與java虛擬機交互,簡介與機器交互。所以虛擬機完成的是java命令,也就是我們要完成的是java這個指令的功能

  那么我們把第一個目標定為,實現簡單的命令行。即我們通過命令行可以輸入一些內容,虛擬機讀取之后可以給一定的反饋。

  GO語言中有兩個和命令行相關的包,分別是os和flag(java中以類庫即jar文件導入,go中直接以包的形式導入)。

  首先在GOPATH目錄下的src里面新建一個jvmgo文件夾作為我們的工作空間目錄,jvmgo里面再新建一個ch01為我們的第一個目標源碼文件夾,添加cmd.go文件。

  

  在cmd.go里面輸入如下代碼(由於博客園的添加代碼方式不支持go語言着色,所以采用C語言着色,高亮可能不太正確

package main import "flag" import "fmt" import "os"

//定義Cmd結構體
type Cmd struct{ helpFlag bool versionFlag bool cpOption string
    class     string args []string } //解析命令行參數
func parseCmd() *Cmd { cmd:=&Cmd{} //將printUsage函數傳給flag.Usage
    flag.Usage=printUsage //設置各種解析的選項
    flag.BoolVar(&cmd.helpFlag, "help", false, "print help message") flag.BoolVar(&cmd.helpFlag, "?", false, "print help message") flag.BoolVar(&cmd.versionFlag, "version", false, "print version and exit") flag.StringVar(&cmd.cpOption, "classpath", "", "classpath") flag.StringVar(&cmd.cpOption, "cp", "", "classpath") //所有選項設置完成后調用flag.Parse解析所有選項,如果Parse失敗,則調用flag.Usage打印幫助信息
 flag.Parse() //調用flag.Args函數捕獲未被解析的參數,第一個參數為主類名,后面的為傳遞給主類的參數
    args:=flag.Args() if len(args)>0{ cmd.class=args[0] cmd.args=args[1:] } return cmd } func printUsage() { fmt.Printf("Usage:%s[-options] class [args...]\n",os.Args[0]) }

 

  第一行為包名,main包,接着引入了三個包os,flag,fmt。os和flag都是處理命令行所需的包fmt類似於C語言的printf和scanf等格式化IO。再往下定義了一個結構體Cmd,用來這個數據結構來格式化存儲輸入的命令行信息。helpFlag參數為命令行是否請求help,versionFlag參數為命令行是否請求version,cpOption為命令行傳入的classpath即目標.class文件所在文件夾,class為命令行傳入的.class文件名(不包括.class),args為命令行傳入的其他參數。

  緊接着是一個parseCmd函數(go語言有函數和方法之分,方法調用需要receiver,函數調用則不需要 ),返回值為*Cmd,用來解析cmd傳過來的參數。該函數里面先聲明一個cmd並給這個cmd賦值一個新建的Cmd對象。go語言中的“:=”為聲明並賦值,而"="為賦值。先把printUsage的函數賦值給flag.Usage,然后調用flag設置需要解析的選項,全部解析完畢,調用Parse函數解析所有選項。解析成功則結束,解析失敗則調用printUsage打印到控制台。

  flag.Args可以捕獲其他沒有被解析的參數。上面解析成功之后,第一個參數就是主類名,剩下的就是傳給主類的參數。

  工具類編寫完成,下一個是主函數。先上主函數代碼:

package main import "fmt" func main() { //調用parseCmd解析命令行參數
    cmd:=parseCmd() if cmd.versionFlag{ //輸入了-version選項
        fmt.Println("version 0.0.1") }else if cmd.helpFlag||cmd.class==""{ //輸入了-help選項
 printUsage() }else{ //啟動jvm
 stratJVM(cmd) } } func stratJVM(cmd *Cmd){ fmt.Printf("classpath:%s class:%s args:%v\n", cmd.cpOption,cmd.class,cmd.args) }

  跟java類似,在go里面main是一個特殊的包,go程序的入口就是main函數,但是不接受任何參數,也不能有返回值。main函數先調用parseCmd解析命令行參數,如果是-version則返回版本號,如果是-help則返回幫助信息,如果是其他則啟動jvm,這里用一些輸出信息“假裝”啟動了jvm,真正的jvm代碼后面會加上。

  至此,對命令行的解析工作全部完成。先展示一下整個工作目錄的結構,不然后面編譯運行的時候會出錯。

  

  我們的工作目錄是D盤下的JVM里的goWorkSpace,再下面src,jvmgo,ch01,ch01里面包含的是我們的go文件。

  來測試一下,打開一命令行,輸入go install jvmgo\ch01。這個命令是使用go.exe來install文件,這個文件存在於GOPATH下面的文件夾(jvmgo\ch01中),結果如圖:

  

  然后在工作空間(GOPATH)的bin文件夾中就多出了一個ch01.exe。

  

  在此處打開命令行。可以進行一些操作:

  

  到這里,我們的命令行工具就完成了,雖然還沒有涉及真正的虛擬機設計,但這也是虛擬機運行的重要一步,后面會逐漸介紹虛擬機的設計。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM