---恢復內容開始---
一般,我們都是通過Visual Studio(下面簡稱vs)來編寫和編譯vb.net應用程序的,但是,不少的人並不知道vs是通過何種方式編譯程序的。今天,我們就來探討一下編譯vb.net程序的真正原理。
這篇隨筆包含如下幾個部分:
- 1.VS是怎么編譯的
- 2.通過vbc.exe來編譯應用程序
- 3.在代碼中通過VBCodeProvider動態編譯應用程序
ok,首先來說說vs編譯應用程序的方法。其實,vs是通過調用vbc.exe來編譯vbnet應用程序的。vs把用戶編寫的代碼文件和指定定的參數(引用,編譯出的程序的類型,目標文件的保存位置等)一股腦的傳給vbc,然后剩下的工作就交給vbc來做了。
其實,vbnet程序的編譯並不像我們想象的那么困難。對於編譯一些比較簡單的代碼,幾行cmd命令就可以完全搞定。而且,這個vbc.exe並不是vs的專用附屬,不論你是.net2.0還是最新的.net4.5.3預覽版,只要你家有.net Framework,就能找到vbc。vbc的地址是 C:\Windows\Microsoft.NET\Framework\.net版本(比如v4.0.30319)\vbc.exe.
可以cmd來調用它。這是截圖。
首先cd到指定路徑下然后調用vbc。vbc功能強大,這里列出一部分。
/target:exe 創建控制台應用程序(默認)。 (縮寫: /t)
/target:winexe 創建 Windows 應用程序。
/target:library 創建庫程序集。
/target:module 創建可添加到程序集的模塊。
/target:appcontainerexe 創建在 AppContainer 中運行的 Windows
應用程序。
/target:winmdobj 創建 Windows 元數據中間文件
/doc[+|-] 生成 XML 文檔文件。
/doc:<file> 將 XML 文檔文件生成到 <file>。
- 輸入文件 -
/addmodule:<file_list> 從指定模塊中引用元數據。
/link:<file_list> 嵌入所指定互操作程序集中的元數據。 (縮寫: /l)
/recurse:<wildcard> 根據通配符規范,包括當前目錄及子目錄下的所有文
件。
/reference:<file_list> 從指定的程序集引用元數據。 (縮寫: /r)
- 資源 -
/linkresource:<resinfo> 將指定文件鏈接為外部程序集資源。resinfo:
<file>[,<name>[,public|private]] (縮寫:
/linkres)
/nowin32manifest 默認清單不應嵌入在輸出 PE 的清單部分中。
/resource:<resinfo> 將指定文件添加為嵌入的程序集資源。resinfo:
<file>[,<name>[,public|private]] (縮寫: /res)
/win32icon:<file> 為默認 Win32 資源指定 Win32 圖標文件(.ico)。
/win32manifest:<file> 提供的文件嵌入在輸出 PE 的清單部分中。
/win32resource:<file> 指定 Win32 資源文件(.res)。
- 代碼生成 -
/optimize[+|-] 啟用優化。
/removeintchecks[+|-] 移除整數檢查。默認為“關”。
/debug[+|-] 發出調試信息。
/debug:full 發出完全調試信息(默認)。
/debug:pdbonly 只發出 PDB 文件。
- 錯誤和警告 -
/nowarn 禁用所有警告。
/nowarn:<number_list> 禁用各個警告的列表。
/warnaserror[+|-] 將所有警告視為錯誤。
/warnaserror[+|-]:<number_list> 將警告列表視為錯誤。
這里詳細介紹幾個參數:
1./target
縮寫為/t 他指定了編譯類型。有四種選項 exe(將編譯成控制台應用程序) ,winexe(將編譯成 窗體應用程序),library(編譯成類庫【*.dll】)和module(編譯成模塊)。
例如 /t:exe
2./reference:<file_list>
縮寫: /r 從指定的程序集引用元數據。 它的作用就相當於vs里的添加引用。可以通過指定這個參數來添加引用,vbc為我們默認引用了許多類庫。他們保存在vbc.rsp里。默認的vbc.rsp也就是默認添加的引用如下。
# command line compiler (VBC) will process as part
# of every compilation, unless the "/noconfig" option
# is specified.
# Reference the common Framework libraries
/r:Accessibility.dll
/r:System.Configuration.dll
/r:System.Configuration.Install.dll
/r:System.Data.dll
/r:System.Data.OracleClient.dll
/r:System.Deployment.dll
/r:System.Design.dll
/r:System.DirectoryServices.dll
/r:System.dll
/r:System.Drawing.Design.dll
/r:System.Drawing.dll
/r:System.EnterpriseServices.dll
/r:System.Management.dll
/r:System.Messaging.dll
/r:System.Runtime.Remoting.dll
/r:System.Runtime.Serialization.Formatters.Soap.dll
/r:System.Security.dll
/r:System.ServiceProcess.dll
/r:System.Transactions.dll
/r:System.Web.dll
/r:System.Web.Mobile.dll
/r:System.Web.RegularExpressions.dll
/r:System.Web.Services.dll
/r:System.Windows.Forms.Dll
/r:System.XML.dll
/r:System.Workflow.Activities.dll
/r:System.Workflow.ComponentModel.dll
/r:System.Workflow.Runtime.dll
/r:System.Runtime.Serialization.dll
/r:System.ServiceModel.dll
/r:System.Core.dll
/r:System.Xml.Linq.dll
/r:System.Data.Linq.dll
/r:System.Data.DataSetExtensions.dll
/r:System.Web.Extensions.dll
/r:System.Web.Extensions.Design.dll
/r:System.ServiceModel.Web.dll
# Import System and Microsoft.VisualBasic
/imports:System
/imports:Microsoft.VisualBasic
/imports:System.Linq
/imports:System.Xml.Linq
/optioninfer+
下面我們來一個簡單的vbc編譯例子。
首先我們編寫了一個最最簡單的vb代碼。如下:
Imports System.Windows.Forms Module Module1 Sub Main() MessageBox.Show("Hello World") End Sub End Module
我們把它保存到vbc的目錄下。名為a.txt
我們調用cmd,cd 到指定目錄下,調用vbc.exe執行下面的命令:vbc.exe a.txt.然后回車。編譯成功。
這時目錄下出現了一個a.exe的文件。點擊執行,效果如下。
消息框彈出了。后面還有個黑框框。有人可能會問了,這里並沒有什么/r /t的,為什么會成功呢?這就是默認的緣故。我們說了,/t默認為exe(控制台),/r默認引用了vbc.rsp里的程序集。所以可以成功。
好了,這里就不多講了,大家可以自己看着參數摸索一下,很容易就會掌握了。
下面進入第三部分,vbnet的動態編譯。
動態編譯,即在代碼中動態編譯出程序。方法有很多,可以調用vbc,也可以使用反射,但我們這里使用一種較為簡單的方法VBCodeProvider。
為使用,首先添加引用
Imports System.Reflection Imports System.CodeDom.Compiler
然后,聲明VBCodeProvider對象。
Dim cp As New VBCodeProvider()
這樣,就創建了一個vb編譯器實例。
我們還要再聲明一個參數對象。CompilerParameters
Dim cParameters As New CompilerParameters()
我就不放出CompilerParameters的成員列表了。大家可從http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=ZH-CN&k=k%28System.CodeDom.Compiler.CompilerParameters%29;k%28TargetFrameworkMoniker-.NETFramework 查看。
這里講一下它的幾個重要的屬性
1.GenerateExecutable 通過它可以設置是否編譯成exe文件。我們這里為true。
2.GenerateInMemory 他可以指示是否在內存中生成輸出。 這里為false。
3. OutputAssembly 用它來設置輸出程序集的名稱。
4.ReferencedAssemblies 獲取本程序引用的程序集。可以通過ReferencedAssemblies的Add方法添加引用。
設置完了之后來上這一句獲得編譯結果
Dim cresult As CompilerResults = pc.CompileAssemblyFromFile(cParameters, "a.txt")
這就是說,用cParameters作為參數編譯a.txt這個文件,返回編譯結果給cresult
然后對cresult進行操作保存生成的exe,詳見代碼。
好了,用這個方法方法,我們用代碼編譯a.txt
我很懶,就直接寫在form的load里面了。
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load '聲明 Dim pc As VBCodeProvider = New VBCodeProvider Dim cParameters As New CompilerParameters() cParameters.GenerateExecutable = True '生成exe文件 cParameters.GenerateInMemory = False '不在內存中生成 '添加引用 cParameters.ReferencedAssemblies.Add("System.dll") cParameters.ReferencedAssemblies.Add("System.Windows.Forms.dll") '編譯並返回結果 Dim cresult As CompilerResults = pc.CompileAssemblyFromFile(cParameters, "a.txt") If cresult.NativeCompilerReturnValue = 0 Then '如果編譯成功 Try '避免重名而報錯,刪除舊文件 My.Computer.FileSystem.DeleteFile("a.exe") Catch ex As Exception End Try '把生成的文件移動到目標目錄下(因為他是保存在一個特定的地方, 文件名也是隨機的) My.Computer.FileSystem.MoveFile(cresult.PathToAssembly, "a.exe") Else For Each a As CompilerError In cresult.Errors '出錯,顯示錯誤信息 MsgBox(a.ErrorText) Next End If End Sub
這樣,一個簡單編譯器就做好了。
