Lua與C++交互初探之Lua調用C++
上一篇我們已經成功將Lua的運行環境搭建了起來,也成功在C++里調用了Lua函數。今天我來講解一下如何在Lua里調用C++函數。
Lua作為一個輕量級腳本語言,他只包含了一些必要的系統庫函數,當有需要時還得自己去寫。有一次我要做一個兩數異或的操作發現函數庫里居然沒有異或運算。不得不非常苦逼的自己去寫。后來接觸Lua深了之后才知道將這種"缺陷"可以由C函數來彌補。但要做到這一點對於一個對C只知道if else的學生來說確實還是有不少難度。
在學習調用的時候我也確實遇到了不少問題。一開始當然是看不懂代碼,各種指針以及莫名其妙的函數,各種頭大啊有木有。后來試着靜下心來慢慢理了一下Hello World程序,覺得是一知半解了就把代碼拷到編譯器里運行一下。是的,各種報錯。錯得我都沒信心再看下去了。
好了,上面都是一些沒用的,現在開始步入正題。
Lua調用C++有兩種方法:
-
將C函數作為應用程序的一部分
-
將C函數作為Lua的一個模塊
這里解釋一下,將C函數作為程序的一部分,就是說源文件有main方法,函數在main方法里注冊,使得c函數成為Lua里的全局函數,供lua直接調用;將C函數作為Lua的一個模塊。本人比較喜歡這種方式。其實是將C函數注冊后封裝到一個模塊中,在windows系統中以dll的文件形式存在。在使用時我們只需要將他require進來就行了,另外dll文件也非常方便復制和轉移,也保護了源碼。所以下面將主要講解如何將C函數注冊成為Lua的模塊庫。記住:我之所以說是C函數,是因為函數必須以C的形式導出,因此在C++里在函數名前必須加"extern "C"";這一點下面你將會看到
下面來講講步驟:
-
在VS里新建一個VC++的win32項目(控制台應用亦可)
-
點擊下一步選擇dll(以前我們為了省事直接點了finish,這里我們得改一下后面的設置)
-
新建完成后配置好lua在VS里的運行環境,不會的可以看一下我的上一篇文章
-
輸入下面代碼
// Mydll.cpp : 定義 DLL 應用程序的導出函數。 // #include "stdafx.h" #include <stdio.h> #include <string.h> #include <lua.hpp> #include <lauxlib.h> #include <lualib.h> //待注冊的C函數 //需要說明的是,該函數必須以C的形式被導出,因此extern "C"是必須的。 //定義一個求取平均數的函數 extern "C" int average(lua_State* L) { double sum=0; int num = lua_gettop(L);//獲取參數的個數 for (int i = 1; i <= num;i++) sum+= lua_tonumber(L, i); //依次獲取所有參數值,相加 lua_pushnumber(L, sum/num);//將平均數壓如棧,供lua獲取 return 1;//返回返回值個數,通知lua應該在棧里取幾個值作為返回結果 } extern "C" int Communicate(lua_State* L) { const char *name = lua_tostring(L, 1);//獲取字符串 printf("Hello %s\n", name); printf("I`m in C,I send a message to you"); lua_pushstring(L, "This message from C"); return 1; } //luaL_Reg結構體的第一個字段為字符串,在注冊時用於通知Lua該函數的名字。 //第一個字段為C函數指針。 //結構體數組中的最后一個元素的兩個字段均為NULL,用於提示Lua注冊函數已經到達數組的末尾。 static luaL_Reg cMethods[] = { { "average", average }, { "Communicate", Communicate }, { NULL, NULL } }; //該C庫的唯一入口函數。其函數簽名等同於上面的注冊函數。見如下幾點說明: //1. 我們可以將該函數簡單的理解為模塊的工廠函數。 //2. 其函數名必須為luaopen_xxx,其中xxx表示library名稱。Lua代碼require "xxx"需要與之對應。 //3. 在luaL_register的調用中,其第一個字符串參數為模塊名"xxx",第二個參數為待注冊函數的數組。 //4. 需要強調的是,所有需要用到"xxx"的代碼,不論C還是Lua,都必須保持一致,這是Lua的約定, // 否則將無法調用。 extern "C" __declspec(dllexport) int luaopen_Mydll(lua_State* L) { const char* libName = "Mydll"; luaL_register(L, libName, cMethods); return 1; }
-
點擊運行后我們把編譯好的dll文件復制到lua解析器能找到的位置(lua文件同目錄或者LUA_CPATH指向的路徑一般在lua安裝目錄的clibs目錄下)
-
下面編寫lua測試代碼
require "Mydll" --引入包 --在調用時,必須是package.function print(Mydll.average(1.0,2.0,3.0,4)); print(Mydll.Communicate("Zack"));
-
測試運行
注意他的打印順序,因為我在Communicate函數里只向棧里壓了"This message from C"這一個字符串,所以在Lua端他只向棧里取了一個元素作為返回值。還有就是注意一下在C++里面的輸出,一般情況我們使用C++處理邏輯不會有輸出操作,不過他的順序沒有反。下面有個很有意思的東西
把Communicate函數提到average函數前面,結果更意外:
C++的所有輸出都放在了最后面