程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 為 Lua 綁定 C/C++ 對象

為 Lua 綁定 C/C++ 對象

編輯:C++入門知識

如何綁定 C/C++ 對象到 Lua 裡?通常是創建一個 userdata ,存放 C/C++ 對象指針,然後給 userdata 添加元表,用 index 元方法映射 C/C++ 中的對象方法。   也有另一個手段,直接用 lightuserdata 保存 C/C++ 對象指針放到 Lua 中,在 Lua 中創建一個 table 附加元表來來包裝這個指針,效果是類似的。區別在於對象生命期的管理方式有所不同。就這個問題,幾年前我寫過一篇 blog。   綁定 C/C++ 對象到 Lua 裡的設計難點往往在這個正確的生命期管理上。因為 C/C++ 沒有 GC 系統,依賴手工管理資源;而 Lua 則是利用 GC 做自動回收。這兩者的差異容易導致在 Lua 中的對象對應的 C/C++ 對象已經銷毀而 Lua 層不自知,或 Lua 層中已無對象之引用,而 C/C++ 層中卻未能及時回收資源而造成內存洩露。   理清這個問題,首先你要確定,你打算以 Lua 為主干來維護對象的生命期,還是以 C/C++ 層為主干 Lua 部分只是做一些對這些對象的行為控制。   我個人主張圍繞 Lua 來開發,C/C++ 只是寫一些性能相關的庫供 Lua 調用,即框架層在 Lua 中。這樣,C/C++ 層只提供對象的創建和銷毀函數,不要用 C 指針做對象的相互引用。Lua 中對象被回收時,銷毀對應的 C 對象即可。   但是,也有相當多的項目做不到這點。Lua 是在後期引入的,之前 C/C++ 框架層中已做好了相當之復雜的對象管理。或者構架師不希望把腳本層過多的侵入引擎的設計。   那麼,下面給出另一個方案。   我們將包裝進 Lua 的 C 對象稱為 script object ,那麼只需要提供三個函數即可。   int script_pushobject(lua_State *L, void * object) {     void **ud;     if (luaL_newmetatable(L, "script")) {         // 在注冊表中創建一個表存放所有的 object 指針到 userdata 的關系。         // 這個表應該是一個 weak table ,當 Lua 中不再存在對 C 對象的引用會刪除對應的記錄。         lua_newtable(L);         lua_pushliteral(L, "kv");         lua_setfield(L, -2, "__mode");         lua_setmetatable(L, -2);     }     lua_rawgetp(L,-1,object);     if (lua_type(L,-1)==LUA_TUSERDATA) {         ud = (void **)lua_touserdata(L,-1);         if (*ud == object) {             lua_replace(L, -2);             return 0;         }         // C 對象指針被釋放後,有可能地址被重用。         // 這個時候,可能取到曾經保存起來的 userdata ,裡面的指針必然為空。         assert(*ud == NULL);     }     ud = (void **)lua_newuserdata(L, sizeof(void*));     *ud = object;     lua_pushvalue(L, -1);     lua_rawsetp(L, -4, object);     lua_replace(L, -3);     lua_pop(L,1);     return 1; } 這個函數把一個 C 對象指針置入對應的 userdata ,如果是第一次 push 則創建出新的 userdata ,否則復用曾經創建過的。   void * script_toobject(lua_State *L, int index) {     void **ud = (void **)lua_touserdata(L,index);     if (ud == NULL)         return NULL;     // 如果 object 已在 C 代碼中銷毀,*ud 為 NULL 。     return *ud; } 這個函數把 index 處的 userdata 轉換為一個 C 對象。如果對象已經銷毀,則返回 NULL 指針。 在給這個對象綁定 C 方法時,應注意在 toobject 調用後,全部對指針做檢查,空指針應該被正確處理。   void script_deleteobject(lua_State *L, void *object) {     luaL_getmetatable(L, "script");     if (lua_istable(L,-1)) {         lua_rawgetp(L, -1, object);         if (lua_type(L,-1) == LUA_TUSERDATA) {             void **ud = (void **)lua_touserdata(L,-1);             // 這個 assert 防止 deleteobject 被重復調用。             assert(*ud == object);  www.2cto.com             // 銷毀一個被 Lua 引用住的對象,只需要把 *ud 置為 NULL 。             *ud = NULL;         }         lua_pop(L,2);     } else {         // 有可能從未調用過 pushobject ,此時注冊表中 script 項尚未建立。         lua_pop(L,1);     } } 這個函數會解除 C 對象在 Lua 中的引用,後續在 Lua 中對這個對象的訪問,都將得到 NULL 指針。   這些代碼是在我寫這篇 blog 的同時隨手寫的,並未經過嚴格測試。它們也有許多改進空間,比如給 C 對象加入類型,對 userdata 做更嚴格的檢查,等等。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved