在windows程序中嵌入Lua脚本引擎--使用VS IDE编译Luajit脚本引擎
? ? ? ? 前些天聽到一個需求:某業務方需要我們幫忙清理用戶電腦上的一些廢棄文件。同事完成這個邏輯的方案便是在我們程序中加入了一個很“獨立”的業務邏輯:檢索和刪除某個程序產生的廢棄文件。試想,該“獨立”的邏輯之后會如何?被刪掉?一直保留著?不管如何,這都意味著我們代碼需要做修改,我們生成的二進制文件將產生差異,我們要為了這個需求要發一次版本。想一想客戶端升級這樣一個漫長且耗流量的過程,我甚至認為為了這么一個需求去浪費這些非常不值得。那么有沒有一種比較好的辦法,讓我們不修改代碼,不發布版本就能完成這樣的“一次性”需求呢?當然有!是否記得若干年前,某個大公司和某個大公司吵架,當時那位新上任的CEO說某某公司可以“云暗殺”。且這種“暗殺”是一次性的,做完后可以銷毀證據,且非常難以捕捉。我沒有考究這個說法,但是從技術層面來說,這樣的技術可以說并不復雜。那如何實現呢?就是本系列文章中討論的:在程序中嵌入Lua腳本引擎。(轉載請指明出于breaksoftware的csdn博客)
? ? ? ? 首先簡要介紹下Lua。它是巴西里約熱內盧某高校發明的一種輕量級腳本語言。設計該語言的目標是:要成為一個很容易嵌入其它語言中使用的語言。由于“輕量級”和“易嵌入”這兩個特性,會減少我們內嵌其的代價,這也是我選擇它的最主要原因。至于穩定性,我無法評說,但是目前很多游戲中都內嵌了lua的腳本引擎,其中不乏《魔獸世界》這樣的大作。我覺得像這樣的產品都選用Lua,那么至少證明Lua的安全和穩定性還是非??煽康摹?/p>
? ? ? ?Luajit是Lua的一個即時編譯器,它就是我們要內嵌windows程序的目標。http://luajit.org/是它的官方網站,我們可以從它的子頁面得到源碼。我正式準備該系列文章的時間是2012年11月份,此時2.0beta11已經發布。我決定以這個最新的源碼作為我們的例子。
? ? ? ? 下載
? ? ? ? ?http://luajit.org/download/LuaJIT-2.0.0-beta11.zip
? ? ? ? 目錄結構
? ? ? ? 編譯
? ? ? ? 在http://luajit.org/install.html#windows里有詳細的說明,我們只要在使用VS的Command Prompt中定位到src目錄,然后執行msvcbuild.bat。
? ? ? ?VS IDE編譯Luajit
? ? ? ? 如此便編譯成功了。但是,往往我們的工程不是用批處理文件編譯的,而是用IDE。本文主要就是說明如何將該批處理文件轉換為IDE編譯環境。這個操作的過程將拆分各個編譯和鏈接過程,在這個過程中,我們將發現Luajit的生成過程,這將有助于我們之后對Luajit的改造。
? ? ? ? 總體來說,Luajit的編譯和鏈接分為3個大部分:
- 生成minilua程序。利用minilua產生一些文件。
- 使用minilua產生的一些文件生成buildvm程序。使用buildvm產生一些文件。
- 使用buildvm產生的一些文件生成lua程序。
? ? ? ? 編譯環境準備
? ? ? ? 在msvcbuild.bat中有這么一段設置編譯環境的
@set LJCOMPILE=cl /nologo /c /MD /O2 /W3 /D_CRT_SECURE_NO_DEPRECATE
? ? ? ? 其中非常關鍵的一個選項是/02,它對應于“工程屬性”中Configuration Properties->C/C++->Optimization->Optimization的Maximize Speed(/O2)。設置該屬性后,還要設置Configuration Properties->C/C++->Debug Information Format為Program Database(/Zi)。這個設置非常重要,否則會報很多錯誤。我說的這些設置是針對All?Configurations的,這樣在debug和release下編譯和鏈接才不會有問題。
? ? ? ? 編譯MiniLua輔助程序
? ? ? ? 在批處理中有
%LJCOMPILE% host\minilua.c
@if errorlevel 1 goto :BAD
%LJLINK% /out:minilua.exe minilua.obj
@if errorlevel 1 goto :BAD
if exist minilua.exe.manifest^%LJMT% -manifest minilua.exe.manifest -outputresource:minilua.exe
? ? ? ? 于是我們要在項目中新建一個空的console工程,并命名為MiniLua。我們將該工程需要的文件host\minilua.c放入工程目錄,同時加入工程。編譯生成MiniLua.exe。
? ? ? ? Minilua是用于根據平臺來生成平臺相關的代碼。這些生成的代碼將在之后創建的Buildvm工程中使用到。
? ? ? ? 編譯Buildvm輔助程序
? ? ? ? 在批處理中有
%LJCOMPILE% /I "." /I %DASMDIR% host\buildvm*.c
@if errorlevel 1 goto :BAD
%LJLINK% /out:buildvm.exe buildvm*.obj
@if errorlevel 1 goto :BAD
if exist buildvm.exe.manifest^%LJMT% -manifest buildvm.exe.manifest -outputresource:buildvm.exe
? ? ? ? 我們新建一個空的Console程序,并命名為Buildvm。再將host下所有以buildvm開頭的文件拷貝進入該工程目錄(buildvm_peobj.c、buildvm_lib.c、buildvm_fold.c、buildvm_asm.c、buildvm.h、buildvm.c),并將其加入工程。
? ? ? ? 其實只有這些是不夠的,我們之前提過:MiniLua是用于生成Buildvm需要的文件。
@set DASMFLAGS=-D WIN -D JIT -D FFI -D P64
@set LJARCH=x64
@minilua
@if errorlevel 8 goto :X64
@set DASMFLAGS=-D WIN -D JIT -D FFI
@set LJARCH=x86
:X64
minilua %DASM% -LN %DASMFLAGS% -o host\buildvm_arch.h vm_x86.dasc
@if errorlevel 1 goto :BAD
? ? ? ?我們大致可以猜到minilua是使用vm_x86.dasc,在host目錄下生成buildvm_arch.h。而我為了讓工程分離,我已經將buildvm開頭的文件搬到Buildvm工程目錄了。于是我們要在Buildvm工程的Pre-Build Event中設置(將元目錄的*.dasc文件拷貝到Buildvm工程目錄下,因為只有這個工程需要使用到它)
$(OutDir)\MiniLua.exe $(SolutionDir)dynasm/dynasm.lua -LN -D WIN -D JIT -D FFI -o $(SolutionDir)Buildvm/buildvm_arch.h archdasc/vm_x86.dasc
? ? ? ? 我們再將生成的buildvm_arch.h加入工程。
? ? ? ? 因為Buildvm編譯和Minilua生成后都要使用原目錄下的dynasm文件夾下文件。我們將dynasm文件夾拷貝到和這兩個項目同等級的目錄下(LuaProject\dynasm),在Buildvm工程中引用這些文件。
? ? ? ? buidlvm*文件還依賴原src目錄下的lj_*文件,我們將這些文件拷貝到LuaProject\Lj目錄下。并將其相關的頭文件lua.h、luaconf.h、luajit.h拷貝到LuaProject\Header中。在工程設置中設置C\C++->General->Additional Include Directories為"$(SolutionDir)Lj";"$(SolutionDir)Header"。
? ? ? ? Buildvm也是輔助程序,在它生成后,要使用它再生成一些文件。我們將?ALL_LIB中文件(lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c)從原目錄src下拷貝到LuaProject\Lualib的目錄下。
buildvm -m peobj -o lj_vm.obj
@if errorlevel 1 goto :BAD
pause
buildvm -m bcdef -o lj_bcdef.h %ALL_LIB%
@if errorlevel 1 goto :BAD
pause
buildvm -m ffdef -o lj_ffdef.h %ALL_LIB%
@if errorlevel 1 goto :BAD
buildvm -m libdef -o lj_libdef.h %ALL_LIB%
@if errorlevel 1 goto :BAD
buildvm -m recdef -o lj_recdef.h %ALL_LIB%
@if errorlevel 1 goto :BAD
buildvm -m vmdef -o jit\vmdef.lua %ALL_LIB%
@if errorlevel 1 goto :BAD
buildvm -m folddef -o lj_folddef.h lj_opt_fold.c
@if errorlevel 1 goto :BAD
? ? ? ? 我們在Buildvm的Post-Build Event事件中設置如下
@setlocal
cd /d ..\lualib
@set ALL_LIB=lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c lib_fl.c
$(OutDir)\buildvm.exe -m peobj -o $(OutDir)\lj_vm.obj
md $(SolutionDir)Builvmheader
$(OutDir)\buildvm.exe -m bcdef -o $(SolutionDir)Builvmheader\lj_bcdef.h %ALL_LIB%
$(OutDir)\buildvm.exe -m ffdef -o $(SolutionDir)Builvmheader\lj_ffdef.h %ALL_LIB%
$(OutDir)\buildvm.exe -m libdef -o $(SolutionDir)Builvmheader\lj_libdef.h %ALL_LIB%
$(OutDir)\buildvm.exe -m recdef -o $(SolutionDir)Builvmheader\lj_recdef.h %ALL_LIB%
$(OutDir)\buildvm.exe -m vmdef -o ..\jit\vmdef.lua %ALL_LIB%
cd /d ..\lj
$(OutDir)\buildvm.exe -m folddef -o $(SolutionDir)builvmheader\lj_folddef.h lj_opt_fold.c
@endlocal
? ? ? ? 將Buildvm生成為lj_*.h文件生成到LuaProject\Buildvmheader目錄下。將生成的vmdef.lua放到LuaProject\jit(從原src\jit拷貝來的)的目錄下。
? ? ? ? 生成obj文件
%LJCOMPILE% /DLUA_BUILD_AS_DLL lj_*.c lib_*.c
? ? ? ? 該處理要將原src下所有lj_*.c和lib_*.c生成obj,以供之后不同場景進行連接。
? ? ? ?我們新建一個win32項目,將之前沒有放入LuaProject\Lualib的lib_aux.c、lib_init.c拷貝到LuaProject\OtherLualib目錄下。這樣我們Lua工程將引用LuaProject\Builvmheader、LuaProject\Lj、LuaProject\OtherLualib、LuaProject\Lualib?,F在我們要讓我們工程只編譯不鏈接,并將生成的obj文件拷貝到LuaProject\ljobj目錄下。我們在Pre-Link Event事件中設置
md $(TargetDir)ljobj
md $(TargetDir)libobj
copy $(InputDir)$(IntDir)\lj_*.obj $(TargetDir)ljobj\lj_*.obj
copy $(InputDir)$(IntDir)\lib_*.obj $(TargetDir)libobj\lib_*.obj
? ? ? ?右擊Lua工程,選擇Tool Build Order
? ? ? ?設置如下
? ? ? ? 這樣就Lua工程就只編譯不鏈接。
? ? ? ? 生成Dll文件
%LJLINK% /DLL /out:lua51.dll lj_*.obj lib_*.obj
? ? ? ? 我們新建一個win32 dll工程LuaDllProject。并為該工程增加一個空的cpp文件。并在Linker->Input->Additional Dependencies中設置
$(TargetDir)libobj\lib_*.obj $(TargetDir)ljobj\lj_*.obj $(TargetDir)lj_vm.obj
? ? ? ? 這樣就可以生成DLL文件。
? ? ? ? 生成Lib文件
%LJLIB% /OUT:lua51.lib lj_*.obj lib_*.obj
? ? ? ? 除了我們要新建一個win32 lib工程外,其他設置和DLL一樣。
? ? ? ? 生成Exe文件
%LJCOMPILE% luajit.c
@if errorlevel 1 goto :BAD
%LJLINK% /out:luajit.exe luajit.obj lua51.lib
@if errorlevel 1 goto :BAD
if exist luajit.exe.manifest^%LJMT% -manifest luajit.exe.manifest -outputresource:luajit.exe
? ? ? ? 新建一個Win32 Exe工程LuajitExe,將luajit.c拷貝到工程中,同時設置Linker->Input->Additional Dependencies的值為$(TargetDir)LualibProject.lib。在C\C++->General->Additional Include Directories設置"$(SolutionDir)Header";"$(SolutionDir)OtherHeader";"$(SolutionDir)Lj";"$(SolutionDir)Builvmheader";"$(SolutionDir)OtherLualib";"$(SolutionDir)Lualib"。這樣Exe程序生成OK。
? ? ?
? ? ? ? 當然,不否認。將批處理生成修改成IDE生成是一個簡單到復雜的過程。但是這個過程將有助于我們熟悉luajit的生成過程。也將有助于我們之后對其的改造。
總結
以上是生活随笔為你收集整理的在windows程序中嵌入Lua脚本引擎--使用VS IDE编译Luajit脚本引擎的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一种不会导致资源泄露的“终止”线程的方法
- 下一篇: 在windows程序中嵌入Lua脚本引擎